dsh-checklists 0.0.0-stage → 0.3.2

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.
@@ -0,0 +1,127 @@
1
+ /**
2
+ * Pure data model for session-scoped checklists.
3
+ *
4
+ * Nothing in this module imports a DSH package: the tree shape, the id
5
+ * allocation, and the reference resolver are unit-testable without a running
6
+ * harness.
7
+ */
8
+ export type ItemState = 'todo' | 'doing' | 'done' | 'blocked';
9
+ export declare const ITEM_STATES: readonly ItemState[];
10
+ export interface ChecklistItem {
11
+ id: string;
12
+ text: string;
13
+ state: ItemState;
14
+ children: ChecklistItem[];
15
+ }
16
+ export interface Checklist {
17
+ id: string;
18
+ title: string;
19
+ createdAt: string;
20
+ items: ChecklistItem[];
21
+ }
22
+ export interface SessionDoc {
23
+ version: 1;
24
+ sessionId: string;
25
+ lists: Checklist[];
26
+ }
27
+ export declare function emptyDoc(sessionId: string): SessionDoc;
28
+ /** Mint the next `<prefix>N` identifier that is not already in `used`. */
29
+ export declare function mintId(prefix: string, used: ReadonlySet<string>): string;
30
+ /**
31
+ * Build an item tree from plain lines.
32
+ *
33
+ * A line indented with two leading spaces per level nests under the previous
34
+ * shallower line. This is the format the model writes, and the format
35
+ * {@link toLines} round-trips.
36
+ *
37
+ * @param lines - item texts, optionally indented.
38
+ * @param used - id set to extend; pass the ids already present when appending
39
+ * to an existing checklist, or ids will collide.
40
+ * @returns the root items, with ids assigned depth-first.
41
+ */
42
+ export declare function buildItems(lines: readonly string[], used?: Set<string>): ChecklistItem[];
43
+ /** Render an item tree back to indented lines (inverse of {@link buildItems}). */
44
+ export declare function toLines(items: readonly ChecklistItem[], depth?: number): string[];
45
+ export interface FlatItem {
46
+ item: ChecklistItem;
47
+ depth: number;
48
+ }
49
+ /** Depth-first flattening, carrying each item's nesting depth. */
50
+ export declare function flatten(items: readonly ChecklistItem[], depth?: number): FlatItem[];
51
+ export interface ItemCounts {
52
+ total: number;
53
+ done: number;
54
+ }
55
+ export declare function countItems(items: readonly ChecklistItem[]): ItemCounts;
56
+ export interface RefHit {
57
+ list: Checklist;
58
+ item: ChecklistItem;
59
+ }
60
+ export type RefResult = {
61
+ kind: 'hit';
62
+ hit: RefHit;
63
+ } | {
64
+ kind: 'ambiguous';
65
+ candidates: RefHit[];
66
+ } | {
67
+ kind: 'miss';
68
+ };
69
+ /**
70
+ * Resolve one model-supplied reference against the session's checklists.
71
+ *
72
+ * Resolution order: exact id, exact case-insensitive text, unique
73
+ * case-insensitive prefix. Ambiguity is surfaced rather than guessed: the
74
+ * model can then pass a list id or the full text.
75
+ *
76
+ * @param lists - the session's checklists.
77
+ * @param ref - a model-supplied item id, text, or prefix.
78
+ * @param listId - optional checklist id to search within.
79
+ * @returns the hit, the ambiguity, or a miss.
80
+ */
81
+ export declare function resolveRef(lists: readonly Checklist[], ref: string, listId?: string): RefResult;
82
+ /** Whether a value is a valid item state. */
83
+ export declare function isItemState(value: unknown): value is ItemState;
84
+ /**
85
+ * Find a checklist by id or by exact case-insensitive title.
86
+ *
87
+ * Ids are what the tools hand out, but a model that just created "Release 2.3"
88
+ * will often pass the title back; accepting both avoids a pointless round trip.
89
+ *
90
+ * @param lists - the session's checklists.
91
+ * @param ref - a checklist id or its exact title.
92
+ * @returns the matching checklist, or `undefined`.
93
+ */
94
+ export declare function findList(lists: readonly Checklist[], ref: string): Checklist | undefined;
95
+ /** Find one item by exact id across every list. */
96
+ export declare function findItemById(lists: readonly Checklist[], id: string): {
97
+ list: Checklist;
98
+ item: ChecklistItem;
99
+ } | undefined;
100
+ export interface NextItem {
101
+ list: Checklist;
102
+ item: ChecklistItem;
103
+ /** Ancestor texts, outermost first; empty for a top-level item. */
104
+ path: string[];
105
+ }
106
+ export interface NextResult {
107
+ next?: NextItem;
108
+ /** Items that are not done, across the searched scope. */
109
+ remaining: number;
110
+ /** Items currently blocked (their subtrees are skipped). */
111
+ blocked: number;
112
+ }
113
+ /**
114
+ * The next item to work on, in depth-first order.
115
+ *
116
+ * Rules, in the order they are applied:
117
+ * - A `done` subtree is skipped entirely.
118
+ * - A `blocked` subtree is skipped (and counted).
119
+ * - An item with open children is a container: descend and return its first
120
+ * actionable leaf. A section header is never "the next thing to do".
121
+ * - A leaf is returned as-is.
122
+ *
123
+ * @param lists - the checklists to search.
124
+ * @param listId - optional checklist id to scope the search.
125
+ * @returns the next item (if any) plus the remaining and blocked counts.
126
+ */
127
+ export declare function nextItem(lists: readonly Checklist[], listId?: string): NextResult;
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Client diagnostics channel.
3
+ *
4
+ * The client half runs in a browser we cannot attach a debugger to from here.
5
+ * This endpoint lets it report its own load/registration stages to the host, so
6
+ * "the tab is not there" can be told apart from "the module never loaded".
7
+ *
8
+ * In-memory and bounded: it answers one question and dies with the process.
9
+ */
10
+ import type { IncomingMessage, ServerResponse } from 'node:http';
11
+ export declare const PROBE_ROUTE = "/dsh-checklist/client-probe";
12
+ export interface ProbeReport {
13
+ at: string;
14
+ stage: string;
15
+ detail: string;
16
+ }
17
+ export declare function serveProbe(req: IncomingMessage, res: ServerResponse): void;
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Read-only HTTP endpoint for the sidebar.
3
+ *
4
+ * Same-origin, GET only, one query parameter. The store validates the session
5
+ * id (it becomes a file name), so a crafted `session` cannot escape the storage
6
+ * directory; anything invalid answers 400 rather than throwing.
7
+ */
8
+ import type { IncomingMessage, ServerResponse } from 'node:http';
9
+ import type { ChecklistStore } from './store.js';
10
+ export declare const STATE_ROUTE = "/dsh-checklist/state";
11
+ export declare const ITEM_ROUTE = "/dsh-checklist/item";
12
+ /**
13
+ * Serve one session's checklist state.
14
+ *
15
+ * @param req - the incoming request.
16
+ * @param res - the response to write.
17
+ * @param store - the session store.
18
+ */
19
+ export declare function serveState(req: IncomingMessage, res: ServerResponse, store: ChecklistStore): Promise<void>;
20
+ /**
21
+ * Apply one text edit from the sidebar.
22
+ *
23
+ * This is the only write path outside the agent, so it is deliberately narrow:
24
+ * one item, by exact id, with an optional `expectedText` guard. When the guard
25
+ * does not match, the answer is 409 rather than a silent overwrite — the client
26
+ * re-reads and the user retries.
27
+ *
28
+ * @param req - the incoming request; must be POST.
29
+ * @param res - the response to write.
30
+ * @param store - the session store.
31
+ */
32
+ export declare function serveItemUpdate(req: IncomingMessage, res: ServerResponse, store: ChecklistStore): Promise<void>;
@@ -0,0 +1,30 @@
1
+ /**
2
+ * The public JSON shape the sidebar reads.
3
+ *
4
+ * Deliberately separate from the stored document: the client gets no `version`
5
+ * field, no future internals, and nothing it could mistake for a write channel.
6
+ */
7
+ import { type SessionDoc } from './model.js';
8
+ export interface PublicItem {
9
+ id: string;
10
+ text: string;
11
+ state: string;
12
+ children: PublicItem[];
13
+ }
14
+ export interface PublicList {
15
+ id: string;
16
+ title: string;
17
+ createdAt: string;
18
+ items: PublicItem[];
19
+ }
20
+ export interface PublicCounts {
21
+ lists: number;
22
+ total: number;
23
+ done: number;
24
+ }
25
+ export interface PublicState {
26
+ sessionId: string;
27
+ lists: PublicList[];
28
+ counts: PublicCounts;
29
+ }
30
+ export declare function toPublicState(doc: SessionDoc): PublicState;
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Durable per-session checklist storage.
3
+ *
4
+ * Layout under the DSH home directory:
5
+ *
6
+ * $DSH_HOME/storages/dsh-checklist/
7
+ * ├─ sessions/<sessionId>.json one document per session
8
+ * └─ templates.json reusable, cross-session templates
9
+ *
10
+ * The store keeps an in-process cache because the system-prompt digest is
11
+ * rendered synchronously, while disk access is not. Writes are serialized per
12
+ * session and land atomically (temp file + rename), so two concurrent tool
13
+ * calls cannot interleave into a torn document.
14
+ */
15
+ import { type SessionDoc } from './model.js';
16
+ /** Resolve the DSH home directory (env override wins). */
17
+ export declare function resolveDshHome(env?: NodeJS.ProcessEnv): string;
18
+ export declare class ChecklistStore {
19
+ readonly root: string;
20
+ private readonly cache;
21
+ private readonly chains;
22
+ private readonly inflight;
23
+ constructor(root?: string);
24
+ /** The synchronous view used by the prompt digest; `undefined` until primed. */
25
+ peek(sessionId: string): SessionDoc | undefined;
26
+ /** Load one session (cache first, then disk). */
27
+ prime(sessionId: string): Promise<SessionDoc>;
28
+ /**
29
+ * Apply one mutation to a session and persist it.
30
+ *
31
+ * The mutator receives a fresh clone, so a throw leaves the cached document
32
+ * untouched. Calls for one session are serialized.
33
+ */
34
+ update<T>(sessionId: string, mutate: (doc: SessionDoc) => T | Promise<T>): Promise<T>;
35
+ readTemplates(): Promise<Record<string, string[]>>;
36
+ writeTemplates(templates: Record<string, string[]>): Promise<void>;
37
+ /** Delete session files untouched for `maxAgeDays`. Returns the count removed. */
38
+ prune(maxAgeDays: number): Promise<number>;
39
+ private templatesFile;
40
+ private fileFor;
41
+ private read;
42
+ private write;
43
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * The write surface: five tools the agent uses to keep the checklist current.
3
+ *
4
+ * Only the agent writes. There is no client write path, so the host document is
5
+ * the single source of truth and the sidebar (phase 2) is a read-only mirror.
6
+ */
7
+ import type { Context } from '@deepseek-ai/cordis';
8
+ import type { ChecklistStore } from './store.js';
9
+ export interface ToolsConfig {
10
+ maxItemsPerList?: number;
11
+ templates?: Record<string, string[]>;
12
+ }
13
+ export declare function registerChecklistTools(ctx: Context, store: ChecklistStore, config?: ToolsConfig): void;
package/package.json CHANGED
@@ -1,6 +1,81 @@
1
1
  {
2
2
  "name": "dsh-checklists",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.3.2",
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
+ "type": "module",
6
+ "main": "lib/index.js",
7
+ "types": "lib/types/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./lib/types/index.d.ts",
11
+ "default": "./lib/index.js"
12
+ },
13
+ "./client": {
14
+ "default": "./client/index.js"
15
+ },
16
+ "./cordis.patch.yml": "./cordis.patch.yml",
17
+ "./package.json": "./package.json"
18
+ },
19
+ "files": [
20
+ "lib",
21
+ "client",
22
+ "cordis.patch.yml",
23
+ "README.md"
24
+ ],
25
+ "engines": {
26
+ "node": "^22.19.0 || >=24.0.0"
27
+ },
28
+ "license": "MIT",
29
+ "author": "manny333",
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "git+https://github.com/manny333/dsh-checklists.git"
33
+ },
34
+ "homepage": "https://github.com/manny333/dsh-checklists#readme",
35
+ "bugs": {
36
+ "url": "https://github.com/manny333/dsh-checklists/issues"
37
+ },
38
+ "keywords": [
39
+ "dsh",
40
+ "dsh-plugin",
41
+ "deepseek-harness",
42
+ "checklist",
43
+ "cordis",
44
+ "agent"
45
+ ],
46
+ "dsh": {
47
+ "bundle": {
48
+ "patch": "./cordis.patch.yml"
49
+ },
50
+ "client": {
51
+ "platform": "web",
52
+ "inject": [
53
+ "@deepseek-ai/dsh-client-locale",
54
+ "@deepseek-ai/dsh-client-ui-sidebar-right"
55
+ ]
56
+ }
57
+ },
58
+ "scripts": {
59
+ "typecheck": "tsc --noEmit",
60
+ "build": "tsc",
61
+ "test": "vitest run",
62
+ "smoke": "node scripts/smoke.mjs",
63
+ "prepack": "npm run build",
64
+ "check": "npm run typecheck && npm run test && npm run build"
65
+ },
66
+ "peerDependencies": {
67
+ "@deepseek-ai/cordis": "^4.0.1",
68
+ "@deepseek-ai/dsh-host-webserver": ">=0.1.0-rc.1 <0.3.0",
69
+ "@deepseek-ai/dsh-system-prompt": ">=0.1.0-rc.1 <0.3.0",
70
+ "@deepseek-ai/dsh-tools": ">=0.1.0-rc.1 <0.3.0"
71
+ },
72
+ "devDependencies": {
73
+ "@deepseek-ai/cordis": "^4.0.1",
74
+ "@deepseek-ai/dsh-host-webserver": "^0.1.7-rc.2",
75
+ "@deepseek-ai/dsh-system-prompt": "^0.1.7-rc.2",
76
+ "@deepseek-ai/dsh-tools": "^0.1.7-rc.2",
77
+ "@types/node": "^22.20.0",
78
+ "typescript": "^5.9.0",
79
+ "vitest": "^3.2.0"
80
+ }
81
+ }