@staff0rd/assist 0.224.1 → 0.226.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/README.md CHANGED
@@ -111,8 +111,10 @@ The first backlog command in a repository that still has a local `.assist/backlo
111
111
  - `assist backlog add` - Add a new backlog item interactively (prompts for type: story/bug)
112
112
  - `assist backlog add --name <n> --type <t> --desc <d> --ac <criterion...>` - Add a backlog item from CLI options (used by `/draft`)
113
113
  - `assist backlog add-phase <id> <name> --task <t...> [--manual-check <c...>] [--position <pos>]` - Add a phase (appends by default; `--position` inserts at a 1-indexed position)
114
- - `assist backlog update <id> [--name <n>] [--desc <d>] [--type <t>] [--ac <criterion...>]` - Update fields on a backlog item
114
+ - `assist backlog update-field <id> [--name <n>] [--desc <d>] [--type <t>] [--ac <criterion...>]` - Update fields on a backlog item
115
+ - `assist backlog update-field <id> [--add-ac <text>] [--edit-ac <n> <text>] [--remove-ac <n>]` - Granular acceptance-criteria edits using 1-based indices matching `backlog show`: `--add-ac` appends (repeatable), `--edit-ac` replaces criterion n in place, `--remove-ac` deletes criterion n and renumbers the rest (cannot be combined with the whole-list `--ac`)
115
116
  - `assist backlog update-phase <id> <phase> [--name <n>] [--task <t...>] [--manual-check <c...>]` - Modify a plan phase (name, tasks, or manual checks)
117
+ - `assist backlog update-phase <id> <phase> [--add-task <text>] [--edit-task <n> <text>] [--remove-task <n>] [--add-check <text>] [--edit-check <n> <text>] [--remove-check <n>]` - Granular task and manual-check edits using 1-based indices matching `backlog show`: `--add-*` appends (repeatable), `--edit-*` replaces entry n in place, `--remove-*` deletes entry n and renumbers the rest (task ops cannot be combined with `--task`; check ops cannot be combined with `--manual-check`)
116
118
  - `assist backlog remove-phase <id> <phase>` - Remove a plan phase from a backlog item
117
119
  - `assist backlog next` - Pick and run the next backlog item, or open `/draft` if none remain
118
120
  - `assist backlog start <id>` - Set a backlog item to in-progress
@@ -35,10 +35,10 @@ Based on the user's input, apply changes using the appropriate commands. Always
35
35
 
36
36
  **To update item fields:**
37
37
  ```
38
- assist backlog update <id> --name "New name"
39
- assist backlog update <id> --desc "New description"
40
- assist backlog update <id> --type story
41
- assist backlog update <id> --ac "criterion 1" --ac "criterion 2"
38
+ assist backlog update-field <id> --name "New name"
39
+ assist backlog update-field <id> --desc "New description"
40
+ assist backlog update-field <id> --type story
41
+ assist backlog update-field <id> --ac "criterion 1" --ac "criterion 2"
42
42
  ```
43
43
 
44
44
  Note: `--ac` replaces all acceptance criteria, so include the full list (both existing and new).
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ import { Command } from "commander";
6
6
  // package.json
7
7
  var package_default = {
8
8
  name: "@staff0rd/assist",
9
- version: "0.224.1",
9
+ version: "0.226.0",
10
10
  type: "module",
11
11
  main: "dist/index.js",
12
12
  bin: {
@@ -5597,6 +5597,78 @@ async function removePhase(id, phase) {
5597
5597
  // src/commands/backlog/update/index.ts
5598
5598
  import chalk67 from "chalk";
5599
5599
 
5600
+ // src/commands/backlog/update/parseListIndex.ts
5601
+ function parseListIndex(raw, length, label2) {
5602
+ if (!/^\d+$/.test(raw)) {
5603
+ return { ok: false, error: `${label2} must be a positive integer.` };
5604
+ }
5605
+ const index = Number.parseInt(raw, 10);
5606
+ if (index < 1 || index > length) {
5607
+ return {
5608
+ ok: false,
5609
+ error: `${label2} ${index} is out of range (1-${length}).`
5610
+ };
5611
+ }
5612
+ return { ok: true, index };
5613
+ }
5614
+
5615
+ // src/commands/backlog/update/applyListMutations.ts
5616
+ function hasListMutations(options2) {
5617
+ return Boolean(
5618
+ options2.add && options2.add.length > 0 || options2.edit || options2.remove
5619
+ );
5620
+ }
5621
+ function applyListMutations(current, options2, flags) {
5622
+ let items = [...current];
5623
+ if (options2.edit) {
5624
+ const [rawIndex, ...textParts] = options2.edit;
5625
+ if (rawIndex === void 0 || textParts.length === 0) {
5626
+ return {
5627
+ ok: false,
5628
+ error: `${flags.edit} requires an index and replacement text.`
5629
+ };
5630
+ }
5631
+ const parsed = parseListIndex(
5632
+ rawIndex,
5633
+ items.length,
5634
+ `${flags.edit} index`
5635
+ );
5636
+ if (!parsed.ok) return parsed;
5637
+ items[parsed.index - 1] = textParts.join(" ");
5638
+ }
5639
+ if (options2.remove) {
5640
+ const parsed = parseListIndex(
5641
+ options2.remove,
5642
+ items.length,
5643
+ `${flags.remove} index`
5644
+ );
5645
+ if (!parsed.ok) return parsed;
5646
+ items = items.filter((_, i) => i !== parsed.index - 1);
5647
+ }
5648
+ if (options2.add) {
5649
+ items.push(...options2.add);
5650
+ }
5651
+ return { ok: true, items };
5652
+ }
5653
+
5654
+ // src/commands/backlog/update/applyAcMutations.ts
5655
+ function hasAcMutations(options2) {
5656
+ return hasListMutations({
5657
+ add: options2.addAc,
5658
+ edit: options2.editAc,
5659
+ remove: options2.removeAc
5660
+ });
5661
+ }
5662
+ function applyAcMutations(current, options2) {
5663
+ const result = applyListMutations(
5664
+ current,
5665
+ { add: options2.addAc, edit: options2.editAc, remove: options2.removeAc },
5666
+ { add: "--add-ac", edit: "--edit-ac", remove: "--remove-ac" }
5667
+ );
5668
+ if (!result.ok) return result;
5669
+ return { ok: true, criteria: result.items };
5670
+ }
5671
+
5600
5672
  // src/commands/backlog/update/buildUpdateSql.ts
5601
5673
  import chalk66 from "chalk";
5602
5674
  function buildUpdateSql(options2) {
@@ -5641,7 +5713,24 @@ function buildUpdateSql(options2) {
5641
5713
  async function update(id, options2) {
5642
5714
  const result = await loadAndFindItem(id);
5643
5715
  if (!result) return;
5644
- const built = buildUpdateSql(options2);
5716
+ let ac = options2.ac;
5717
+ if (hasAcMutations(options2)) {
5718
+ if (options2.ac) {
5719
+ console.log(
5720
+ chalk67.red("Cannot combine --ac with --add-ac/--edit-ac/--remove-ac.")
5721
+ );
5722
+ process.exitCode = 1;
5723
+ return;
5724
+ }
5725
+ const mutation = applyAcMutations(result.item.acceptanceCriteria, options2);
5726
+ if (!mutation.ok) {
5727
+ console.log(chalk67.red(mutation.error));
5728
+ process.exitCode = 1;
5729
+ return;
5730
+ }
5731
+ ac = mutation.criteria;
5732
+ }
5733
+ const built = buildUpdateSql({ ...options2, ac });
5645
5734
  if (!built) return;
5646
5735
  const db = await getBacklogDb();
5647
5736
  const itemId = result.item.id;
@@ -5685,17 +5774,81 @@ async function applyPhaseUpdate(db, itemId, phaseIdx, fields) {
5685
5774
  });
5686
5775
  }
5687
5776
 
5777
+ // src/commands/backlog/update/resolveListUpdate.ts
5778
+ function resolveListUpdate(whole, current, mutations, wholeFlag, flags) {
5779
+ if (!hasListMutations(mutations)) return { ok: true, items: whole };
5780
+ if (whole) {
5781
+ return {
5782
+ ok: false,
5783
+ error: `Cannot combine ${wholeFlag} with ${flags.add}/${flags.edit}/${flags.remove}.`
5784
+ };
5785
+ }
5786
+ return applyListMutations(current, mutations, flags);
5787
+ }
5788
+
5789
+ // src/commands/backlog/resolvePhaseFields.ts
5790
+ var TASK_FLAGS = {
5791
+ add: "--add-task",
5792
+ edit: "--edit-task",
5793
+ remove: "--remove-task"
5794
+ };
5795
+ var CHECK_FLAGS = {
5796
+ add: "--add-check",
5797
+ edit: "--edit-check",
5798
+ remove: "--remove-check"
5799
+ };
5800
+ function resolveTasks(o, current) {
5801
+ return resolveListUpdate(
5802
+ o.task,
5803
+ (current?.tasks ?? []).map((t) => t.task),
5804
+ { add: o.addTask, edit: o.editTask, remove: o.removeTask },
5805
+ "--task",
5806
+ TASK_FLAGS
5807
+ );
5808
+ }
5809
+ function resolveChecks(o, current) {
5810
+ return resolveListUpdate(
5811
+ o.manualCheck,
5812
+ current?.manualChecks ?? [],
5813
+ { add: o.addCheck, edit: o.editCheck, remove: o.removeCheck },
5814
+ "--manual-check",
5815
+ CHECK_FLAGS
5816
+ );
5817
+ }
5818
+ function buildFields(name, tasks, checks) {
5819
+ const fields = {
5820
+ name,
5821
+ task: tasks.items,
5822
+ manualCheck: checks.items
5823
+ };
5824
+ if (!fields.name && !fields.task && !fields.manualCheck) {
5825
+ return {
5826
+ ok: false,
5827
+ error: "Nothing to update. Provide at least one flag."
5828
+ };
5829
+ }
5830
+ return { ok: true, fields };
5831
+ }
5832
+ function resolvePhaseFields(options2, current) {
5833
+ const tasks = resolveTasks(options2, current);
5834
+ if (!tasks.ok) return tasks;
5835
+ const checks = resolveChecks(options2, current);
5836
+ if (!checks.ok) return checks;
5837
+ return buildFields(options2.name, tasks, checks);
5838
+ }
5839
+
5688
5840
  // src/commands/backlog/updatePhase.ts
5689
5841
  async function updatePhase(id, phase, options2) {
5690
- const { name, task, manualCheck } = options2;
5691
- if (!name && !task && !manualCheck) {
5692
- console.log(chalk68.red("Nothing to update. Provide at least one flag."));
5842
+ const found = await findPhase(id, phase);
5843
+ if (!found) return;
5844
+ const { result, db, itemId, phaseIdx } = found;
5845
+ const resolved = resolvePhaseFields(options2, result.item.plan?.[phaseIdx]);
5846
+ if (!resolved.ok) {
5847
+ console.log(chalk68.red(resolved.error));
5693
5848
  process.exitCode = 1;
5694
5849
  return;
5695
5850
  }
5696
- const found = await findPhase(id, phase);
5697
- if (!found) return;
5698
- const { db, itemId, phaseIdx } = found;
5851
+ const { name, task, manualCheck } = resolved.fields;
5699
5852
  await applyPhaseUpdate(db, itemId, phaseIdx, { name, task, manualCheck });
5700
5853
  const fields = [
5701
5854
  name && "name",
@@ -5710,9 +5863,28 @@ async function updatePhase(id, phase, options2) {
5710
5863
  }
5711
5864
 
5712
5865
  // src/commands/backlog/registerUpdateCommands.ts
5866
+ function collect(value, previous) {
5867
+ return [...previous, value];
5868
+ }
5713
5869
  function registerUpdateCommands(cmd) {
5714
- cmd.command("update <id>").description("Update fields on a backlog item").option("--name <name>", "New item name").option("--desc <description>", "New description").option("--type <type>", "New type (story or bug)").option("--ac <criterion...>", "Replace acceptance criteria (repeatable)").action(update);
5715
- cmd.command("update-phase <id> <phase>").description("Modify a plan phase on a backlog item").option("--name <name>", "New phase name").option("--task <task...>", "Replace tasks (repeatable)").option("--manual-check <check...>", "Replace manual checks (repeatable)").action(updatePhase);
5870
+ cmd.command("update-field <id>").description("Update fields on a backlog item").option("--name <name>", "New item name").option("--desc <description>", "New description").option("--type <type>", "New type (story or bug)").option("--ac <criterion...>", "Replace acceptance criteria (repeatable)").option(
5871
+ "--add-ac <text>",
5872
+ "Append one acceptance criterion (repeatable)",
5873
+ collect,
5874
+ []
5875
+ ).option(
5876
+ "--edit-ac <n> <text...>",
5877
+ "Replace acceptance criterion n (1-based) in place"
5878
+ ).option("--remove-ac <n>", "Remove acceptance criterion n (1-based)").action(update);
5879
+ cmd.command("update-phase <id> <phase>").description("Modify a plan phase on a backlog item").option("--name <name>", "New phase name").option("--task <task...>", "Replace tasks (repeatable)").option("--manual-check <check...>", "Replace manual checks (repeatable)").option("--add-task <text>", "Append one task (repeatable)", collect, []).option("--edit-task <n> <text...>", "Replace task n (1-based) in place").option("--remove-task <n>", "Remove task n (1-based)").option(
5880
+ "--add-check <text>",
5881
+ "Append one manual check (repeatable)",
5882
+ collect,
5883
+ []
5884
+ ).option(
5885
+ "--edit-check <n> <text...>",
5886
+ "Replace manual check n (1-based) in place"
5887
+ ).option("--remove-check <n>", "Remove manual check n (1-based)").action(updatePhase);
5716
5888
  cmd.command("remove-phase <id> <phase>").description("Remove a plan phase from a backlog item").action(removePhase);
5717
5889
  }
5718
5890
 
@@ -6194,6 +6366,30 @@ function matchesConfigDeny(command) {
6194
6366
  }
6195
6367
 
6196
6368
  // src/commands/cliHook/resolvePermission.ts
6369
+ var SUBCOMMAND_READS = [
6370
+ {
6371
+ prefix: "assist complexity",
6372
+ subcommands: ["maintainability", "cyclomatic", "halstead"]
6373
+ }
6374
+ ];
6375
+ function matchesSubcommandRead(part) {
6376
+ return SUBCOMMAND_READS.find(
6377
+ (rule) => part === rule.prefix || part.startsWith(`${rule.prefix} `)
6378
+ );
6379
+ }
6380
+ function findSubcommandAdvice(parts) {
6381
+ if (parts.length <= 1) return void 0;
6382
+ for (const part of parts) {
6383
+ const rule = matchesSubcommandRead(part);
6384
+ if (rule) {
6385
+ return {
6386
+ permissionDecision: "deny",
6387
+ permissionDecisionReason: `Do not pipe or chain '${rule.prefix}'. Run a focused sub-command directly for targeted output: ${rule.subcommands.map((s) => `${rule.prefix} ${s} <file>`).join(", ")}.`
6388
+ };
6389
+ }
6390
+ }
6391
+ return void 0;
6392
+ }
6197
6393
  function findDeny(toolName, parts) {
6198
6394
  for (const part of parts) {
6199
6395
  const configDeny = matchesConfigDeny(part);
@@ -6204,6 +6400,8 @@ function findDeny(toolName, parts) {
6204
6400
  };
6205
6401
  }
6206
6402
  }
6403
+ const subcommandAdvice = findSubcommandAdvice(parts);
6404
+ if (subcommandAdvice) return subcommandAdvice;
6207
6405
  for (const part of parts) {
6208
6406
  const denied = matchesDeny(toolName, part);
6209
6407
  if (denied) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@staff0rd/assist",
3
- "version": "0.224.1",
3
+ "version": "0.226.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "bin": {