@brainervirus/workit-cli 8.39.2 → 8.39.4

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.
Files changed (2) hide show
  1. package/dist/index.js +413 -33
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -9317,7 +9317,7 @@ var package_default;
9317
9317
  var init_package = __esm(() => {
9318
9318
  package_default = {
9319
9319
  name: "@brainervirus/workit-cli",
9320
- version: "8.39.2",
9320
+ version: "8.39.4",
9321
9321
  private: false,
9322
9322
  description: "Workit CLI — setup wizard, doctor, and task control for agentic coding workflows",
9323
9323
  keywords: [
@@ -9355,13 +9355,13 @@ var init_package = __esm(() => {
9355
9355
  },
9356
9356
  dependencies: {
9357
9357
  "@babel/parser": "7.29.9",
9358
- "@brainervirus/workit-cursor": "^8.39.2",
9358
+ "@brainervirus/workit-cursor": "^8.39.4",
9359
9359
  "@inkjs/ui": "2.0.0",
9360
9360
  ink: "7.1.1",
9361
9361
  react: "19.2.8"
9362
9362
  },
9363
9363
  devDependencies: {
9364
- "@brainervirus/workit-core": "^8.39.2"
9364
+ "@brainervirus/workit-core": "^8.39.4"
9365
9365
  },
9366
9366
  engines: {
9367
9367
  node: ">=24"
@@ -9462,6 +9462,11 @@ function detectedChecks(root) {
9462
9462
  if (typeof script === "string" && script.trim() !== "")
9463
9463
  add(scriptCheck(pm, name));
9464
9464
  }
9465
+ for (const [name, script] of Object.entries(SCRIPT_CHECK_ALIASES)) {
9466
+ const body = scripts[script];
9467
+ if (typeof body === "string" && body.trim() !== "")
9468
+ add({ ...scriptCheck(pm, script), name });
9469
+ }
9465
9470
  }
9466
9471
  const plain = (name, argv) => ({ name, argv, accepts: [argv] });
9467
9472
  if (read("go.mod") !== null)
@@ -9580,7 +9585,7 @@ function workitAvailable(env = process.env) {
9580
9585
  function checkCommand(args, version, onPath = workitAvailable()) {
9581
9586
  return onPath ? `workit check ${args}` : `npx -y @brainervirus/workit-cli@${version} check ${args}`;
9582
9587
  }
9583
- var SCRIPT_CHECKS, CHECKS_FILE = ".workit/checks.json", LEGACY_CHECKS_FILE = "workit.checks.json", WORKIT_GITIGNORE = `*
9588
+ var SCRIPT_CHECKS, SCRIPT_CHECK_ALIASES, CHECKS_FILE = ".workit/checks.json", LEGACY_CHECKS_FILE = "workit.checks.json", WORKIT_GITIGNORE = `*
9584
9589
  !risk-paths
9585
9590
  `, isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), SHELL_META, git3 = (cwd, args) => {
9586
9591
  const run = spawnSync7("git", args, {
@@ -9610,6 +9615,7 @@ var SCRIPT_CHECKS, CHECKS_FILE = ".workit/checks.json", LEGACY_CHECKS_FILE = "wo
9610
9615
  }, CHECK_NAME, GATES, sameArgv = (left, right) => left.length === right.length && left.every((item, index) => item === right[index]), workitOnPath;
9611
9616
  var init_check_config = __esm(() => {
9612
9617
  SCRIPT_CHECKS = ["test", "lint", "typecheck", "check"];
9618
+ SCRIPT_CHECK_ALIASES = { packaging: "test:packaging" };
9613
9619
  SHELL_META = /[|&;<>()$`\\*?[\]{}~!#]/u;
9614
9620
  CHECK_NAME = /^[a-z0-9][a-z0-9:._-]{0,63}$/u;
9615
9621
  GATES = ["testing", "verification"];
@@ -15571,6 +15577,7 @@ function recordVerdict(context, input) {
15571
15577
  sessionSource: context.sessionSource ?? "env",
15572
15578
  ...context.sessionBound === true ? { sessionBound: true } : {},
15573
15579
  evidenceRefs: input.evidenceRefs ?? [],
15580
+ ...input.checkEvidence?.length ? { checkEvidence: input.checkEvidence } : {},
15574
15581
  ...untrackedField(context.cwd)
15575
15582
  }, { now: context.now });
15576
15583
  }
@@ -21298,7 +21305,10 @@ async function payloadValue(raw, deps) {
21298
21305
  }
21299
21306
  try {
21300
21307
  const value = JSON.parse(source);
21301
- return isObject7(value) ? { ok: true, value } : { ok: false, result: failure2("invalid_input", "payload must be a JSON object") };
21308
+ return isObject7(value) ? { ok: true, value } : {
21309
+ ok: false,
21310
+ result: failure2("invalid_input", "payload must be a JSON object")
21311
+ };
21302
21312
  } catch (error) {
21303
21313
  return {
21304
21314
  ok: false,
@@ -21493,7 +21503,11 @@ function laneOf(root) {
21493
21503
  } catch {
21494
21504
  verification = DEFAULT_VERIFICATION;
21495
21505
  }
21496
- const lane = branchLane(root, { head: null, workingTree: true, judgment: currentJudgment(root) });
21506
+ const lane = branchLane(root, {
21507
+ head: null,
21508
+ workingTree: true,
21509
+ judgment: currentJudgment(root)
21510
+ });
21497
21511
  return {
21498
21512
  lane: lane.lane,
21499
21513
  reasons: lane.reasons,
@@ -21567,7 +21581,69 @@ function printHuman(result, deps, handoff = false) {
21567
21581
  write(stream, `policy gap: ${gap.reason}`);
21568
21582
  }
21569
21583
  }
21584
+ function runFindingList(argv, deps) {
21585
+ let json = false;
21586
+ let branch;
21587
+ let status;
21588
+ const fail = (message) => {
21589
+ if (json)
21590
+ jsonResult(outOf(deps), usage(message));
21591
+ else
21592
+ write(errOf(deps), `${message}
21593
+ usage: ${FINDING_LIST_USAGE}`);
21594
+ return 2;
21595
+ };
21596
+ for (let i = 0;i < argv.length; i += 1) {
21597
+ const eq = argv[i].startsWith("--") ? argv[i].indexOf("=") : -1;
21598
+ const token = eq > 0 ? argv[i].slice(0, eq) : argv[i];
21599
+ if (token === "--json")
21600
+ json = true;
21601
+ else if (token === "--branch" || token === "--status") {
21602
+ const value = eq > 0 ? argv[i].slice(eq + 1) : argv[++i];
21603
+ if (value === undefined || !value.trim())
21604
+ return fail(`${token} requires a value`);
21605
+ if (token === "--branch")
21606
+ branch = value;
21607
+ else
21608
+ status = value;
21609
+ } else
21610
+ return fail(`unknown argument: ${argv[i]}`);
21611
+ }
21612
+ if (status !== undefined && status !== "open" && status !== "resolved")
21613
+ return fail(`--status is open or resolved (got ${status})`);
21614
+ const store = new TaskStore(workspaceRootFor(deps));
21615
+ const index = store.listTaskIndex();
21616
+ const tasks = store.listTasks();
21617
+ const result = !index.ok ? index : !tasks.ok ? tasks : success(null, null, null);
21618
+ if (!result.ok || !index.ok || !tasks.ok) {
21619
+ if (json)
21620
+ jsonResult(outOf(deps), result);
21621
+ else
21622
+ printHuman(result, deps);
21623
+ return 1;
21624
+ }
21625
+ const keys = new Map(index.data.map((entry) => [entry.id, entry.key]));
21626
+ const rows = tasks.data.filter((task) => branch === undefined || keys.get(task.id) === branch).flatMap((task) => task.findings.filter((item) => status === undefined || item.data.disposition === "open" === (status === "open")).map((item) => ({
21627
+ id: item.id,
21628
+ taskId: task.id,
21629
+ branch: keys.get(task.id) ?? null,
21630
+ disposition: item.data.disposition,
21631
+ recordedAt: item.recordedAt,
21632
+ claim: item.data.claim
21633
+ })));
21634
+ const total = tasks.data.reduce((sum, task) => sum + task.findings.length, 0);
21635
+ if (json)
21636
+ jsonResult(outOf(deps), success(null, null, { findings: rows, total }));
21637
+ else if (!rows.length)
21638
+ write(outOf(deps), total ? `no findings match the filters (${total} finding${total === 1 ? "" : "s"} in all; drop --branch/--status to list them)` : "no findings recorded (record one: workit finding record --payload '{…}'); ledger rows hold verdicts and rulings, not findings");
21639
+ else
21640
+ for (const row of rows)
21641
+ write(outOf(deps), `${row.id} ${row.disposition}${row.branch ? ` [${row.branch}]` : ""} (task ${row.taskId}) ${row.claim}`);
21642
+ return 0;
21643
+ }
21570
21644
  async function runTaskCommand(argv, deps = {}) {
21645
+ if (argv[0] === "finding" && argv[1] === "list")
21646
+ return runFindingList(argv.slice(2), deps);
21571
21647
  const parsed = await parseTaskArgs(argv, deps);
21572
21648
  if (!parsed.ok) {
21573
21649
  if (parsed.json)
@@ -21580,7 +21656,9 @@ async function runTaskCommand(argv, deps = {}) {
21580
21656
  const store = new TaskStore(root);
21581
21657
  try {
21582
21658
  const now = new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
21583
- closeDeliveredTasks(store, now, { cached: parsed.parsed.action !== "list" });
21659
+ closeDeliveredTasks(store, now, {
21660
+ cached: parsed.parsed.action !== "list"
21661
+ });
21584
21662
  } catch {}
21585
21663
  const core = new WorkitCore(store, contextFor(root, parsed.parsed.actor !== undefined ? { ...deps, actor: parsed.parsed.actor } : deps));
21586
21664
  let result;
@@ -21671,7 +21749,7 @@ var TASK_FAMILIES, workspaceRootFor = (deps = {}) => {
21671
21749
  capabilities: deps.capabilities ?? [],
21672
21750
  constraints: deps.constraints ?? [],
21673
21751
  now: deps.now ?? (() => new Date().toISOString())
21674
- });
21752
+ }), FINDING_LIST_USAGE = "workit finding list [--branch <b>] [--status open|resolved] [--json]";
21675
21753
  var init_task = __esm(() => {
21676
21754
  init_check_config();
21677
21755
  init_autonomy();
@@ -21688,7 +21766,7 @@ var init_task = __esm(() => {
21688
21766
  task: ["start", "list", "inspect", "revise", "progress", "pause", "resume", "close"],
21689
21767
  policy: ["assess", "preview", "explain"],
21690
21768
  evidence: ["record"],
21691
- finding: ["record", "resolve"],
21769
+ finding: ["record", "resolve", "list"],
21692
21770
  decision: ["record", "revoke"],
21693
21771
  worker: ["assign", "report", "cancel"],
21694
21772
  state: ["export", "import"]
@@ -78399,7 +78477,7 @@ var init_resources = __esm(() => {
78399
78477
  init_checks3();
78400
78478
  init_heavy();
78401
78479
  init_heavy_scope();
78402
- DEFAULT_HEAVY_CHECKS = ["test", "build"];
78480
+ DEFAULT_HEAVY_CHECKS = ["test", "build", "packaging"];
78403
78481
  DEFAULT_LIGHT_CHECKS = ["lint", "typecheck", "format"];
78404
78482
  SIZE = /^(\d+(?:\.\d+)?)([KMGT]?)$/iu;
78405
78483
  PERCENT = /^(\d+(?:\.\d+)?)%$/u;
@@ -78617,28 +78695,39 @@ function countWarnings(log) {
78617
78695
  return redactLog(log).replace(ANSI_COLOR, "").split(`
78618
78696
  `).filter((line) => WARNING_LINE.test(line) && !NO_WARNINGS.test(line)).length;
78619
78697
  }
78620
- function boundedOutput(log, lines, stderr = "") {
78698
+ function boundedOutput(log, lines, stderr = "", failed = false) {
78621
78699
  const all = tailLines(log, Number.MAX_SAFE_INTEGER);
78622
78700
  if (all.length === 0)
78623
78701
  return "";
78624
- let shown = all.slice(-lines);
78702
+ const markers = failed ? [...new Set(all.filter((line) => FAILURE_LINE.test(line)))] : [];
78703
+ const listed = markers.slice(0, MAX_FAILURE_LINES);
78704
+ const more = markers.length - listed.length;
78705
+ const failureRoom = listed.length ? listed.length + 2 + (more > 0 ? 1 : 0) : 0;
78706
+ let shown = all.slice(-Math.max(1, lines - failureRoom));
78625
78707
  const errors = tailLines(redactLog(stderr), STDERR_TAIL_LINES);
78626
78708
  let missing = [];
78627
78709
  if (errors.length) {
78628
78710
  const tail = new Set(shown);
78629
78711
  if (errors.some((line) => !tail.has(line))) {
78630
- shown = all.slice(-Math.max(1, lines - errors.length - 2));
78712
+ shown = all.slice(-Math.max(1, lines - failureRoom - errors.length - 2));
78631
78713
  const kept = new Set(shown);
78632
78714
  missing = errors.filter((line) => !kept.has(line));
78633
78715
  }
78634
78716
  }
78635
78717
  const omitted = all.length - shown.length;
78718
+ const inTail = new Set([...shown, ...missing]);
78719
+ const failures = listed.filter((line) => !inTail.has(line));
78720
+ const failureFooter = failures.length ? `… failures from the omitted lines:
78721
+ ${failures.join(`
78722
+ `)}
78723
+ ${more > 0 ? `+${more} more failures in the log
78724
+ ` : ""}` : "";
78636
78725
  const footer = missing.length ? `… the last stderr lines, which the merged log holds earlier:
78637
78726
  ${missing.join(`
78638
78727
  `)}
78639
78728
  ` : "";
78640
78729
  return `${omitted ? `… ${omitted} earlier line${omitted === 1 ? "" : "s"} omitted (the full log is named below; --full streams it)
78641
- ` : ""}${shown.join(`
78730
+ ` : ""}${failureFooter}${shown.join(`
78642
78731
  `)}
78643
78732
  ${footer}`;
78644
78733
  }
@@ -78938,7 +79027,7 @@ async function run12(argv, io) {
78938
79027
  `);
78939
79028
  else {
78940
79029
  if (!options.full)
78941
- io.stdout(boundedOutput(result.log, OUTPUT_LINES, stderrTail));
79030
+ io.stdout(boundedOutput(result.log, OUTPUT_LINES, stderrTail, !passed));
78942
79031
  io.stderr(`${summary}
78943
79032
  `);
78944
79033
  if (recordError && passed)
@@ -78955,7 +79044,7 @@ async function run12(argv, io) {
78955
79044
  return recordError.code === "busy" ? 4 : recordError.code === "invalid_input" ? 2 : 5;
78956
79045
  return 0;
78957
79046
  }
78958
- var USAGE6 = 'workit check <name> [--full] [--wait <dur> | --no-wait] [--no-lease --reason "<why>"] | workit check [--name <n>] [--shell] [--heavy] [--full] [--wait <dur> | --no-wait] [--timeout <s>] [--task <id>] [--json] -- <cmd…>', OUTPUT_LINES = 60, MAX_UNVERIFIED_RUN_MS, RECORD_TAIL_LINES = 20, RECORD_TAIL_CHARS = 200, posix = (value) => value.split(path53.sep).join("/") || ".", ENGINE_CODES2, FINGERPRINT_VARS, ANSI_COLOR, WARNING_LINE, NO_WARNINGS, STDERR_TAIL_LINES = 10, STDERR_KEEP_CHARS = 65536;
79047
+ var USAGE6 = 'workit check <name> [--full] [--wait <dur> | --no-wait] [--no-lease --reason "<why>"] | workit check [--name <n>] [--shell] [--heavy] [--full] [--wait <dur> | --no-wait] [--timeout <s>] [--task <id>] [--json] -- <cmd…>', OUTPUT_LINES = 60, MAX_UNVERIFIED_RUN_MS, RECORD_TAIL_LINES = 20, RECORD_TAIL_CHARS = 200, posix = (value) => value.split(path53.sep).join("/") || ".", ENGINE_CODES2, FINGERPRINT_VARS, ANSI_COLOR, WARNING_LINE, NO_WARNINGS, STDERR_TAIL_LINES = 10, STDERR_KEEP_CHARS = 65536, FAILURE_LINE, MAX_FAILURE_LINES = 15;
78959
79048
  var init_check = __esm(() => {
78960
79049
  init_check_config();
78961
79050
  init_checks3();
@@ -78976,6 +79065,7 @@ var init_check = __esm(() => {
78976
79065
  ANSI_COLOR = new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, "g");
78977
79066
  WARNING_LINE = /^\s*(?:⚠|\[(?:WARN|WARNING|warn|warning)\]|(?:npm\s+)?(?:WARN\s|warn\s)|warn(?:ing)?(?:\[[^\]]*\])?:|\d+:\d+\s+warning\s|\S+\(\d+,\d+\):\s*warning\s|\S+:\d+:\d+:\s*warning\b)/;
78978
79067
  NO_WARNINGS = /\b(?:no|0|zero|none)\s+warnings?\b/i;
79068
+ FAILURE_LINE = /^\s*(?:\(fail\)|FAIL\b|✗|error:)/u;
78979
79069
  });
78980
79070
 
78981
79071
  // packages/workit-cli/src/verbs/lock.ts
@@ -80398,6 +80488,67 @@ var init_pr_lane = __esm(() => {
80398
80488
  init_lane();
80399
80489
  });
80400
80490
 
80491
+ // packages/workit-core/src/core/policy/check-evidence.ts
80492
+ function neededChecks(lane, configured) {
80493
+ if (lane !== "focused" && lane !== "full")
80494
+ return [];
80495
+ const names = configured.filter((name) => name !== AGGREGATE);
80496
+ return names.length ? names : [...configured];
80497
+ }
80498
+ function localCheckRows(rows) {
80499
+ return rows.filter((row) => row.type === "check" && row.observer === "workit_cli").map((row) => ({
80500
+ name: String(row.name ?? ""),
80501
+ head: String(row.head ?? ""),
80502
+ dirty: row.dirty === true,
80503
+ passed: row.result === "passed"
80504
+ }));
80505
+ }
80506
+ function verdictEvidence(cwd, input) {
80507
+ const lane = branchLane(cwd, { base: input.base ?? null, head: input.head });
80508
+ return checkEvidence({
80509
+ lane: lane.lane,
80510
+ head: input.head,
80511
+ configured: gateCheckNames(loadCheckConfig(cwd), "verification"),
80512
+ local: localCheckRows(input.rows),
80513
+ ci: []
80514
+ });
80515
+ }
80516
+ function checkEvidence(input) {
80517
+ const ciCovers = input.lane === "focused";
80518
+ const needed = neededChecks(input.lane, input.configured).map((name) => {
80519
+ const last = input.local.findLast((row) => row.name === name && row.head === input.head && !row.dirty);
80520
+ if (last?.passed)
80521
+ return { name, status: "fresh", via: "local", ciCovers };
80522
+ const ci = input.ci.filter((check) => ciMatch(check, name));
80523
+ if (ci.some((check) => check.state === "failing"))
80524
+ return { name, status: "failing", via: "ci", ciCovers };
80525
+ if (last)
80526
+ return { name, status: "failing", via: "local", ciCovers };
80527
+ if (ciCovers && ci.some((check) => check.state === "passing"))
80528
+ return { name, status: "fresh", via: "ci", ciCovers };
80529
+ return { name, status: "missing", via: null, ciCovers };
80530
+ });
80531
+ return {
80532
+ lane: input.lane,
80533
+ head: input.head,
80534
+ needed,
80535
+ satisfied: needed.every((item) => item.status === "fresh")
80536
+ };
80537
+ }
80538
+ function evidenceLines(doc) {
80539
+ if (!doc.needed.length)
80540
+ return [`evidence: lane ${doc.lane} needs no check run`];
80541
+ return [
80542
+ `evidence for lane ${doc.lane} on ${doc.head.slice(0, 7)}: ${doc.satisfied ? "complete" : "incomplete"}`,
80543
+ ...doc.needed.map((item) => ` ${item.status === "fresh" ? "ok" : "x"} ${item.name}: ${item.status}${item.via ? ` (${item.via})` : item.ciCovers ? " (run it, or green CI covers it)" : " (run workit check " + item.name + " on this head)"}`)
80544
+ ];
80545
+ }
80546
+ var AGGREGATE = "check", ciMatch = (ci, name) => new RegExp(`(^|[^a-z0-9])${name.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&")}([^a-z0-9]|$)`, "iu").test(ci.name);
80547
+ var init_check_evidence = __esm(() => {
80548
+ init_check_config();
80549
+ init_lane();
80550
+ });
80551
+
80401
80552
  // packages/workit-core/src/forge/reruns.ts
80402
80553
  import fs27 from "node:fs";
80403
80554
  import path58 from "node:path";
@@ -80614,6 +80765,14 @@ function selectPr(cwd, resolved, options) {
80614
80765
  }
80615
80766
  return success2(found.data.number);
80616
80767
  }
80768
+ function evidenceFor(cwd, status, ci = status.checks) {
80769
+ const lane = branchLane(cwd, { base: status.base, head: status.head.sha });
80770
+ const configured = gateCheckNames(loadCheckConfig(cwd), "verification");
80771
+ const read = readLedger(cwd);
80772
+ const local = read.ok ? localCheckRows(read.value.rows) : [];
80773
+ const ciChecks = ci.flatMap((check) => check.state === "skipped" ? [] : [{ name: check.name, state: check.state }]);
80774
+ return checkEvidence({ lane: lane.lane, head: status.head.sha, configured, local, ci: ciChecks });
80775
+ }
80617
80776
  function buildStatusDoc(cwd, resolved, status, options) {
80618
80777
  const { forge } = resolved;
80619
80778
  const reruns = rerunCounts(cwd, { repo: forge.repo, pr: status.number, head: status.head.sha });
@@ -80691,6 +80850,7 @@ function buildStatusDoc(cwd, resolved, status, options) {
80691
80850
  passing: status.checks.filter((check) => check.state === "passing").length,
80692
80851
  missingRequired: missing
80693
80852
  },
80853
+ evidence: evidenceFor(cwd, status),
80694
80854
  reviews: { decision: status.reviewDecision, unresolvedThreads: status.threads },
80695
80855
  blockers: status.state === "open" ? blockers.map((blocker) => blocker.reason) : [],
80696
80856
  identity: options.identity ?? null,
@@ -80874,6 +81034,9 @@ var MAX_LOG_TAILS = 5, GITLAB_MERGEABLE, GITHUB_MERGEABLE, NO_CHECKS_GRACE_MS =
80874
81034
  var init_report2 = __esm(() => {
80875
81035
  init_rev();
80876
81036
  init_ledger();
81037
+ init_check_config();
81038
+ init_lane();
81039
+ init_check_evidence();
80877
81040
  init_reruns();
80878
81041
  GITLAB_MERGEABLE = new Set(["mergeable"]);
80879
81042
  GITHUB_MERGEABLE = new Set(["clean", "unstable", "has_hooks"]);
@@ -86620,6 +86783,8 @@ function renderStatus(doc) {
86620
86783
  lines.push(` - ${thread.path ? `${thread.path}${thread.line ? `:${thread.line}` : ""} ` : ""}@${thread.author ?? "?"}${thread.isBot ? " (bot)" : ""}: ${thread.body}`);
86621
86784
  if (doc.truncated)
86622
86785
  lines.push("(lists truncated at the page cap)");
86786
+ if (doc.evidence)
86787
+ lines.push(...evidenceLines(doc.evidence));
86623
86788
  if (doc.blockers.length)
86624
86789
  lines.push(`blockers: ${doc.blockers.join(", ")}`);
86625
86790
  if (doc.identity?.note)
@@ -86636,6 +86801,7 @@ var forgeDeps, MAX_REASON = 500, usage4 = (io, message, line) => emit(io, fail2(
86636
86801
  now: forgeDeps.now
86637
86802
  }), short = (sha) => sha ? sha.slice(0, 7) : "?";
86638
86803
  var init_forge_common = __esm(() => {
86804
+ init_check_evidence();
86639
86805
  init_resolve();
86640
86806
  init_vcs_config();
86641
86807
  init_duration();
@@ -86652,10 +86818,124 @@ var init_forge_common = __esm(() => {
86652
86818
  // packages/workit-cli/src/verbs/pr.ts
86653
86819
  var exports_pr = {};
86654
86820
  __export(exports_pr, {
86821
+ checkPrTitle: () => checkPrTitle,
86655
86822
  run: () => run15
86656
86823
  });
86657
- import { readFileSync as readFileSync27 } from "node:fs";
86824
+ import { existsSync as existsSync26, readFileSync as readFileSync27 } from "node:fs";
86658
86825
  import path66 from "node:path";
86826
+ function titleRules(cwd) {
86827
+ let root;
86828
+ try {
86829
+ root = checkRoot(cwd);
86830
+ } catch {
86831
+ return { rules: null, note: null };
86832
+ }
86833
+ let text = null;
86834
+ let name = "";
86835
+ for (const candidate of COMMITLINT_FILES) {
86836
+ const full = path66.join(root, candidate);
86837
+ if (!existsSync26(full))
86838
+ continue;
86839
+ text = readFileSync27(full, "utf8");
86840
+ name = candidate;
86841
+ break;
86842
+ }
86843
+ if (text === null) {
86844
+ try {
86845
+ const pkg = JSON.parse(readFileSync27(path66.join(root, "package.json"), "utf8"));
86846
+ if (pkg.commitlint === undefined)
86847
+ return { rules: null, note: null };
86848
+ text = JSON.stringify(pkg.commitlint);
86849
+ } catch {
86850
+ return { rules: null, note: null };
86851
+ }
86852
+ }
86853
+ if (/\.[cm]?[jt]s$/u.test(name))
86854
+ return UNCHECKED("JS/TS");
86855
+ let config;
86856
+ try {
86857
+ const parsed = JSON.parse(text);
86858
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
86859
+ return UNCHECKED("not a plain JSON object");
86860
+ config = parsed;
86861
+ } catch {
86862
+ return UNCHECKED(/\.ya?ml$|^\.commitlintrc$/u.test(name) ? "YAML" : "unreadable");
86863
+ }
86864
+ if (config.ignores !== undefined)
86865
+ return UNCHECKED("using custom ignores");
86866
+ const extend = config.extends === undefined ? [] : [config.extends].flat();
86867
+ const known = (entry) => typeof entry === "string" && /^(@commitlint\/)?config-conventional$/u.test(entry);
86868
+ if (!extend.every(known))
86869
+ return UNCHECKED("extending a shareable config");
86870
+ const rules = {
86871
+ types: null,
86872
+ maxHeader: null,
86873
+ format: 0,
86874
+ defaultIgnores: config.defaultIgnores !== false
86875
+ };
86876
+ if (extend.length) {
86877
+ rules.types = { level: 2, values: CONVENTIONAL_TYPES };
86878
+ rules.maxHeader = { level: 2, value: 100 };
86879
+ rules.format = 2;
86880
+ }
86881
+ const own = config.rules ?? {};
86882
+ const types = readRule(own, "type-enum");
86883
+ if (types === "off")
86884
+ rules.types = null;
86885
+ else if (types && Array.isArray(types.value))
86886
+ rules.types = { level: types.level, values: types.value.map(String) };
86887
+ const max = readRule(own, "header-max-length");
86888
+ if (max === "off")
86889
+ rules.maxHeader = null;
86890
+ else if (max && typeof max.value === "number")
86891
+ rules.maxHeader = { level: max.level, value: max.value };
86892
+ for (const rule of ["type-empty", "subject-empty"]) {
86893
+ const read = readRule(own, rule);
86894
+ if (read === "off")
86895
+ rules.format = 0;
86896
+ else if (read)
86897
+ rules.format = Math.max(rules.format, read.level);
86898
+ }
86899
+ return { rules, note: null };
86900
+ }
86901
+ function checkPrTitle(cwd, title) {
86902
+ const { rules, note } = titleRules(cwd);
86903
+ if (!rules)
86904
+ return { error: null, notes: note ? [note] : [] };
86905
+ const header = title.split(`
86906
+ `, 1)[0] ?? "";
86907
+ if (rules.defaultIgnores && DEFAULT_IGNORES.some((pattern) => pattern.test(title)))
86908
+ return { error: null, notes: [] };
86909
+ const problems = [];
86910
+ const parsed = /^(\w*)(?:\((.*)\))?!?: (.*)$/u.exec(header);
86911
+ if (rules.format && (!parsed || !parsed[1] || !parsed[3]?.trim()))
86912
+ problems.push({
86913
+ level: rules.format,
86914
+ message: `PR title ${JSON.stringify(header)} is not a Conventional Commit "<type>(<scope>): <subject>" (commitlint type-empty/subject-empty); the squash-merge title is the commit and CI lints it`
86915
+ });
86916
+ else if (parsed && rules.types && !rules.types.values.includes(parsed[1]))
86917
+ problems.push({
86918
+ level: rules.types.level,
86919
+ message: `PR title type "${parsed[1]}" is not allowed (commitlint type-enum: ${rules.types.values.join(", ")})`
86920
+ });
86921
+ if (rules.maxHeader && header.length > rules.maxHeader.value)
86922
+ problems.push({
86923
+ level: rules.maxHeader.level,
86924
+ message: `PR title is ${header.length} characters, over the ${rules.maxHeader.value} allowed (commitlint header-max-length); shorten it by ${header.length - rules.maxHeader.value}`
86925
+ });
86926
+ const refused = problems.filter((problem) => problem.level >= 2);
86927
+ return {
86928
+ error: refused.length ? refused.map((problem) => problem.message).join("; ") : null,
86929
+ notes: problems.filter((problem) => problem.level < 2).map((p) => `warning: ${p.message}`)
86930
+ };
86931
+ }
86932
+ function refuseTitle(io, title, line) {
86933
+ const checked = checkPrTitle(io.cwd, title);
86934
+ for (const note of checked.notes)
86935
+ io.stderr(`${note}
86936
+ `);
86937
+ return checked.error ? usage4(io, checked.error, line) : null;
86938
+ }
86659
86939
  function readBodyFile(io, file) {
86660
86940
  try {
86661
86941
  return {
@@ -86825,6 +87105,9 @@ async function create4(argv, io) {
86825
87105
  }
86826
87106
  if (!base)
86827
87107
  return usage4(io, "no default target branch is configured; pass --base <b>", CREATE_USAGE);
87108
+ const given = flags.values.title ? refuseTitle(io, flags.values.title, CREATE_USAGE) : null;
87109
+ if (given !== null)
87110
+ return given;
86828
87111
  const connected = connect(io, branch);
86829
87112
  if (!connected.ok)
86830
87113
  return forgeFail(io, connected);
@@ -86836,6 +87119,11 @@ async function create4(argv, io) {
86836
87119
  title ||= filled.data.title;
86837
87120
  body ??= filled.data.body;
86838
87121
  }
87122
+ if (!flags.values.title) {
87123
+ const filled = refuseTitle(io, title, CREATE_USAGE);
87124
+ if (filled !== null)
87125
+ return filled;
87126
+ }
86839
87127
  const forge = connected.data.forge;
86840
87128
  const planned = planIssueLinks(io.cwd, {
86841
87129
  issues: flags.lists.issue ?? [],
@@ -87054,6 +87342,11 @@ async function edit(argv, io) {
87054
87342
  return usage4(io, read, EDIT_USAGE);
87055
87343
  body = read.body;
87056
87344
  }
87345
+ if (flags.values.title !== undefined) {
87346
+ const edited = refuseTitle(io, flags.values.title, EDIT_USAGE);
87347
+ if (edited !== null)
87348
+ return edited;
87349
+ }
87057
87350
  const connected = connect(io);
87058
87351
  if (!connected.ok)
87059
87352
  return forgeFail(io, connected);
@@ -87156,7 +87449,19 @@ async function run15(argv, io) {
87156
87449
  return reply(rest, io);
87157
87450
  return usage4(io, sub && !sub.startsWith("-") ? `unknown pr subcommand "${sub}"` : "missing pr subcommand", USAGE9);
87158
87451
  }
87159
- var STATUS_USAGE = "workit pr status [--pr <n> | --branch <b>] [--log-lines 60] [--json]", CREATE_USAGE = "workit pr create [--base <b> | --track <t>] (--title <t> [--body <text> | --body-file <f|->] | --fill) [--issue <#n|ID|url>]… [--refs] [--label <l>]… [--reviewer <login>]… [--draft] [--json]", MERGE_USAGE = "workit pr merge [--pr <n>] [--squash|--merge|--rebase | --method squash|merge|rebase] [--subject <s>] [--body <b> | --body-file <f|->] [--delete-branch | --keep-branch] [--unverified --reason <why>] [--base-red-ok --reason <why>] [--json]", READY_USAGE = "workit pr ready [--pr <n>] [--undo] [--json]", EDIT_USAGE = "workit pr edit [--pr <n>] [--title <t>] [--body-file <f|->] [--add-label <l>]… [--remove-label <l>]… [--add-reviewer <login>]… [--base <b>] [--json]", THREADS_USAGE = "workit pr threads [--pr <n>] [--json]", REPLY_USAGE = "workit pr reply [--pr <n>] --thread <id> [--body-file <f|->] [--resolve] [--json]", USAGE9 = "workit pr status|create|merge|ready|edit|threads|reply ... (workit help pr)", prLabel = (kind, number) => `${kind === "github" ? "PR #" : "MR !"}${number}`, NEED_TEXT, mergedVerdictLine = (landed) => landed === "untracked" ? "merged outside workit (no merge record)" : landed.unverified ? `merged unverified at ${(landed.head ?? "?").slice(0, 12)} (recorded bypass)` : landed.lane === "none" && !landed.verdictId ? `merged at ${(landed.head ?? "?").slice(0, 12)} with no verdict (lane none)` : landed.review === "self-reviewed" ? `merged self-reviewed (lane self) with the author's verdict ${landed.verdictId ?? "?"} at ${(landed.head ?? "?").slice(0, 12)}` : `merged with accepted verdict ${landed.verdictId ?? "?"} at ${(landed.head ?? "?").slice(0, 12)}${landed.intentOnly ? " (merge intent recorded; the merge call itself failed)" : ""}`, mergedOnLine = (verdict) => {
87452
+ var CONVENTIONAL_TYPES, COMMITLINT_FILES, DEFAULT_IGNORES, UNCHECKED = (what) => ({
87453
+ rules: null,
87454
+ note: `commitlint config is ${what}; title not pre-checked, CI checks it`
87455
+ }), readRule = (rules, name) => {
87456
+ const rule = rules[name];
87457
+ if (rule === undefined)
87458
+ return null;
87459
+ if (!Array.isArray(rule) || typeof rule[0] !== "number")
87460
+ return "off";
87461
+ if (rule[0] === 0 || rule[1] === "never")
87462
+ return "off";
87463
+ return { level: rule[0], value: rule[2] };
87464
+ }, STATUS_USAGE = "workit pr status [--pr <n> | --branch <b>] [--log-lines 60] [--json]", CREATE_USAGE = "workit pr create [--base <b> | --track <t>] (--title <t> [--body <text> | --body-file <f|->] | --fill) [--issue <#n|ID|url>]… [--refs] [--label <l>]… [--reviewer <login>]… [--draft] [--json]", MERGE_USAGE = "workit pr merge [--pr <n>] [--squash|--merge|--rebase | --method squash|merge|rebase] [--subject <s>] [--body <b> | --body-file <f|->] [--delete-branch | --keep-branch] [--unverified --reason <why>] [--base-red-ok --reason <why>] [--json]", READY_USAGE = "workit pr ready [--pr <n>] [--undo] [--json]", EDIT_USAGE = "workit pr edit [--pr <n>] [--title <t>] [--body-file <f|->] [--add-label <l>]… [--remove-label <l>]… [--add-reviewer <login>]… [--base <b>] [--json]", THREADS_USAGE = "workit pr threads [--pr <n>] [--json]", REPLY_USAGE = "workit pr reply [--pr <n>] --thread <id> [--body-file <f|->] [--resolve] [--json]", USAGE9 = "workit pr status|create|merge|ready|edit|threads|reply ... (workit help pr)", prLabel = (kind, number) => `${kind === "github" ? "PR #" : "MR !"}${number}`, NEED_TEXT, mergedVerdictLine = (landed) => landed === "untracked" ? "merged outside workit (no merge record)" : landed.unverified ? `merged unverified at ${(landed.head ?? "?").slice(0, 12)} (recorded bypass)` : landed.lane === "none" && !landed.verdictId ? `merged at ${(landed.head ?? "?").slice(0, 12)} with no verdict (lane none)` : landed.review === "self-reviewed" ? `merged self-reviewed (lane self) with the author's verdict ${landed.verdictId ?? "?"} at ${(landed.head ?? "?").slice(0, 12)}` : `merged with accepted verdict ${landed.verdictId ?? "?"} at ${(landed.head ?? "?").slice(0, 12)}${landed.intentOnly ? " (merge intent recorded; the merge call itself failed)" : ""}`, mergedOnLine = (verdict) => {
87160
87465
  if (verdict.review === "self-reviewed")
87161
87466
  return `self-reviewed (lane ${verdict.lane}): the author's verdict ${verdict.verdictId ?? "?"}; no independent verdict accepted for that head`;
87162
87467
  if (!verdict.verdictId)
@@ -87180,8 +87485,48 @@ var init_pr = __esm(() => {
87180
87485
  init_lane();
87181
87486
  init_pr_lane();
87182
87487
  init_vcs_config();
87488
+ init_check_config();
87183
87489
  init_output();
87184
87490
  init_forge_common();
87491
+ CONVENTIONAL_TYPES = [
87492
+ "build",
87493
+ "chore",
87494
+ "ci",
87495
+ "docs",
87496
+ "feat",
87497
+ "fix",
87498
+ "perf",
87499
+ "refactor",
87500
+ "revert",
87501
+ "style",
87502
+ "test"
87503
+ ];
87504
+ COMMITLINT_FILES = [
87505
+ ".commitlintrc.json",
87506
+ ".commitlintrc",
87507
+ ".commitlintrc.yaml",
87508
+ ".commitlintrc.yml",
87509
+ ".commitlintrc.js",
87510
+ ".commitlintrc.cjs",
87511
+ ".commitlintrc.mjs",
87512
+ ".commitlintrc.ts",
87513
+ "commitlint.config.js",
87514
+ "commitlint.config.cjs",
87515
+ "commitlint.config.mjs",
87516
+ "commitlint.config.ts"
87517
+ ];
87518
+ DEFAULT_IGNORES = [
87519
+ /^((Merge pull request)|(Merge (.*?) into (.*?)|(Merge branch (.*?)))(?:\r?\n)*$)/m,
87520
+ /^(Merge tag (.*?))(?:\r?\n)*$/m,
87521
+ /^(R|r)evert (.*)/,
87522
+ /^(R|r)eapply (.*)/,
87523
+ /^(amend|fixup|squash)!/,
87524
+ /^(Merged (.*?)(in|into) (.*)|Merged PR (.*): (.*))/,
87525
+ /^Merge remote-tracking branch(\s*)(.*)/,
87526
+ /^Automatic merge(.*)/,
87527
+ /^Auto-merged (.*?) into (.*)/,
87528
+ /^(chore(\([^)]+\))?:\s*)?v?\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?\s*$/
87529
+ ];
87185
87530
  NEED_TEXT = {
87186
87531
  none: "no verdict",
87187
87532
  self: "the author's own --self verdict (or an independent one)",
@@ -87638,10 +87983,10 @@ async function push(argv, io) {
87638
87983
  });
87639
87984
  if (typeof flags === "string")
87640
87985
  return usage4(io, flags, PUSH_USAGE);
87641
- const release = flags.lists.release;
87986
+ const release = flags.lists.release?.flatMap((value) => value.split(",").map((name) => name.trim()).filter(Boolean));
87642
87987
  if (release) {
87643
87988
  if (flags.booleans.size || flags.values.expect !== undefined || flags.positionals.length)
87644
- return usage4(io, "--release takes only ref names (repeat it: --release main --release v1.2.0)", PUSH_USAGE);
87989
+ return usage4(io, "--release takes only ref names (a comma list or a repeated flag: --release main,v1.2.0)", PUSH_USAGE);
87645
87990
  return releasePush(release, io);
87646
87991
  }
87647
87992
  if (flags.positionals.length)
@@ -87743,7 +88088,7 @@ async function run17(argv, io) {
87743
88088
  return trailers(rest, io);
87744
88089
  return usage4(io, sub && !sub.startsWith("-") ? `unknown git subcommand "${sub}"` : "missing git subcommand", USAGE10);
87745
88090
  }
87746
- var BRANCH_USAGE = "workit git branch <name> | --kind feature|bugfix|release|hotfix --slug <s> [--base <b>] [--track <t>] [--carry] [--json]", COMMIT_USAGE = "workit git commit -m|--message <msg>… | -F|--file <file|-> [--trailer <key: value>]… [--amend [--no-edit]] [--allow-empty] [-a|--all | [--] <paths…>] [--json]", PUSH_USAGE = "workit git push [--set-upstream] [--force-with-lease [--expect <sha>] [--overwrite-unintegrated]] [--release <main|develop|v-tag>…] [--json]", USAGE10 = "workit git branch|commit|push|trailers ... (workit help git)", short2 = (sha) => sha ? sha.slice(0, 12) : "(none)", COMMIT_SWITCHES, READ_ONLY;
88091
+ var BRANCH_USAGE = "workit git branch <name> | --kind feature|bugfix|release|hotfix --slug <s> [--base <b>] [--track <t>] [--carry] [--json]", COMMIT_USAGE = "workit git commit -m|--message <msg>… | -F|--file <file|-> [--trailer <key: value>]… [--amend [--no-edit]] [--allow-empty] [-a|--all | [--] <paths…>] [--json]", PUSH_USAGE = "workit git push [--set-upstream] [--force-with-lease [--expect <sha>] [--overwrite-unintegrated]] [--release <main|develop|v-tag>[,…]] [--json]", USAGE10 = "workit git branch|commit|push|trailers ... (workit help git)", short2 = (sha) => sha ? sha.slice(0, 12) : "(none)", COMMIT_SWITCHES, READ_ONLY;
87747
88092
  var init_git2 = __esm(() => {
87748
88093
  init_autonomy();
87749
88094
  init_branch();
@@ -90033,7 +90378,13 @@ async function run22(argv, io) {
90033
90378
  type: values.type,
90034
90379
  last: last ?? 50
90035
90380
  });
90036
- return emit(io, ok2({ path: ledger.value.path, skipped: ledger.value.skipped, rows: listed }), (data) => data.rows.length ? data.rows.map((row) => {
90381
+ const note = values.type === "finding" && listed.length === 0 ? "findings are not ledger rows; they live in the task: workit finding list [--branch <b>] [--status open|resolved]" : undefined;
90382
+ return emit(io, ok2({
90383
+ path: ledger.value.path,
90384
+ skipped: ledger.value.skipped,
90385
+ rows: listed,
90386
+ ...note ? { note } : {}
90387
+ }), (data) => note ? note : data.rows.length ? data.rows.map((row) => {
90037
90388
  const line = summarizeRow(row);
90038
90389
  return `#${line.seq} ${line.at} ${line.type}${line.branch ? ` [${line.branch}]` : ""}${line.labels.length ? ` (${line.labels.join(", ")})` : ""} ${line.summary}`;
90039
90390
  }) : "ledger is empty");
@@ -90105,6 +90456,16 @@ async function run22(argv, io) {
90105
90456
  const refusal = claimed === null ? null : sessionClaimRefusal(io.env, claimed, io.cwd, { token, flag: acting.source === "flag" });
90106
90457
  if (refusal)
90107
90458
  return emit(io, fail2("blocked", refusal.error, { unblock: refusal.unblock }));
90459
+ const evidence = values.self !== true && (rest[0] === "verified" || rest[0] === "tests-verified") ? verdictCheckEvidence(io.cwd, target.value, values.base ?? null) : null;
90460
+ const redCheck = evidence?.needed.find((item) => item.status === "failing");
90461
+ if (redCheck)
90462
+ return emit(io, fail2("blocked", `check_failing: the configured check "${redCheck.name}" is failing on ${evidence?.head.slice(0, 12)} (${redCheck.via ?? "local"} run); a ${rest[0]} verdict cannot be recorded`, {
90463
+ unblock: `fix it, then workit check ${redCheck.name} on this head, or record failed/blocked`
90464
+ }));
90465
+ const missingChecks = evidence?.needed.filter((item) => item.status === "missing") ?? [];
90466
+ if (missingChecks.length)
90467
+ io.stderr(`warning: no fresh run on ${evidence?.head.slice(0, 12)} of the configured check${missingChecks.length === 1 ? "" : "s"} ${missingChecks.map((item) => item.name).join(", ")}; the verdict records it as missing
90468
+ `);
90108
90469
  const bound = claimed !== null && issuedTokenMatches(io.cwd, claimed, token);
90109
90470
  const verdict = await recorded(recordVerdict({ ...context, ...bound ? { sessionBound: true } : {} }, {
90110
90471
  result: rest[0],
@@ -90113,6 +90474,13 @@ async function run22(argv, io) {
90113
90474
  surface: values.surface ?? null,
90114
90475
  self: values.self === true,
90115
90476
  evidenceRefs: values.evidence,
90477
+ ...evidence?.needed.length ? {
90478
+ checkEvidence: evidence.needed.map(({ name, status, via }) => ({
90479
+ name,
90480
+ status,
90481
+ via
90482
+ }))
90483
+ } : {},
90116
90484
  ..."derivedFrom" in acting ? { derivedFrom: acting.derivedFrom } : {}
90117
90485
  }));
90118
90486
  if (verdict.ok && verdict.value.selfReason === "flag" && !passedTestOn(io.cwd, verdict.value))
@@ -90129,6 +90497,13 @@ async function run22(argv, io) {
90129
90497
  ].join(`
90130
90498
  `));
90131
90499
  }
90500
+ function verdictCheckEvidence(cwd, branch, base) {
90501
+ const head = branch === null ? headSha(cwd) : resolveRef(cwd, branch);
90502
+ const ledger = readLedger(cwd);
90503
+ if (!head || !ledger.ok)
90504
+ return null;
90505
+ return verdictEvidence(cwd, { head, base, rows: ledger.value.rows });
90506
+ }
90132
90507
  function standingFanout(io, values) {
90133
90508
  if (values.fanout !== undefined) {
90134
90509
  const name = values.fanout.trim();
@@ -90273,6 +90648,7 @@ var init_ledger2 = __esm(() => {
90273
90648
  init_fanout();
90274
90649
  init_rev();
90275
90650
  init_host_session();
90651
+ init_check_evidence();
90276
90652
  init_implicit_task();
90277
90653
  init_output();
90278
90654
  OPTIONS = {
@@ -106153,7 +106529,7 @@ var init_rules = __esm(() => {
106153
106529
 
106154
106530
  // packages/workit-cli/src/test-audit/audit.ts
106155
106531
  import { spawnSync as spawnSync29 } from "node:child_process";
106156
- import { existsSync as existsSync26, readdirSync as readdirSync14, readFileSync as readFileSync29, statSync as statSync10 } from "node:fs";
106532
+ import { existsSync as existsSync27, readdirSync as readdirSync14, readFileSync as readFileSync29, statSync as statSync10 } from "node:fs";
106157
106533
  import path71 from "node:path";
106158
106534
  function git7(cwd, args) {
106159
106535
  const run = spawnSync29("git", ["-c", "core.quotepath=off", ...args], {
@@ -106180,7 +106556,7 @@ function expandPaths(cwd, inputs) {
106180
106556
  const missing = [];
106181
106557
  for (const input of inputs.length > 0 ? inputs : ["."]) {
106182
106558
  const full = path71.resolve(cwd, input);
106183
- if (!existsSync26(full))
106559
+ if (!existsSync27(full))
106184
106560
  missing.push(input);
106185
106561
  else if (statSync10(full).isDirectory())
106186
106562
  walkTests(full, files);
@@ -106296,7 +106672,7 @@ var init_audit = __esm(() => {
106296
106672
  // packages/workit-cli/src/test-audit/mutate.ts
106297
106673
  import { spawn as spawn2 } from "node:child_process";
106298
106674
  import {
106299
- existsSync as existsSync27,
106675
+ existsSync as existsSync28,
106300
106676
  lstatSync as lstatSync8,
106301
106677
  mkdirSync as mkdirSync12,
106302
106678
  mkdtempSync as mkdtempSync4,
@@ -106525,7 +106901,7 @@ function copyWorkingTree(repo) {
106525
106901
  }
106526
106902
  for (const parent of dirs) {
106527
106903
  const modules = path72.join(repo, parent, "node_modules");
106528
- if (existsSync27(modules) && !existsSync27(path72.join(tree, parent, "node_modules")))
106904
+ if (existsSync28(modules) && !existsSync28(path72.join(tree, parent, "node_modules")))
106529
106905
  linkNodeModules(modules, path72.join(tree, parent, "node_modules"), repo, tree);
106530
106906
  }
106531
106907
  return { dir, tree };
@@ -106575,7 +106951,7 @@ async function runMutation(options) {
106575
106951
  const relative = toPosix(path72.relative(repo, absolute));
106576
106952
  if (!CODE_FILE.test(relative) || TEST_FILE.test(relative) || relative.endsWith(".d.ts"))
106577
106953
  continue;
106578
- if (NOT_SOURCE.test(relative) || !existsSync27(absolute))
106954
+ if (NOT_SOURCE.test(relative) || !existsSync28(absolute))
106579
106955
  continue;
106580
106956
  try {
106581
106957
  candidates.push(...mutantsFor(readFileSync30(absolute, "utf8"), relative, lines));
@@ -106765,7 +107141,7 @@ var exports_test_audit = {};
106765
107141
  __export(exports_test_audit, {
106766
107142
  run: () => run23
106767
107143
  });
106768
- import { existsSync as existsSync28 } from "node:fs";
107144
+ import { existsSync as existsSync29 } from "node:fs";
106769
107145
  import path73 from "node:path";
106770
107146
  function parse8(argv) {
106771
107147
  const options = {
@@ -106889,7 +107265,7 @@ usage: ${USAGE16}`));
106889
107265
  }
106890
107266
  let files;
106891
107267
  if (options.diff && changed)
106892
- files = [...changed.keys()].filter((file) => TEST_FILE.test(toPosix(file)) && existsSync28(file)).toSorted();
107268
+ files = [...changed.keys()].filter((file) => TEST_FILE.test(toPosix(file)) && existsSync29(file)).toSorted();
106893
107269
  else {
106894
107270
  const expanded = expandPaths(io.cwd, options.paths);
106895
107271
  if (expanded.missing.length > 0)
@@ -107337,7 +107713,11 @@ var FAMILY_ACTIONS = {
107337
107713
  explain: "Explain the policy in force"
107338
107714
  },
107339
107715
  evidence: { record: "Record evidence against a task's requirements" },
107340
- finding: { record: "Record a finding", resolve: "Resolve a finding" },
107716
+ finding: {
107717
+ record: "Record a finding",
107718
+ resolve: "Resolve a finding",
107719
+ list: "List findings with their ids [--branch <b>] [--status open|resolved]"
107720
+ },
107341
107721
  decision: {
107342
107722
  record: "Record a task decision",
107343
107723
  revoke: "Revoke a task decision"
@@ -107384,7 +107764,7 @@ var familySubcommands = (name) => {
107384
107764
  const implicit = name === "task" ? TASK_IMPLICIT : [];
107385
107765
  const grammar = Object.entries(FAMILY_ACTIONS[name]).filter(([action]) => !implicit.some((entry) => entry.name === action)).map(([action, summary]) => ({
107386
107766
  name: action,
107387
- usage: name === "task" && action === "list" ? `workit task list [--status all] ${FAMILY_OPTIONS}` : name === "policy" && action !== "explain" ? `workit policy ${action} ${POLICY_JUDGE} ${FAMILY_OPTIONS}` : name === "worker" && action !== "assign" ? `workit worker ${action} [<workerId>] ${FAMILY_OPTIONS}` : `workit ${name} ${action} ${FAMILY_OPTIONS}`,
107767
+ usage: name === "task" && action === "list" ? `workit task list [--status all] ${FAMILY_OPTIONS}` : name === "finding" && action === "list" ? "workit finding list [--branch <b>] [--status open|resolved] [--json]" : name === "policy" && action !== "explain" ? `workit policy ${action} ${POLICY_JUDGE} ${FAMILY_OPTIONS}` : name === "worker" && action !== "assign" ? `workit worker ${action} [<workerId>] ${FAMILY_OPTIONS}` : `workit ${name} ${action} ${FAMILY_OPTIONS}`,
107388
107768
  summary
107389
107769
  }));
107390
107770
  return [...implicit, ...grammar];
@@ -107699,7 +108079,7 @@ var VERBS = [
107699
108079
  {
107700
108080
  name: "push",
107701
108081
  deferredFlags: ["force"],
107702
- usage: "workit git push [-u|--set-upstream] [--force-with-lease [--expect <sha>] [--overwrite-unintegrated]] [--release <main|develop|v-tag>…]",
108082
+ usage: "workit git push [-u|--set-upstream] [--force-with-lease [--expect <sha>] [--overwrite-unintegrated]] [--release <main|develop|v-tag>[,…]]",
107703
108083
  summary: "Push the current branch and verify the remote tip (never a blind --force); --release finishes a git-flow release"
107704
108084
  },
107705
108085
  {
@@ -107869,7 +108249,7 @@ var VERBS = [
107869
108249
  name: "list",
107870
108250
  aliases: ["show"],
107871
108251
  usage: "workit ledger list|show [--branch <b>] [--pr <n>] [--type <t>] [--last <n>]",
107872
- summary: "List ledger rows, newest last"
108252
+ summary: "List ledger rows, newest last (findings live in the task: workit finding list)"
107873
108253
  },
107874
108254
  {
107875
108255
  name: "check",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brainervirus/workit-cli",
3
- "version": "8.39.2",
3
+ "version": "8.39.4",
4
4
  "private": false,
5
5
  "description": "Workit CLI — setup wizard, doctor, and task control for agentic coding workflows",
6
6
  "keywords": [
@@ -38,13 +38,13 @@
38
38
  },
39
39
  "dependencies": {
40
40
  "@babel/parser": "7.29.9",
41
- "@brainervirus/workit-cursor": "^8.39.2",
41
+ "@brainervirus/workit-cursor": "^8.39.4",
42
42
  "@inkjs/ui": "2.0.0",
43
43
  "ink": "7.1.1",
44
44
  "react": "19.2.8"
45
45
  },
46
46
  "devDependencies": {
47
- "@brainervirus/workit-core": "^8.39.2"
47
+ "@brainervirus/workit-core": "^8.39.4"
48
48
  },
49
49
  "engines": {
50
50
  "node": ">=24"