dsh-checklists 0.3.2 → 0.3.3

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_rename` | Renames a list; titles stay unique within the session |
31
32
  | `checklist_template_save` | Saves a list as a reusable template for future sessions |
32
33
 
33
34
  Items are plain strings. **Two leading spaces per level** nest them:
@@ -88,6 +89,8 @@ document degrades to an empty list instead of failing the turn.
88
89
 
89
90
  ## Sidebar tab
90
91
 
92
+ ![Checklist sidebar tab](assets/screenshot-1.png)
93
+
91
94
  Open the right sidebar, then the **guide** (`+`) and pick **Checklist**.
92
95
 
93
96
  | Action | How |
Binary file
package/lib/model.js CHANGED
@@ -147,6 +147,35 @@ export function findItemById(lists, id) {
147
147
  }
148
148
  return undefined;
149
149
  }
150
+ /**
151
+ * Validate a checklist rename before applying it.
152
+ *
153
+ * Titles are how the user reads the sidebar and how `findList` resolves a
154
+ * reference, so a rename must never create a second list with the same title.
155
+ * Renaming a list to its own title (any case) is a no-op, not a conflict.
156
+ *
157
+ * @param lists - the session's checklists.
158
+ * @param ref - checklist id or exact title.
159
+ * @param nextTitle - the requested new title.
160
+ * @returns the resolved list, or the reason the rename cannot proceed.
161
+ */
162
+ export function checkRename(lists, ref, nextTitle) {
163
+ const to = nextTitle.trim();
164
+ if (to.length === 0)
165
+ return { kind: 'empty' };
166
+ const list = findList(lists, ref);
167
+ if (!list) {
168
+ return { kind: 'missing', available: lists.map((entry) => `${entry.id} (${entry.title})`) };
169
+ }
170
+ const lower = to.toLowerCase();
171
+ if (list.title.trim().toLowerCase() === lower) {
172
+ return { kind: 'ok', list, from: list.title, to };
173
+ }
174
+ const clash = lists.find((entry) => entry !== list && entry.title.trim().toLowerCase() === lower);
175
+ if (clash)
176
+ return { kind: 'duplicate', existing: clash.title };
177
+ return { kind: 'ok', list, from: list.title, to };
178
+ }
150
179
  /**
151
180
  * The next item to work on, in depth-first order.
152
181
  *
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, 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, 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: [
@@ -482,6 +482,66 @@ export function registerChecklistTools(ctx, store, config = {}) {
482
482
  });
483
483
  },
484
484
  })));
485
+ ctx.effect(() => ctx.tools.register(defineTool({
486
+ name: 'checklist_rename',
487
+ description: 'Rename an existing checklist in the CURRENT session. Titles must be unique within the session. '
488
+ + 'Pass the id from checklist_list, or the exact current title.',
489
+ parameters: {
490
+ listId: {
491
+ type: 'string',
492
+ required: true,
493
+ description: 'Checklist id, or its exact current title.',
494
+ },
495
+ title: {
496
+ type: 'string',
497
+ required: true,
498
+ description: 'New title for the checklist.',
499
+ },
500
+ },
501
+ output: {
502
+ schema: {
503
+ type: 'object',
504
+ additionalProperties: false,
505
+ properties: {
506
+ listId: { type: 'string', required: true },
507
+ from: { type: 'string', required: true },
508
+ to: { type: 'string', required: true },
509
+ },
510
+ },
511
+ render: (_args, value) => {
512
+ const result = value;
513
+ return [{ type: 'text', text: `Renamed "${result.from}" → "${result.to}".` }];
514
+ },
515
+ },
516
+ timeoutMs: 10_000,
517
+ async execute(args, exec) {
518
+ const input = args;
519
+ const sessionId = sessionOf(exec);
520
+ const ref = text(input.listId).trim();
521
+ const nextTitle = text(input.title).trim();
522
+ if (ref.length === 0)
523
+ throw new Error('checklist_rename: listId must not be empty');
524
+ if (nextTitle.length === 0)
525
+ throw new Error('checklist_rename: title must not be empty');
526
+ if (nextTitle.length > 120)
527
+ throw new Error('checklist_rename: title is longer than 120 characters');
528
+ return store.update(sessionId, (doc) => {
529
+ const check = checkRename(doc.lists, ref, nextTitle);
530
+ if (check.kind === 'empty')
531
+ throw new Error('checklist_rename: title must not be empty');
532
+ if (check.kind === 'missing') {
533
+ const available = check.available.join(', ');
534
+ throw new Error(`checklist_rename: no checklist "${ref}" in this session. `
535
+ + `Available: ${available.length > 0 ? available : 'none'}`);
536
+ }
537
+ if (check.kind === 'duplicate') {
538
+ throw new Error(`checklist_rename: a checklist titled "${check.existing}" already exists in this session`);
539
+ }
540
+ check.list.title = check.to;
541
+ return { listId: check.list.id, from: check.from, to: check.to };
542
+ });
543
+ },
544
+ })));
485
545
  ctx.effect(() => ctx.tools.register(defineTool({
486
546
  name: 'checklist_delete',
487
547
  description: 'Delete one checklist from the CURRENT session. The list and its items are gone; there is no archive. '
@@ -97,6 +97,33 @@ export declare function findItemById(lists: readonly Checklist[], id: string): {
97
97
  list: Checklist;
98
98
  item: ChecklistItem;
99
99
  } | undefined;
100
+ export type RenameCheck = {
101
+ kind: 'ok';
102
+ list: Checklist;
103
+ from: string;
104
+ to: string;
105
+ } | {
106
+ kind: 'empty';
107
+ } | {
108
+ kind: 'missing';
109
+ available: string[];
110
+ } | {
111
+ kind: 'duplicate';
112
+ existing: string;
113
+ };
114
+ /**
115
+ * Validate a checklist rename before applying it.
116
+ *
117
+ * Titles are how the user reads the sidebar and how `findList` resolves a
118
+ * reference, so a rename must never create a second list with the same title.
119
+ * Renaming a list to its own title (any case) is a no-op, not a conflict.
120
+ *
121
+ * @param lists - the session's checklists.
122
+ * @param ref - checklist id or exact title.
123
+ * @param nextTitle - the requested new title.
124
+ * @returns the resolved list, or the reason the rename cannot proceed.
125
+ */
126
+ export declare function checkRename(lists: readonly Checklist[], ref: string, nextTitle: string): RenameCheck;
100
127
  export interface NextItem {
101
128
  list: Checklist;
102
129
  item: ChecklistItem;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-checklists",
3
- "version": "0.3.2",
3
+ "version": "0.3.3",
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",
@@ -19,6 +19,7 @@
19
19
  "files": [
20
20
  "lib",
21
21
  "client",
22
+ "assets",
22
23
  "cordis.patch.yml",
23
24
  "README.md"
24
25
  ],