@brainervirus/workit-cli 8.39.3 → 8.40.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.
Files changed (2) hide show
  1. package/dist/index.js +404 -32
  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.3",
9320
+ version: "8.40.0",
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.3",
9358
+ "@brainervirus/workit-cursor": "^8.40.0",
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.3"
9364
+ "@brainervirus/workit-core": "^8.40.0"
9365
9365
  },
9366
9366
  engines: {
9367
9367
  node: ">=24"
@@ -21305,7 +21305,10 @@ async function payloadValue(raw, deps) {
21305
21305
  }
21306
21306
  try {
21307
21307
  const value = JSON.parse(source);
21308
- 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
+ };
21309
21312
  } catch (error) {
21310
21313
  return {
21311
21314
  ok: false,
@@ -21500,7 +21503,11 @@ function laneOf(root) {
21500
21503
  } catch {
21501
21504
  verification = DEFAULT_VERIFICATION;
21502
21505
  }
21503
- 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
+ });
21504
21511
  return {
21505
21512
  lane: lane.lane,
21506
21513
  reasons: lane.reasons,
@@ -21574,7 +21581,69 @@ function printHuman(result, deps, handoff = false) {
21574
21581
  write(stream, `policy gap: ${gap.reason}`);
21575
21582
  }
21576
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
+ }
21577
21644
  async function runTaskCommand(argv, deps = {}) {
21645
+ if (argv[0] === "finding" && argv[1] === "list")
21646
+ return runFindingList(argv.slice(2), deps);
21578
21647
  const parsed = await parseTaskArgs(argv, deps);
21579
21648
  if (!parsed.ok) {
21580
21649
  if (parsed.json)
@@ -21587,7 +21656,9 @@ async function runTaskCommand(argv, deps = {}) {
21587
21656
  const store = new TaskStore(root);
21588
21657
  try {
21589
21658
  const now = new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
21590
- closeDeliveredTasks(store, now, { cached: parsed.parsed.action !== "list" });
21659
+ closeDeliveredTasks(store, now, {
21660
+ cached: parsed.parsed.action !== "list"
21661
+ });
21591
21662
  } catch {}
21592
21663
  const core = new WorkitCore(store, contextFor(root, parsed.parsed.actor !== undefined ? { ...deps, actor: parsed.parsed.actor } : deps));
21593
21664
  let result;
@@ -21678,7 +21749,7 @@ var TASK_FAMILIES, workspaceRootFor = (deps = {}) => {
21678
21749
  capabilities: deps.capabilities ?? [],
21679
21750
  constraints: deps.constraints ?? [],
21680
21751
  now: deps.now ?? (() => new Date().toISOString())
21681
- });
21752
+ }), FINDING_LIST_USAGE = "workit finding list [--branch <b>] [--status open|resolved] [--json]";
21682
21753
  var init_task = __esm(() => {
21683
21754
  init_check_config();
21684
21755
  init_autonomy();
@@ -21695,7 +21766,7 @@ var init_task = __esm(() => {
21695
21766
  task: ["start", "list", "inspect", "revise", "progress", "pause", "resume", "close"],
21696
21767
  policy: ["assess", "preview", "explain"],
21697
21768
  evidence: ["record"],
21698
- finding: ["record", "resolve"],
21769
+ finding: ["record", "resolve", "list"],
21699
21770
  decision: ["record", "revoke"],
21700
21771
  worker: ["assign", "report", "cancel"],
21701
21772
  state: ["export", "import"]
@@ -76963,6 +77034,7 @@ var init_uninstall2 = __esm(() => {
76963
77034
  // packages/workit-cli/src/verbs/grant.ts
76964
77035
  var exports_grant = {};
76965
77036
  __export(exports_grant, {
77037
+ agentMarker: () => agentMarker,
76966
77038
  run: () => run10,
76967
77039
  runGrant: () => runGrant
76968
77040
  });
@@ -77301,7 +77373,90 @@ function set2(argv, io) {
77301
77373
  `${name}: ${changes.map((change) => change.path.join(".")).join(", ")} updated in ${written(data.path, data.backup)}`
77302
77374
  ]);
77303
77375
  }
77304
- async function runWorkspace(argv, io) {
77376
+ async function rm(argv, io, deps) {
77377
+ const [name, extra] = argv;
77378
+ if (!name)
77379
+ return usage3(io, "rm needs <name>");
77380
+ if (extra)
77381
+ return usage3(io, `unexpected argument ${extra}`);
77382
+ const current = load(io);
77383
+ if (typeof current === "number")
77384
+ return current;
77385
+ const entries = entriesOf(current);
77386
+ const removed = entries.find((entry) => entry.name === name);
77387
+ if (!removed)
77388
+ return emit(io, fail2("not_found", `no workspace named "${name}" in ${current.path}`));
77389
+ const remaining = entries.filter((entry) => entry !== removed);
77390
+ if (remaining.length === 0)
77391
+ return emit(io, fail2("blocked", `"${name}" is the last workspace in ${current.path}: it holds your vcs, policies and grants, and without it every folder falls back to the defaults; workspace rm does not remove it`, {
77392
+ unblock: `add the workspace that should replace it first (workit workspace add <name> --glob <g> --from ${name}), or edit ${current.path} in your own editor`
77393
+ }));
77394
+ const before = deliveredCeilingOf(removed);
77395
+ const defaults = ceilingOf({});
77396
+ const nothing = {
77397
+ grants: Object.fromEntries(GRANT_KINDS.map((kind) => [kind, false])),
77398
+ endpoint: DEFAULT_ENDPOINT,
77399
+ verification: STRICTNESS[0]
77400
+ };
77401
+ const specificity = globSpecificity(removed.glob);
77402
+ const raised = [
77403
+ { taker: "defaults", kinds: raisedKinds(before, defaults), reason: "no entry matches" },
77404
+ ...remaining.map((entry) => ({
77405
+ taker: entry.name,
77406
+ kinds: raisedKinds(before, ceilingOf(entry)),
77407
+ reason: "may match a folder it covered"
77408
+ })),
77409
+ ...remaining.filter((entry) => globSpecificity(entry.glob) === specificity).map((entry) => ({
77410
+ taker: entry.name,
77411
+ kinds: raisedKinds(nothing, ceilingOf(entry)),
77412
+ reason: "equally specific: a folder both matched was ambiguous, with nothing granted"
77413
+ }))
77414
+ ].filter((raise) => raise.kinds.length > 0);
77415
+ if (raised.length > 0) {
77416
+ const kinds = [...new Set(raised.flatMap((raise) => raise.kinds))];
77417
+ const detail = raised.map((raise) => `${raise.taker} (${raise.reason}): ${raise.kinds.join(", ")}`).join("; ");
77418
+ const agent = agentMarker(io.env);
77419
+ if (!deps.interactive() || agent)
77420
+ return emit(io, fail2("blocked", `grant_raise_refused: removing "${name}" may raise ${kinds.join(", ")} for the folders it covered (${detail}); that needs the user at an interactive terminal${agent ? ` (this shell belongs to an agent: ${agent} is set)` : ""}; grants are never raised headless`, {
77421
+ unblock: `ask the user to run, in a terminal outside the agent host (not Claude Code's \`!\` prefix or an agent shell): workit workspace rm ${name}`
77422
+ }));
77423
+ const answer = await askOrCancel(io, () => deps.ask(`Removing workspace "${name}" may raise ${kinds.join(", ")} for the folders it covered (${detail}). An agent there may then do more without asking. Type the workspace name to confirm: `));
77424
+ if (answer === null)
77425
+ return cancelled(io);
77426
+ if (answer.trim() !== name)
77427
+ return emit(io, fail2("blocked", "workspace removal not confirmed; nothing was written"));
77428
+ }
77429
+ const result = save(current, remaining);
77430
+ if (!result.ok)
77431
+ return emit(io, fail2("invalid_input", result.error));
77432
+ const at = globRoot(removed.glob);
77433
+ let covers;
77434
+ try {
77435
+ covers = { at, workspace: resolveWorkspaceFromEntries(at, remaining)?.name ?? null };
77436
+ } catch (error) {
77437
+ covers = { at, workspace: null, error: error instanceof Error ? error.message : String(error) };
77438
+ }
77439
+ const coveringCeiling = ceilingOf(remaining.find((entry) => entry.name === covers.workspace));
77440
+ return emit(io, ok2({
77441
+ path: result.path,
77442
+ backup: result.backup,
77443
+ removed,
77444
+ covers: {
77445
+ ...covers,
77446
+ grants: coveringCeiling.grants,
77447
+ defaultEndpoint: coveringCeiling.endpoint,
77448
+ verification: coveringCeiling.verification
77449
+ },
77450
+ takers: overlappingEntries(removed.glob, remaining).map((entry) => entry.name),
77451
+ raises: raised
77452
+ }), (data) => [
77453
+ `${name} (${removed.glob}) removed from ${written(data.path, data.backup)}`,
77454
+ data.covers.error ? ` ${data.covers.at}: ${data.covers.error}` : ` ${data.covers.at} is now ${data.covers.workspace ? `workspace ${data.covers.workspace}` : "under the defaults (no workspace)"}: ${GRANT_KINDS.map((kind) => `${kind}=${String(data.covers.grants[kind])}`).join(" ")}; default endpoint ${data.covers.defaultEndpoint}; verification ${data.covers.verification}`,
77455
+ ...data.takers.length ? [` entries whose globs may cover its other folders: ${data.takers.join(", ")}`] : [],
77456
+ " workit workspace list --cwd <folder> shows which entry applies to a folder"
77457
+ ]);
77458
+ }
77459
+ async function runWorkspace(argv, io, deps = defaultDeps2) {
77305
77460
  const [sub, ...rest] = argv.filter((arg) => arg !== "--json" && arg !== "--");
77306
77461
  if (sub === undefined || sub === "list")
77307
77462
  return list2(rest, io);
@@ -77309,13 +77464,25 @@ async function runWorkspace(argv, io) {
77309
77464
  return add2(rest, io);
77310
77465
  if (sub === "set")
77311
77466
  return set2(rest, io);
77467
+ if (sub === "rm")
77468
+ return rm(rest, io, deps);
77312
77469
  return usage3(io, `unknown subcommand "${sub}"`);
77313
77470
  }
77314
- var USAGE5 = "workit workspace list | workspace add <name> --glob <g> [--from <parent>] | workspace set <name> <key>=<value>…", INHERITED_KEYS, GRANT_KEYS, SCOPE_KEYS, SETTABLE_KEYS, SEGMENT, UNSAFE_SEGMENTS, ceilingOf = (entry) => ({
77471
+ var USAGE5 = "workit workspace list | workspace add <name> --glob <g> [--from <parent>] | workspace set <name> <key>=<value>… | workspace rm <name>", INHERITED_KEYS, GRANT_KEYS, SCOPE_KEYS, SETTABLE_KEYS, SEGMENT, UNSAFE_SEGMENTS, ceilingOf = (entry) => ({
77315
77472
  grants: { ...DEFAULT_GRANTS, ...configuredGrants(entry).grants },
77316
77473
  endpoint: isDefaultEndpoint(entry?.defaultEndpoint) ? entry.defaultEndpoint : DEFAULT_ENDPOINT,
77317
77474
  verification: verificationOf(entry)
77318
- }), STRICTNESS, looser = (current, next) => STRICTNESS.indexOf(next) > STRICTNESS.indexOf(current), shadowedCeiling = (shadowed) => {
77475
+ }), deliveredCeilingOf = (entry) => {
77476
+ const ceiling = ceilingOf(entry);
77477
+ const account = entry.vcs?.account;
77478
+ if (Object.keys(configuredGrants(entry).grants).length === 0 || account)
77479
+ return ceiling;
77480
+ return {
77481
+ grants: Object.fromEntries(GRANT_KINDS.map((kind) => [kind, false])),
77482
+ endpoint: DEFAULT_ENDPOINT,
77483
+ verification: ceiling.verification
77484
+ };
77485
+ }, STRICTNESS, looser = (current, next) => STRICTNESS.indexOf(next) > STRICTNESS.indexOf(current), shadowedCeiling = (shadowed) => {
77319
77486
  if (shadowed.length === 0)
77320
77487
  return null;
77321
77488
  const ceilings = shadowed.map(ceilingOf);
@@ -77335,7 +77502,11 @@ var USAGE5 = "workit workspace list | workspace add <name> --glob <g> [--from <p
77335
77502
  ...Object.keys(autonomy).length ? { autonomy } : {},
77336
77503
  ...looser(shadowed.verification, DEFAULT_VERIFICATION) ? { verification: shadowed.verification } : {}
77337
77504
  };
77338
- }, usage3 = (io, message) => emit(io, fail2("invalid_input", `${message}
77505
+ }, defaultDeps2, raisedKinds = (before, after) => [
77506
+ ...GRANT_KINDS.filter((kind) => raises(kind, before.grants[kind], after.grants[kind])),
77507
+ ...raisesEndpoint(before.endpoint, after.endpoint) ? ["defaultEndpoint"] : [],
77508
+ ...looser(before.verification, after.verification) ? ["verification"] : []
77509
+ ], usage3 = (io, message) => emit(io, fail2("invalid_input", `${message}
77339
77510
  usage: ${USAGE5}`)), load = (io) => {
77340
77511
  const current = readWorkspacesResult();
77341
77512
  if (current.status === "malformed" || current.status === "invalid")
@@ -77384,6 +77555,8 @@ var init_workspace = __esm(() => {
77384
77555
  init_safe_write();
77385
77556
  init_workspaces();
77386
77557
  init_output();
77558
+ init_prompt();
77559
+ init_grant();
77387
77560
  INHERITED_KEYS = [
77388
77561
  "vcs",
77389
77562
  "issues",
@@ -77415,6 +77588,10 @@ var init_workspace = __esm(() => {
77415
77588
  SEGMENT = /^[A-Za-z0-9_-]+$/u;
77416
77589
  UNSAFE_SEGMENTS = new Set(["__proto__", "constructor", "prototype"]);
77417
77590
  STRICTNESS = ["independent", "self", "proportional"];
77591
+ defaultDeps2 = {
77592
+ interactive: () => process.stdin.isTTY === true && process.stdout.isTTY === true,
77593
+ ask: (question) => askLine(question)
77594
+ };
77418
77595
  });
77419
77596
 
77420
77597
  // packages/workit-core/src/core/heavy-rules.ts
@@ -86747,10 +86924,124 @@ var init_forge_common = __esm(() => {
86747
86924
  // packages/workit-cli/src/verbs/pr.ts
86748
86925
  var exports_pr = {};
86749
86926
  __export(exports_pr, {
86927
+ checkPrTitle: () => checkPrTitle,
86750
86928
  run: () => run15
86751
86929
  });
86752
- import { readFileSync as readFileSync27 } from "node:fs";
86930
+ import { existsSync as existsSync26, readFileSync as readFileSync27 } from "node:fs";
86753
86931
  import path66 from "node:path";
86932
+ function titleRules(cwd) {
86933
+ let root;
86934
+ try {
86935
+ root = checkRoot(cwd);
86936
+ } catch {
86937
+ return { rules: null, note: null };
86938
+ }
86939
+ let text = null;
86940
+ let name = "";
86941
+ for (const candidate of COMMITLINT_FILES) {
86942
+ const full = path66.join(root, candidate);
86943
+ if (!existsSync26(full))
86944
+ continue;
86945
+ text = readFileSync27(full, "utf8");
86946
+ name = candidate;
86947
+ break;
86948
+ }
86949
+ if (text === null) {
86950
+ try {
86951
+ const pkg = JSON.parse(readFileSync27(path66.join(root, "package.json"), "utf8"));
86952
+ if (pkg.commitlint === undefined)
86953
+ return { rules: null, note: null };
86954
+ text = JSON.stringify(pkg.commitlint);
86955
+ } catch {
86956
+ return { rules: null, note: null };
86957
+ }
86958
+ }
86959
+ if (/\.[cm]?[jt]s$/u.test(name))
86960
+ return UNCHECKED("JS/TS");
86961
+ let config;
86962
+ try {
86963
+ const parsed = JSON.parse(text);
86964
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
86965
+ return UNCHECKED("not a plain JSON object");
86966
+ config = parsed;
86967
+ } catch {
86968
+ return UNCHECKED(/\.ya?ml$|^\.commitlintrc$/u.test(name) ? "YAML" : "unreadable");
86969
+ }
86970
+ if (config.ignores !== undefined)
86971
+ return UNCHECKED("using custom ignores");
86972
+ const extend = config.extends === undefined ? [] : [config.extends].flat();
86973
+ const known = (entry) => typeof entry === "string" && /^(@commitlint\/)?config-conventional$/u.test(entry);
86974
+ if (!extend.every(known))
86975
+ return UNCHECKED("extending a shareable config");
86976
+ const rules = {
86977
+ types: null,
86978
+ maxHeader: null,
86979
+ format: 0,
86980
+ defaultIgnores: config.defaultIgnores !== false
86981
+ };
86982
+ if (extend.length) {
86983
+ rules.types = { level: 2, values: CONVENTIONAL_TYPES };
86984
+ rules.maxHeader = { level: 2, value: 100 };
86985
+ rules.format = 2;
86986
+ }
86987
+ const own = config.rules ?? {};
86988
+ const types = readRule(own, "type-enum");
86989
+ if (types === "off")
86990
+ rules.types = null;
86991
+ else if (types && Array.isArray(types.value))
86992
+ rules.types = { level: types.level, values: types.value.map(String) };
86993
+ const max = readRule(own, "header-max-length");
86994
+ if (max === "off")
86995
+ rules.maxHeader = null;
86996
+ else if (max && typeof max.value === "number")
86997
+ rules.maxHeader = { level: max.level, value: max.value };
86998
+ for (const rule of ["type-empty", "subject-empty"]) {
86999
+ const read = readRule(own, rule);
87000
+ if (read === "off")
87001
+ rules.format = 0;
87002
+ else if (read)
87003
+ rules.format = Math.max(rules.format, read.level);
87004
+ }
87005
+ return { rules, note: null };
87006
+ }
87007
+ function checkPrTitle(cwd, title) {
87008
+ const { rules, note } = titleRules(cwd);
87009
+ if (!rules)
87010
+ return { error: null, notes: note ? [note] : [] };
87011
+ const header = title.split(`
87012
+ `, 1)[0] ?? "";
87013
+ if (rules.defaultIgnores && DEFAULT_IGNORES.some((pattern) => pattern.test(title)))
87014
+ return { error: null, notes: [] };
87015
+ const problems = [];
87016
+ const parsed = /^(\w*)(?:\((.*)\))?!?: (.*)$/u.exec(header);
87017
+ if (rules.format && (!parsed || !parsed[1] || !parsed[3]?.trim()))
87018
+ problems.push({
87019
+ level: rules.format,
87020
+ 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`
87021
+ });
87022
+ else if (parsed && rules.types && !rules.types.values.includes(parsed[1]))
87023
+ problems.push({
87024
+ level: rules.types.level,
87025
+ message: `PR title type "${parsed[1]}" is not allowed (commitlint type-enum: ${rules.types.values.join(", ")})`
87026
+ });
87027
+ if (rules.maxHeader && header.length > rules.maxHeader.value)
87028
+ problems.push({
87029
+ level: rules.maxHeader.level,
87030
+ 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}`
87031
+ });
87032
+ const refused = problems.filter((problem) => problem.level >= 2);
87033
+ return {
87034
+ error: refused.length ? refused.map((problem) => problem.message).join("; ") : null,
87035
+ notes: problems.filter((problem) => problem.level < 2).map((p) => `warning: ${p.message}`)
87036
+ };
87037
+ }
87038
+ function refuseTitle(io, title, line) {
87039
+ const checked = checkPrTitle(io.cwd, title);
87040
+ for (const note of checked.notes)
87041
+ io.stderr(`${note}
87042
+ `);
87043
+ return checked.error ? usage4(io, checked.error, line) : null;
87044
+ }
86754
87045
  function readBodyFile(io, file) {
86755
87046
  try {
86756
87047
  return {
@@ -86920,6 +87211,9 @@ async function create4(argv, io) {
86920
87211
  }
86921
87212
  if (!base)
86922
87213
  return usage4(io, "no default target branch is configured; pass --base <b>", CREATE_USAGE);
87214
+ const given = flags.values.title ? refuseTitle(io, flags.values.title, CREATE_USAGE) : null;
87215
+ if (given !== null)
87216
+ return given;
86923
87217
  const connected = connect(io, branch);
86924
87218
  if (!connected.ok)
86925
87219
  return forgeFail(io, connected);
@@ -86931,6 +87225,11 @@ async function create4(argv, io) {
86931
87225
  title ||= filled.data.title;
86932
87226
  body ??= filled.data.body;
86933
87227
  }
87228
+ if (!flags.values.title) {
87229
+ const filled = refuseTitle(io, title, CREATE_USAGE);
87230
+ if (filled !== null)
87231
+ return filled;
87232
+ }
86934
87233
  const forge = connected.data.forge;
86935
87234
  const planned = planIssueLinks(io.cwd, {
86936
87235
  issues: flags.lists.issue ?? [],
@@ -87149,6 +87448,11 @@ async function edit(argv, io) {
87149
87448
  return usage4(io, read, EDIT_USAGE);
87150
87449
  body = read.body;
87151
87450
  }
87451
+ if (flags.values.title !== undefined) {
87452
+ const edited = refuseTitle(io, flags.values.title, EDIT_USAGE);
87453
+ if (edited !== null)
87454
+ return edited;
87455
+ }
87152
87456
  const connected = connect(io);
87153
87457
  if (!connected.ok)
87154
87458
  return forgeFail(io, connected);
@@ -87251,7 +87555,19 @@ async function run15(argv, io) {
87251
87555
  return reply(rest, io);
87252
87556
  return usage4(io, sub && !sub.startsWith("-") ? `unknown pr subcommand "${sub}"` : "missing pr subcommand", USAGE9);
87253
87557
  }
87254
- 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) => {
87558
+ var CONVENTIONAL_TYPES, COMMITLINT_FILES, DEFAULT_IGNORES, UNCHECKED = (what) => ({
87559
+ rules: null,
87560
+ note: `commitlint config is ${what}; title not pre-checked, CI checks it`
87561
+ }), readRule = (rules, name) => {
87562
+ const rule = rules[name];
87563
+ if (rule === undefined)
87564
+ return null;
87565
+ if (!Array.isArray(rule) || typeof rule[0] !== "number")
87566
+ return "off";
87567
+ if (rule[0] === 0 || rule[1] === "never")
87568
+ return "off";
87569
+ return { level: rule[0], value: rule[2] };
87570
+ }, 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) => {
87255
87571
  if (verdict.review === "self-reviewed")
87256
87572
  return `self-reviewed (lane ${verdict.lane}): the author's verdict ${verdict.verdictId ?? "?"}; no independent verdict accepted for that head`;
87257
87573
  if (!verdict.verdictId)
@@ -87275,8 +87591,48 @@ var init_pr = __esm(() => {
87275
87591
  init_lane();
87276
87592
  init_pr_lane();
87277
87593
  init_vcs_config();
87594
+ init_check_config();
87278
87595
  init_output();
87279
87596
  init_forge_common();
87597
+ CONVENTIONAL_TYPES = [
87598
+ "build",
87599
+ "chore",
87600
+ "ci",
87601
+ "docs",
87602
+ "feat",
87603
+ "fix",
87604
+ "perf",
87605
+ "refactor",
87606
+ "revert",
87607
+ "style",
87608
+ "test"
87609
+ ];
87610
+ COMMITLINT_FILES = [
87611
+ ".commitlintrc.json",
87612
+ ".commitlintrc",
87613
+ ".commitlintrc.yaml",
87614
+ ".commitlintrc.yml",
87615
+ ".commitlintrc.js",
87616
+ ".commitlintrc.cjs",
87617
+ ".commitlintrc.mjs",
87618
+ ".commitlintrc.ts",
87619
+ "commitlint.config.js",
87620
+ "commitlint.config.cjs",
87621
+ "commitlint.config.mjs",
87622
+ "commitlint.config.ts"
87623
+ ];
87624
+ DEFAULT_IGNORES = [
87625
+ /^((Merge pull request)|(Merge (.*?) into (.*?)|(Merge branch (.*?)))(?:\r?\n)*$)/m,
87626
+ /^(Merge tag (.*?))(?:\r?\n)*$/m,
87627
+ /^(R|r)evert (.*)/,
87628
+ /^(R|r)eapply (.*)/,
87629
+ /^(amend|fixup|squash)!/,
87630
+ /^(Merged (.*?)(in|into) (.*)|Merged PR (.*): (.*))/,
87631
+ /^Merge remote-tracking branch(\s*)(.*)/,
87632
+ /^Automatic merge(.*)/,
87633
+ /^Auto-merged (.*?) into (.*)/,
87634
+ /^(chore(\([^)]+\))?:\s*)?v?\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?\s*$/
87635
+ ];
87280
87636
  NEED_TEXT = {
87281
87637
  none: "no verdict",
87282
87638
  self: "the author's own --self verdict (or an independent one)",
@@ -87733,10 +88089,10 @@ async function push(argv, io) {
87733
88089
  });
87734
88090
  if (typeof flags === "string")
87735
88091
  return usage4(io, flags, PUSH_USAGE);
87736
- const release = flags.lists.release;
88092
+ const release = flags.lists.release?.flatMap((value) => value.split(",").map((name) => name.trim()).filter(Boolean));
87737
88093
  if (release) {
87738
88094
  if (flags.booleans.size || flags.values.expect !== undefined || flags.positionals.length)
87739
- return usage4(io, "--release takes only ref names (repeat it: --release main --release v1.2.0)", PUSH_USAGE);
88095
+ return usage4(io, "--release takes only ref names (a comma list or a repeated flag: --release main,v1.2.0)", PUSH_USAGE);
87740
88096
  return releasePush(release, io);
87741
88097
  }
87742
88098
  if (flags.positionals.length)
@@ -87838,7 +88194,7 @@ async function run17(argv, io) {
87838
88194
  return trailers(rest, io);
87839
88195
  return usage4(io, sub && !sub.startsWith("-") ? `unknown git subcommand "${sub}"` : "missing git subcommand", USAGE10);
87840
88196
  }
87841
- 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;
88197
+ 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;
87842
88198
  var init_git2 = __esm(() => {
87843
88199
  init_autonomy();
87844
88200
  init_branch();
@@ -90128,7 +90484,13 @@ async function run22(argv, io) {
90128
90484
  type: values.type,
90129
90485
  last: last ?? 50
90130
90486
  });
90131
- return emit(io, ok2({ path: ledger.value.path, skipped: ledger.value.skipped, rows: listed }), (data) => data.rows.length ? data.rows.map((row) => {
90487
+ 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;
90488
+ return emit(io, ok2({
90489
+ path: ledger.value.path,
90490
+ skipped: ledger.value.skipped,
90491
+ rows: listed,
90492
+ ...note ? { note } : {}
90493
+ }), (data) => note ? note : data.rows.length ? data.rows.map((row) => {
90132
90494
  const line = summarizeRow(row);
90133
90495
  return `#${line.seq} ${line.at} ${line.type}${line.branch ? ` [${line.branch}]` : ""}${line.labels.length ? ` (${line.labels.join(", ")})` : ""} ${line.summary}`;
90134
90496
  }) : "ledger is empty");
@@ -106273,7 +106635,7 @@ var init_rules = __esm(() => {
106273
106635
 
106274
106636
  // packages/workit-cli/src/test-audit/audit.ts
106275
106637
  import { spawnSync as spawnSync29 } from "node:child_process";
106276
- import { existsSync as existsSync26, readdirSync as readdirSync14, readFileSync as readFileSync29, statSync as statSync10 } from "node:fs";
106638
+ import { existsSync as existsSync27, readdirSync as readdirSync14, readFileSync as readFileSync29, statSync as statSync10 } from "node:fs";
106277
106639
  import path71 from "node:path";
106278
106640
  function git7(cwd, args) {
106279
106641
  const run = spawnSync29("git", ["-c", "core.quotepath=off", ...args], {
@@ -106300,7 +106662,7 @@ function expandPaths(cwd, inputs) {
106300
106662
  const missing = [];
106301
106663
  for (const input of inputs.length > 0 ? inputs : ["."]) {
106302
106664
  const full = path71.resolve(cwd, input);
106303
- if (!existsSync26(full))
106665
+ if (!existsSync27(full))
106304
106666
  missing.push(input);
106305
106667
  else if (statSync10(full).isDirectory())
106306
106668
  walkTests(full, files);
@@ -106416,7 +106778,7 @@ var init_audit = __esm(() => {
106416
106778
  // packages/workit-cli/src/test-audit/mutate.ts
106417
106779
  import { spawn as spawn2 } from "node:child_process";
106418
106780
  import {
106419
- existsSync as existsSync27,
106781
+ existsSync as existsSync28,
106420
106782
  lstatSync as lstatSync8,
106421
106783
  mkdirSync as mkdirSync12,
106422
106784
  mkdtempSync as mkdtempSync4,
@@ -106645,7 +107007,7 @@ function copyWorkingTree(repo) {
106645
107007
  }
106646
107008
  for (const parent of dirs) {
106647
107009
  const modules = path72.join(repo, parent, "node_modules");
106648
- if (existsSync27(modules) && !existsSync27(path72.join(tree, parent, "node_modules")))
107010
+ if (existsSync28(modules) && !existsSync28(path72.join(tree, parent, "node_modules")))
106649
107011
  linkNodeModules(modules, path72.join(tree, parent, "node_modules"), repo, tree);
106650
107012
  }
106651
107013
  return { dir, tree };
@@ -106695,7 +107057,7 @@ async function runMutation(options) {
106695
107057
  const relative = toPosix(path72.relative(repo, absolute));
106696
107058
  if (!CODE_FILE.test(relative) || TEST_FILE.test(relative) || relative.endsWith(".d.ts"))
106697
107059
  continue;
106698
- if (NOT_SOURCE.test(relative) || !existsSync27(absolute))
107060
+ if (NOT_SOURCE.test(relative) || !existsSync28(absolute))
106699
107061
  continue;
106700
107062
  try {
106701
107063
  candidates.push(...mutantsFor(readFileSync30(absolute, "utf8"), relative, lines));
@@ -106885,7 +107247,7 @@ var exports_test_audit = {};
106885
107247
  __export(exports_test_audit, {
106886
107248
  run: () => run23
106887
107249
  });
106888
- import { existsSync as existsSync28 } from "node:fs";
107250
+ import { existsSync as existsSync29 } from "node:fs";
106889
107251
  import path73 from "node:path";
106890
107252
  function parse8(argv) {
106891
107253
  const options = {
@@ -107009,7 +107371,7 @@ usage: ${USAGE16}`));
107009
107371
  }
107010
107372
  let files;
107011
107373
  if (options.diff && changed)
107012
- files = [...changed.keys()].filter((file) => TEST_FILE.test(toPosix(file)) && existsSync28(file)).toSorted();
107374
+ files = [...changed.keys()].filter((file) => TEST_FILE.test(toPosix(file)) && existsSync29(file)).toSorted();
107013
107375
  else {
107014
107376
  const expanded = expandPaths(io.cwd, options.paths);
107015
107377
  if (expanded.missing.length > 0)
@@ -107457,7 +107819,11 @@ var FAMILY_ACTIONS = {
107457
107819
  explain: "Explain the policy in force"
107458
107820
  },
107459
107821
  evidence: { record: "Record evidence against a task's requirements" },
107460
- finding: { record: "Record a finding", resolve: "Resolve a finding" },
107822
+ finding: {
107823
+ record: "Record a finding",
107824
+ resolve: "Resolve a finding",
107825
+ list: "List findings with their ids [--branch <b>] [--status open|resolved]"
107826
+ },
107461
107827
  decision: {
107462
107828
  record: "Record a task decision",
107463
107829
  revoke: "Revoke a task decision"
@@ -107504,7 +107870,7 @@ var familySubcommands = (name) => {
107504
107870
  const implicit = name === "task" ? TASK_IMPLICIT : [];
107505
107871
  const grammar = Object.entries(FAMILY_ACTIONS[name]).filter(([action]) => !implicit.some((entry) => entry.name === action)).map(([action, summary]) => ({
107506
107872
  name: action,
107507
- 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}`,
107873
+ 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}`,
107508
107874
  summary
107509
107875
  }));
107510
107876
  return [...implicit, ...grammar];
@@ -107622,7 +107988,7 @@ var VERBS = [
107622
107988
  withSubcommands({
107623
107989
  name: "workspace",
107624
107990
  group: "setup",
107625
- summary: "Edit workspace entries without the wizard; never raises a grant",
107991
+ summary: "Edit workspace entries without the wizard; a raise (rm included) needs the user at a terminal",
107626
107992
  subcommands: [
107627
107993
  {
107628
107994
  name: "list",
@@ -107638,15 +108004,21 @@ var VERBS = [
107638
108004
  name: "set",
107639
108005
  usage: "workit workspace set <name> <key>=<value>…",
107640
108006
  summary: "Change non-grant keys (dotted paths, JSON values, null removes); grants stay with workit grant set"
108007
+ },
108008
+ {
108009
+ name: "rm",
108010
+ usage: "workit workspace rm <name>",
108011
+ summary: "Remove an entry; if another entry or the defaults could grant its folders more, the user confirms at their own terminal"
107641
108012
  }
107642
108013
  ],
107643
108014
  examples: [
107644
108015
  "workit workspace list",
107645
108016
  "workit workspace add kit --glob '/home/me/work/libs/kit/**' --from work",
107646
- "workit workspace set kit branchPolicy.integration=merge"
108017
+ "workit workspace set kit branchPolicy.integration=merge",
108018
+ "workit workspace rm kit"
107647
108019
  ],
107648
108020
  notes: [
107649
- "A new entry starts with no grants of its own. It starts at the strictest level of every entry and the defaults, so its folders never get more than they had. Remove an entry in your own editor. Raising a grant, defaultEndpoint or verification needs the user: workit grant set <name> <kind>=<value> in their own terminal.",
108021
+ "A new entry starts with no grants of its own. It starts at the strictest level of every entry and the defaults, so its folders never get more than they had. rm counts every other entry and the defaults as a possible taker of the removed folders: when any could grant more, it is refused in an agent or headless shell and the user types the name to confirm at their own terminal; it never removes the last entry. Raising a grant, defaultEndpoint or verification needs the user: workit grant set <name> <kind>=<value> in their own terminal.",
107650
108022
  "Every write keeps the previous file as workspaces.json.bak. `workit doctor` lists overlapping globs and which entry wins."
107651
108023
  ],
107652
108024
  load: () => Promise.resolve().then(() => (init_workspace(), exports_workspace))
@@ -107819,7 +108191,7 @@ var VERBS = [
107819
108191
  {
107820
108192
  name: "push",
107821
108193
  deferredFlags: ["force"],
107822
- usage: "workit git push [-u|--set-upstream] [--force-with-lease [--expect <sha>] [--overwrite-unintegrated]] [--release <main|develop|v-tag>…]",
108194
+ usage: "workit git push [-u|--set-upstream] [--force-with-lease [--expect <sha>] [--overwrite-unintegrated]] [--release <main|develop|v-tag>[,…]]",
107823
108195
  summary: "Push the current branch and verify the remote tip (never a blind --force); --release finishes a git-flow release"
107824
108196
  },
107825
108197
  {
@@ -107989,7 +108361,7 @@ var VERBS = [
107989
108361
  name: "list",
107990
108362
  aliases: ["show"],
107991
108363
  usage: "workit ledger list|show [--branch <b>] [--pr <n>] [--type <t>] [--last <n>]",
107992
- summary: "List ledger rows, newest last"
108364
+ summary: "List ledger rows, newest last (findings live in the task: workit finding list)"
107993
108365
  },
107994
108366
  {
107995
108367
  name: "check",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brainervirus/workit-cli",
3
- "version": "8.39.3",
3
+ "version": "8.40.0",
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.3",
41
+ "@brainervirus/workit-cursor": "^8.40.0",
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.3"
47
+ "@brainervirus/workit-core": "^8.40.0"
48
48
  },
49
49
  "engines": {
50
50
  "node": ">=24"