dsh-checklists 0.3.3 → 0.4.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
@@ -28,6 +28,7 @@ If the agent forgets to mark something, you say so in chat and it fixes it.
28
28
  | `checklist_edit` | Changes an item's text (not its state) |
29
29
  | `checklist_list` | Full tree with ids and states, for when the digest is truncated |
30
30
  | `checklist_delete` | Deletes a list (by id or exact title) |
31
+ | `checklist_remove_item` | Removes one item and everything nested under it |
31
32
  | `checklist_rename` | Renames a list; titles stay unique within the session |
32
33
  | `checklist_template_save` | Saves a list as a reusable template for future sessions |
33
34
 
@@ -49,7 +50,7 @@ From a local build or tarball:
49
50
  npm install
50
51
  npm run build
51
52
  npm pack
52
- dsh plugin --profile dev add ./dsh-checklists-0.3.2.tgz
53
+ dsh plugin --profile dev add ./dsh-checklists-0.4.0.tgz
53
54
  dsh --profile dev --dump-config # the tool-checklist row should appear
54
55
  ```
55
56
 
package/lib/model.js CHANGED
@@ -147,6 +147,27 @@ export function findItemById(lists, id) {
147
147
  }
148
148
  return undefined;
149
149
  }
150
+ /**
151
+ * Remove one item (and its whole subtree) by exact id.
152
+ *
153
+ * Mutates the array in place, so callers must pass a document they own — the
154
+ * store's `update` already hands out a clone.
155
+ *
156
+ * @param items - sibling list to search, at any depth.
157
+ * @param id - exact item id.
158
+ * @returns the removed node, or `undefined` when no item has that id.
159
+ */
160
+ export function removeItemById(items, id) {
161
+ const index = items.findIndex((entry) => entry.id === id);
162
+ if (index >= 0)
163
+ return items.splice(index, 1)[0];
164
+ for (const item of items) {
165
+ const removed = removeItemById(item.children, id);
166
+ if (removed)
167
+ return removed;
168
+ }
169
+ return undefined;
170
+ }
150
171
  /**
151
172
  * Validate a checklist rename before applying it.
152
173
  *
package/lib/tools.js CHANGED
@@ -5,7 +5,7 @@
5
5
  * the single source of truth and the sidebar (phase 2) is a read-only mirror.
6
6
  */
7
7
  import { defineTool } from '@deepseek-ai/dsh-tools';
8
- import { buildItems, checkRename, countItems, findList, flatten, ITEM_STATES, isItemState, mintId, nextItem, resolveRef, toLines, } from './model.js';
8
+ import { buildItems, checkRename, countItems, findList, flatten, ITEM_STATES, isItemState, mintId, nextItem, removeItemById, resolveRef, toLines, } from './model.js';
9
9
  /** Ships with the plugin so a first run has something to instantiate. */
10
10
  const DEFAULT_TEMPLATES = {
11
11
  release: [
@@ -542,6 +542,78 @@ export function registerChecklistTools(ctx, store, config = {}) {
542
542
  });
543
543
  },
544
544
  })));
545
+ ctx.effect(() => ctx.tools.register(defineTool({
546
+ name: 'checklist_remove_item',
547
+ description: 'Remove one item — and everything nested under it — from a checklist in the CURRENT session. '
548
+ + 'Match it by id or exact text. Use it to drop work that no longer applies; '
549
+ + 'to change the item instead, use checklist_edit.',
550
+ parameters: {
551
+ listId: {
552
+ type: 'string',
553
+ required: true,
554
+ description: 'Checklist id, or its exact title.',
555
+ },
556
+ item: {
557
+ type: 'string',
558
+ required: true,
559
+ description: 'Item id or exact item text.',
560
+ },
561
+ },
562
+ output: {
563
+ schema: {
564
+ type: 'object',
565
+ additionalProperties: false,
566
+ properties: {
567
+ listId: { type: 'string', required: true },
568
+ itemId: { type: 'string', required: true },
569
+ text: { type: 'string', required: true },
570
+ removed: { type: 'integer', required: true },
571
+ },
572
+ },
573
+ render: (_args, value) => {
574
+ const result = value;
575
+ const extra = result.removed > 1 ? ` (+${result.removed - 1} nested)` : '';
576
+ return [{ type: 'text', text: `Removed "${result.text}"${extra}.` }];
577
+ },
578
+ },
579
+ timeoutMs: 10_000,
580
+ async execute(args, exec) {
581
+ const input = args;
582
+ const sessionId = sessionOf(exec);
583
+ const listRef = text(input.listId).trim();
584
+ const itemRef = text(input.item).trim();
585
+ if (listRef.length === 0)
586
+ throw new Error('checklist_remove_item: listId must not be empty');
587
+ if (itemRef.length === 0)
588
+ throw new Error('checklist_remove_item: item must not be empty');
589
+ return store.update(sessionId, (doc) => {
590
+ const list = findList(doc.lists, listRef);
591
+ if (!list) {
592
+ const available = doc.lists.map((entry) => `${entry.id} (${entry.title})`).join(', ');
593
+ throw new Error(`checklist_remove_item: no checklist "${listRef}" in this session. `
594
+ + `Available: ${available.length > 0 ? available : 'none'}`);
595
+ }
596
+ const found = resolveRef([list], itemRef);
597
+ if (found.kind === 'ambiguous') {
598
+ const options = found.candidates.map((entry) => `${entry.item.id} (${entry.item.text})`).join(', ');
599
+ throw new Error(`checklist_remove_item: "${itemRef}" is ambiguous; use an id: ${options}`);
600
+ }
601
+ if (found.kind === 'miss') {
602
+ throw new Error(`checklist_remove_item: no item "${itemRef}" in "${list.title}". Call checklist_list for ids.`);
603
+ }
604
+ const removed = removeItemById(list.items, found.hit.item.id);
605
+ if (!removed) {
606
+ throw new Error(`checklist_remove_item: item "${itemRef}" disappeared from "${list.title}"`);
607
+ }
608
+ return {
609
+ listId: list.id,
610
+ itemId: removed.id,
611
+ text: removed.text,
612
+ removed: countItems([removed]).total,
613
+ };
614
+ });
615
+ },
616
+ })));
545
617
  ctx.effect(() => ctx.tools.register(defineTool({
546
618
  name: 'checklist_delete',
547
619
  description: 'Delete one checklist from the CURRENT session. The list and its items are gone; there is no archive. '
@@ -97,6 +97,17 @@ export declare function findItemById(lists: readonly Checklist[], id: string): {
97
97
  list: Checklist;
98
98
  item: ChecklistItem;
99
99
  } | undefined;
100
+ /**
101
+ * Remove one item (and its whole subtree) by exact id.
102
+ *
103
+ * Mutates the array in place, so callers must pass a document they own — the
104
+ * store's `update` already hands out a clone.
105
+ *
106
+ * @param items - sibling list to search, at any depth.
107
+ * @param id - exact item id.
108
+ * @returns the removed node, or `undefined` when no item has that id.
109
+ */
110
+ export declare function removeItemById(items: ChecklistItem[], id: string): ChecklistItem | undefined;
100
111
  export type RenameCheck = {
101
112
  kind: 'ok';
102
113
  list: Checklist;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-checklists",
3
- "version": "0.3.3",
3
+ "version": "0.4.0",
4
4
  "description": "Session-scoped checklists for DeepSeek Harness: the agent tracks work as a tree, the model always sees a compact digest, and a read-only sidebar tab mirrors it.",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",