@staff0rd/assist 0.209.0 → 0.210.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
@@ -39,6 +39,7 @@ After installation, the `assist` command will be available globally. You can als
39
39
  - `/commit` - Commit only relevant files from the session
40
40
  - `/devlog` - Generate devlog entry for the next unversioned day
41
41
  - `/draft` - Draft a new backlog item with LLM-assisted questioning
42
+ - `/forward-comments` - Split a coarse PR comment (e.g. from Slack) into per-line review comments on the current branch's PR, attributed to the original reviewer
42
43
  - `/pr` - Raise a PR with a concise description
43
44
  - `/refactor` - Run refactoring checks for code quality
44
45
  - `/prompts` - Analyze denied tool calls and suggest settings changes to auto-allow recurring prompts
@@ -0,0 +1,59 @@
1
+ ---
2
+ description: Split a coarse PR comment (e.g. from Slack) into per-line review comments on the current branch's PR, attributed to the original reviewer
3
+ allowed_args: "<reviewer handle> — followed by the quoted comment text"
4
+ ---
5
+
6
+ Take a multi-point comment that was given outside of GitHub's review UI (Slack, chat, in-person notes) and post each concern as a separate line-level review comment on the current branch's PR, attributed to the original reviewer with a `via @handle` prefix.
7
+
8
+ ## Inputs
9
+
10
+ - **Reviewer handle** — the GitHub username of the person whose feedback this is (e.g. `Sebastian-MakerX`)
11
+ - **Comment text** — the verbatim feedback, usually pasted into the user's prompt
12
+
13
+ If either is missing or ambiguous, ask before proceeding. The PR is always the current branch's PR.
14
+
15
+ ## Steps
16
+
17
+ 1. **Split the comment into atomic concerns.** Each concern becomes one review comment. A concern is a single actionable point about a single location in the code. Framing prose ("the only thing slightly naff is..."), transitions ("Another thing is..."), and meta-comments ("looks pretty good") are NOT concerns — drop them.
18
+
19
+ 2. **For each concern, locate the target file and line.** Read the PR's changed files (`gh pr diff` or `gh api repos/.../pulls/<n>/files`) and the relevant source. Anchor each comment on the most specific line that the concern is about:
20
+ - A suggestion about a specific resolver/function → the line of that resolver
21
+ - A suggestion about a file's name or location → line 1 of the file
22
+ - A suggestion about a type definition → the line where the type is defined
23
+ - A suggestion about a return shape → the line that constructs/returns it
24
+
25
+ 3. **Compose each comment body.** Format:
26
+
27
+ ```
28
+ via @<handle>
29
+
30
+ > <verbatim quote of just the part relevant to this line>
31
+ ```
32
+
33
+ Rules for the quote:
34
+ - **Quote verbatim.** Preserve the reviewer's exact wording, including typos, repeated words, odd spacing, and punctuation. Do not paraphrase, "fix," or tidy. The reviewer's voice matters.
35
+ - **Drop framing prose.** Only the actionable suggestion that maps to *this specific line* should appear. If the reviewer's sentence opens with broad framing ("the only thing that's slightly naff is...") and then states the suggestion, quote only the suggestion part.
36
+ - **Add backticks around symbols.** Identifier names, type names, file names, and function names should be wrapped in backticks (e.g. `` `displayValue` ``, `` `SurveyQuestionModel` ``, `` `resolvers.ts` ``) even if the reviewer didn't use them. This is the only formatting change permitted.
37
+ - **No editorialising.** Do not add your own commentary, explanation, or restatement. The whole body is the attribution line plus the quote.
38
+
39
+ 4. **Confirm placements before posting.** List each planned comment to the user as `<file>:<line> — <one-line summary>`. Wait for the user to confirm or correct file/line choices.
40
+
41
+ 5. **Post each comment** via:
42
+
43
+ ```
44
+ assist prs comment '<path>' <line> '<body>' 2>&1
45
+ ```
46
+
47
+ - Always single-quote `<path>` and `<body>`. Never double-quote — backticks in the body would break.
48
+ - For apostrophes inside the body (e.g. `it's`), use the shell escape `'\''`.
49
+ - Bodies must not contain "claude" or "opus" (the command rejects them).
50
+ - Run posts in parallel when there are no dependencies between them.
51
+
52
+ 6. **Report** the resulting `<file>:<line>` line returned by each `assist prs comment` call, plus any failures.
53
+
54
+ ## Notes
55
+
56
+ - `assist prs comment` publishes a new review thread immediately (despite the help text saying "pending review") — the comment appears live and notifies subscribers.
57
+ - `assist prs comment` targets the **current branch's PR**. If the feedback is for a different PR, switch branches first.
58
+ - If the same concern legitimately spans multiple lines/files, post one comment per anchor location, not a single combined comment.
59
+ - If the reviewer's text contains a clear typo of a real symbol name (e.g. `SuveyQuestion` for `SurveyQuestion`), preserve the typo in the quote. Do not silently correct.
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.209.0",
9
+ version: "0.210.0",
10
10
  type: "module",
11
11
  main: "dist/index.js",
12
12
  bin: {
@@ -96,7 +96,7 @@ var package_default = {
96
96
  };
97
97
 
98
98
  // src/commands/backlog/next.ts
99
- import chalk8 from "chalk";
99
+ import chalk9 from "chalk";
100
100
  import enquirer2 from "enquirer";
101
101
 
102
102
  // src/shared/exitOnCancel.ts
@@ -112,13 +112,276 @@ async function exitOnCancel(promise) {
112
112
  }
113
113
 
114
114
  // src/commands/backlog/acquireLock.ts
115
- import { existsSync as existsSync4, readFileSync as readFileSync4, unlinkSync, writeFileSync as writeFileSync3 } from "fs";
116
- import { join as join5 } from "path";
115
+ import { existsSync as existsSync6, readFileSync as readFileSync5, unlinkSync, writeFileSync as writeFileSync4 } from "fs";
116
+ import { join as join6 } from "path";
117
117
 
118
118
  // src/commands/backlog/shared.ts
119
- import { existsSync as existsSync3 } from "fs";
120
- import { join as join4 } from "path";
119
+ import { existsSync as existsSync5 } from "fs";
120
+ import { join as join5 } from "path";
121
+ import chalk2 from "chalk";
122
+
123
+ // src/shared/loadConfig.ts
124
+ import { existsSync as existsSync2, writeFileSync } from "fs";
125
+ import { homedir } from "os";
126
+ import { dirname, join } from "path";
121
127
  import chalk from "chalk";
128
+ import { stringify as stringifyYaml } from "yaml";
129
+
130
+ // src/shared/loadRawYaml.ts
131
+ import { existsSync, readFileSync } from "fs";
132
+ import { parse as parseYaml } from "yaml";
133
+ function loadRawYaml(path53) {
134
+ if (!existsSync(path53)) return {};
135
+ try {
136
+ const content = readFileSync(path53, "utf-8");
137
+ return parseYaml(content) || {};
138
+ } catch {
139
+ return {};
140
+ }
141
+ }
142
+
143
+ // src/shared/mergeDenyRules.ts
144
+ function mergeDenyRules(globalDeny, projectDeny) {
145
+ if (!globalDeny && !projectDeny) return void 0;
146
+ if (!globalDeny) return projectDeny;
147
+ if (!projectDeny) return globalDeny;
148
+ const projectPatterns = new Set(projectDeny.map((r) => r.pattern));
149
+ const globalOnly = globalDeny.filter((r) => !projectPatterns.has(r.pattern));
150
+ return [...globalOnly, ...projectDeny];
151
+ }
152
+ function mergeRawConfigs(globalRaw, projectRaw) {
153
+ const deny = mergeDenyRules(
154
+ globalRaw.deny,
155
+ projectRaw.deny
156
+ );
157
+ const merged = { ...globalRaw, ...projectRaw };
158
+ if (deny !== void 0) {
159
+ merged.deny = deny;
160
+ }
161
+ return merged;
162
+ }
163
+
164
+ // src/shared/types.ts
165
+ import { z as z2 } from "zod";
166
+
167
+ // src/shared/runConfigSchema.ts
168
+ import { z } from "zod";
169
+ var runParamSchema = z.strictObject({
170
+ name: z.string(),
171
+ required: z.boolean().optional(),
172
+ default: z.string().optional(),
173
+ description: z.string().optional()
174
+ });
175
+ var runConfigSchema = z.strictObject({
176
+ name: z.string(),
177
+ command: z.string(),
178
+ args: z.array(z.string()).optional(),
179
+ params: z.array(runParamSchema).optional(),
180
+ env: z.record(z.string(), z.string()).optional(),
181
+ filter: z.string().optional(),
182
+ pre: z.array(z.string()).optional(),
183
+ cwd: z.string().optional()
184
+ });
185
+ var runLinkSchema = z.strictObject({
186
+ link: z.string(),
187
+ prefix: z.string()
188
+ });
189
+
190
+ // src/shared/types.ts
191
+ var transcriptConfigSchema = z2.strictObject({
192
+ vttDir: z2.string(),
193
+ transcriptsDir: z2.string(),
194
+ summaryDir: z2.string()
195
+ });
196
+ var DEFAULT_WAKE_WORDS = ["computer"];
197
+ var DEFAULT_MODELS_DIR = "~/.assist/voice/models";
198
+ var assistConfigSchema = z2.strictObject({
199
+ commit: z2.strictObject({
200
+ conventional: z2.boolean().default(false),
201
+ pull: z2.boolean().default(false),
202
+ push: z2.boolean().default(false)
203
+ }).default({ conventional: false, pull: false, push: false }),
204
+ devlog: z2.strictObject({
205
+ name: z2.string().optional(),
206
+ ignore: z2.array(z2.string()).optional(),
207
+ skip: z2.record(z2.string(), z2.array(z2.string())).optional()
208
+ }).optional(),
209
+ notify: z2.strictObject({
210
+ enabled: z2.boolean().default(true)
211
+ }).default({ enabled: true }),
212
+ complexity: z2.strictObject({
213
+ ignore: z2.array(z2.string()).default(["**/*test.ts*"])
214
+ }).default({ ignore: ["**/*test.ts*"] }),
215
+ hardcodedColors: z2.strictObject({
216
+ ignore: z2.array(z2.string()).default([])
217
+ }).optional(),
218
+ restructure: z2.strictObject({
219
+ ignore: z2.array(z2.string()).default([])
220
+ }).optional(),
221
+ jira: z2.strictObject({
222
+ acField: z2.string().default("customfield_11937")
223
+ }).optional(),
224
+ roam: z2.strictObject({
225
+ clientId: z2.string(),
226
+ clientSecret: z2.string(),
227
+ accessToken: z2.string().optional(),
228
+ refreshToken: z2.string().optional(),
229
+ tokenExpiresAt: z2.number().optional()
230
+ }).optional(),
231
+ run: z2.array(z2.union([runConfigSchema, runLinkSchema])).optional(),
232
+ transcript: transcriptConfigSchema.optional(),
233
+ cliReadVerbs: z2.record(z2.string(), z2.array(z2.string())).optional(),
234
+ news: z2.strictObject({
235
+ feeds: z2.array(z2.string()).default([])
236
+ }).default({ feeds: [] }),
237
+ dotnet: z2.strictObject({
238
+ inspect: z2.strictObject({
239
+ suppress: z2.array(z2.string()).default([])
240
+ }).default({ suppress: [] })
241
+ }).optional(),
242
+ ravendb: z2.strictObject({
243
+ connections: z2.array(
244
+ z2.strictObject({
245
+ name: z2.string(),
246
+ url: z2.string(),
247
+ database: z2.string(),
248
+ apiKeyRef: z2.string()
249
+ })
250
+ ).default([]),
251
+ defaultConnection: z2.string().optional()
252
+ }).optional(),
253
+ seq: z2.strictObject({
254
+ connections: z2.array(
255
+ z2.strictObject({
256
+ name: z2.string(),
257
+ url: z2.string(),
258
+ apiToken: z2.string()
259
+ })
260
+ ).default([]),
261
+ defaultConnection: z2.string().optional()
262
+ }).optional(),
263
+ sql: z2.strictObject({
264
+ connections: z2.array(
265
+ z2.strictObject({
266
+ name: z2.string(),
267
+ server: z2.string(),
268
+ port: z2.number(),
269
+ user: z2.string(),
270
+ password: z2.string(),
271
+ database: z2.string()
272
+ })
273
+ ).default([]),
274
+ defaultConnection: z2.string().optional()
275
+ }).optional(),
276
+ screenshot: z2.strictObject({
277
+ outputDir: z2.string().default("./screenshots")
278
+ }).default({ outputDir: "./screenshots" }),
279
+ backlog: z2.strictObject({
280
+ autoCommit: z2.boolean().default(false)
281
+ }).default({ autoCommit: false }),
282
+ mermaid: z2.strictObject({
283
+ krokiUrl: z2.string().default("https://kroki.io")
284
+ }).default({ krokiUrl: "https://kroki.io" }),
285
+ deny: z2.array(
286
+ z2.strictObject({
287
+ pattern: z2.string(),
288
+ message: z2.string()
289
+ })
290
+ ).optional(),
291
+ sync: z2.strictObject({
292
+ autoConfirm: z2.boolean().default(false)
293
+ }).default({ autoConfirm: false }),
294
+ voice: z2.strictObject({
295
+ wakeWords: z2.array(z2.string()).default(DEFAULT_WAKE_WORDS),
296
+ mic: z2.string().optional(),
297
+ cwd: z2.string().optional(),
298
+ modelsDir: z2.string().default(DEFAULT_MODELS_DIR),
299
+ lockDir: z2.string().optional(),
300
+ submitWindows: z2.array(z2.string()).optional(),
301
+ models: z2.strictObject({
302
+ vad: z2.string().optional(),
303
+ smartTurn: z2.string().optional()
304
+ }).default({})
305
+ }).default({
306
+ wakeWords: DEFAULT_WAKE_WORDS,
307
+ modelsDir: DEFAULT_MODELS_DIR,
308
+ models: {}
309
+ })
310
+ });
311
+ function isRunLink(entry) {
312
+ return "link" in entry;
313
+ }
314
+
315
+ // src/shared/loadConfig.ts
316
+ function findConfigUp(startDir) {
317
+ let current = startDir;
318
+ while (current !== dirname(current)) {
319
+ const claudePath = join(current, ".claude", "assist.yml");
320
+ if (existsSync2(claudePath))
321
+ return { configPath: claudePath, rootDir: current };
322
+ const rootPath = join(current, "assist.yml");
323
+ if (existsSync2(rootPath)) return { configPath: rootPath, rootDir: current };
324
+ current = dirname(current);
325
+ }
326
+ return null;
327
+ }
328
+ function getConfigPath() {
329
+ const found = findConfigUp(process.cwd());
330
+ if (found) return found.configPath;
331
+ return join(process.cwd(), "assist.yml");
332
+ }
333
+ function getGlobalConfigPath() {
334
+ return join(homedir(), ".assist.yml");
335
+ }
336
+ function getConfigDir() {
337
+ return dirname(getConfigPath());
338
+ }
339
+ function getProjectRoot() {
340
+ const found = findConfigUp(process.cwd());
341
+ return found?.rootDir ?? process.cwd();
342
+ }
343
+ function loadConfig() {
344
+ const globalRaw = loadRawYaml(getGlobalConfigPath());
345
+ const projectRaw = loadRawYaml(getConfigPath());
346
+ const merged = mergeRawConfigs(globalRaw, projectRaw);
347
+ return assistConfigSchema.parse(merged);
348
+ }
349
+ function loadConfigFrom(startDir) {
350
+ const found = findConfigUp(startDir);
351
+ const configPath = found?.configPath ?? join(startDir, "assist.yml");
352
+ const globalRaw = loadRawYaml(getGlobalConfigPath());
353
+ const projectRaw = loadRawYaml(configPath);
354
+ const merged = mergeRawConfigs(globalRaw, projectRaw);
355
+ return {
356
+ config: assistConfigSchema.parse(merged),
357
+ configDir: dirname(configPath)
358
+ };
359
+ }
360
+ function loadProjectConfig() {
361
+ return loadRawYaml(getConfigPath());
362
+ }
363
+ function loadGlobalConfigRaw() {
364
+ return loadRawYaml(getGlobalConfigPath());
365
+ }
366
+ function saveGlobalConfig(config) {
367
+ writeFileSync(getGlobalConfigPath(), stringifyYaml(config, { lineWidth: 0 }));
368
+ }
369
+ function saveConfig(config) {
370
+ const configPath = getConfigPath();
371
+ writeFileSync(configPath, stringifyYaml(config, { lineWidth: 0 }));
372
+ }
373
+ function getTranscriptConfig() {
374
+ const config = loadConfig();
375
+ if (!config.transcript) {
376
+ console.error(
377
+ chalk.red(
378
+ "Transcript directories not configured. Run 'assist transcript configure' first."
379
+ )
380
+ );
381
+ process.exit(1);
382
+ }
383
+ return config.transcript;
384
+ }
122
385
 
123
386
  // src/commands/backlog/deleteItemRelations.ts
124
387
  function deleteItemRelations(db, itemId) {
@@ -139,8 +402,8 @@ function deleteItem(db, id) {
139
402
  }
140
403
 
141
404
  // src/commands/backlog/exportToJsonl.ts
142
- import { statSync, writeFileSync } from "fs";
143
- import { join } from "path";
405
+ import { statSync, writeFileSync as writeFileSync2 } from "fs";
406
+ import { join as join2 } from "path";
144
407
 
145
408
  // src/commands/backlog/loadComments.ts
146
409
  function loadComments(db, itemId) {
@@ -210,13 +473,13 @@ function loadAllItems(db) {
210
473
 
211
474
  // src/commands/backlog/exportToJsonl.ts
212
475
  function getJsonlPath(dir) {
213
- return join(dir, ".assist", "backlog.jsonl");
476
+ return join2(dir, ".assist", "backlog.jsonl");
214
477
  }
215
478
  function exportToJsonl(db, dir) {
216
479
  const jsonlPath = getJsonlPath(dir);
217
480
  const items = loadAllItems(db);
218
481
  const lines = items.map((item) => JSON.stringify(item));
219
- writeFileSync(jsonlPath, lines.length > 0 ? `${lines.join("\n")}
482
+ writeFileSync2(jsonlPath, lines.length > 0 ? `${lines.join("\n")}
220
483
  ` : "");
221
484
  const mtimeMs = statSync(jsonlPath).mtimeMs;
222
485
  db.prepare(
@@ -225,7 +488,7 @@ function exportToJsonl(db, dir) {
225
488
  }
226
489
 
227
490
  // src/commands/backlog/importFromJsonlIfNeeded.ts
228
- import { readFileSync, statSync as statSync2 } from "fs";
491
+ import { readFileSync as readFileSync2, statSync as statSync2 } from "fs";
229
492
 
230
493
  // src/commands/backlog/insertItemRelations.ts
231
494
  function insertComments(db, item) {
@@ -332,43 +595,43 @@ function saveAllItems(db, items) {
332
595
  }
333
596
 
334
597
  // src/commands/backlog/types.ts
335
- import { z } from "zod";
336
- var backlogStatusSchema = z.enum(["todo", "in-progress", "done", "wontdo"]);
337
- var backlogTypeSchema = z.enum(["story", "bug"]);
338
- var planTaskSchema = z.object({
339
- task: z.string()
598
+ import { z as z3 } from "zod";
599
+ var backlogStatusSchema = z3.enum(["todo", "in-progress", "done", "wontdo"]);
600
+ var backlogTypeSchema = z3.enum(["story", "bug"]);
601
+ var planTaskSchema = z3.object({
602
+ task: z3.string()
340
603
  }).strip();
341
- var planPhaseSchema = z.strictObject({
342
- name: z.string(),
343
- tasks: z.array(planTaskSchema),
344
- manualChecks: z.array(z.string()).optional()
604
+ var planPhaseSchema = z3.strictObject({
605
+ name: z3.string(),
606
+ tasks: z3.array(planTaskSchema),
607
+ manualChecks: z3.array(z3.string()).optional()
345
608
  });
346
- var backlogCommentTypeSchema = z.enum(["comment", "summary"]);
347
- var backlogCommentSchema = z.strictObject({
348
- id: z.number().optional(),
349
- text: z.string(),
350
- phase: z.number().optional(),
351
- timestamp: z.string(),
609
+ var backlogCommentTypeSchema = z3.enum(["comment", "summary"]);
610
+ var backlogCommentSchema = z3.strictObject({
611
+ id: z3.number().optional(),
612
+ text: z3.string(),
613
+ phase: z3.number().optional(),
614
+ timestamp: z3.string(),
352
615
  type: backlogCommentTypeSchema
353
616
  });
354
- var backlogLinkTypeSchema = z.enum(["relates-to", "depends-on"]);
355
- var backlogLinkSchema = z.strictObject({
617
+ var backlogLinkTypeSchema = z3.enum(["relates-to", "depends-on"]);
618
+ var backlogLinkSchema = z3.strictObject({
356
619
  type: backlogLinkTypeSchema,
357
- targetId: z.number()
620
+ targetId: z3.number()
358
621
  });
359
- var backlogItemSchema = z.strictObject({
360
- id: z.number(),
622
+ var backlogItemSchema = z3.strictObject({
623
+ id: z3.number(),
361
624
  type: backlogTypeSchema.default("story"),
362
- name: z.string(),
363
- description: z.string().optional(),
364
- acceptanceCriteria: z.array(z.string()),
365
- plan: z.array(planPhaseSchema).optional(),
366
- currentPhase: z.number().optional(),
625
+ name: z3.string(),
626
+ description: z3.string().optional(),
627
+ acceptanceCriteria: z3.array(z3.string()),
628
+ plan: z3.array(planPhaseSchema).optional(),
629
+ currentPhase: z3.number().optional(),
367
630
  status: backlogStatusSchema,
368
- comments: z.array(backlogCommentSchema).optional(),
369
- links: z.array(backlogLinkSchema).optional()
631
+ comments: z3.array(backlogCommentSchema).optional(),
632
+ links: z3.array(backlogLinkSchema).optional()
370
633
  });
371
- var backlogFileSchema = z.array(backlogItemSchema);
634
+ var backlogFileSchema = z3.array(backlogItemSchema);
372
635
 
373
636
  // src/commands/backlog/importFromJsonlIfNeeded.ts
374
637
  function getLastImportMs(db) {
@@ -391,7 +654,7 @@ function importFromJsonlIfNeeded(db, dir) {
391
654
  const fileMtimeMs = stat.mtimeMs;
392
655
  const lastImportMs = getLastImportMs(db);
393
656
  if (fileMtimeMs <= lastImportMs) return;
394
- const content = readFileSync(jsonlPath, "utf-8").trim();
657
+ const content = readFileSync2(jsonlPath, "utf-8").trim();
395
658
  if (content.length === 0) {
396
659
  setLastImportMs(db, fileMtimeMs);
397
660
  return;
@@ -405,14 +668,14 @@ function importFromJsonlIfNeeded(db, dir) {
405
668
  }
406
669
 
407
670
  // src/commands/backlog/migrateYamlIfNeeded.ts
408
- import { existsSync, readFileSync as readFileSync2, renameSync } from "fs";
409
- import { parse as parseYaml } from "yaml";
671
+ import { existsSync as existsSync3, readFileSync as readFileSync3, renameSync } from "fs";
672
+ import { parse as parseYaml2 } from "yaml";
410
673
  function migrateYamlIfNeeded(db, yamlPath) {
411
- if (!existsSync(yamlPath)) return false;
674
+ if (!existsSync3(yamlPath)) return false;
412
675
  const existing = db.prepare("SELECT COUNT(*) as count FROM items").get();
413
676
  if (existing.count > 0) return false;
414
- const content = readFileSync2(yamlPath, "utf-8");
415
- const raw = parseYaml(content) || [];
677
+ const content = readFileSync3(yamlPath, "utf-8");
678
+ const raw = parseYaml2(content) || [];
416
679
  const items = backlogFileSchema.parse(raw);
417
680
  if (items.length > 0) {
418
681
  saveAllItems(db, items);
@@ -424,21 +687,21 @@ function migrateYamlIfNeeded(db, yamlPath) {
424
687
 
425
688
  // src/commands/backlog/openDb.ts
426
689
  import { mkdirSync } from "fs";
427
- import { join as join3 } from "path";
690
+ import { join as join4 } from "path";
428
691
  import Database from "better-sqlite3";
429
692
 
430
693
  // src/commands/backlog/ensureGitignore.ts
431
- import { existsSync as existsSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
432
- import { join as join2 } from "path";
694
+ import { existsSync as existsSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
695
+ import { join as join3 } from "path";
433
696
  var gitignoreEntries = [".assist-*", ".assist/*.db*"];
434
697
  function ensureGitignore(dir) {
435
- const gitignorePath = join2(dir, ".gitignore");
436
- const existing = existsSync2(gitignorePath) ? readFileSync3(gitignorePath, "utf-8") : "";
698
+ const gitignorePath = join3(dir, ".gitignore");
699
+ const existing = existsSync4(gitignorePath) ? readFileSync4(gitignorePath, "utf-8") : "";
437
700
  const lines = existing.split(/\r?\n/);
438
701
  const missing = gitignoreEntries.filter((entry) => !lines.includes(entry));
439
702
  if (missing.length === 0) return;
440
703
  const suffix = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
441
- writeFileSync2(gitignorePath, `${existing}${suffix}${missing.join("\n")}
704
+ writeFileSync3(gitignorePath, `${existing}${suffix}${missing.join("\n")}
442
705
  `);
443
706
  }
444
707
 
@@ -478,7 +741,7 @@ function migrateToOneBasedPhases(db) {
478
741
  // src/commands/backlog/openDb.ts
479
742
  var _db;
480
743
  function getDbPath(dir) {
481
- return join3(dir, ".assist", "backlog.db");
744
+ return join4(dir, ".assist", "backlog.db");
482
745
  }
483
746
  function initSchema(db) {
484
747
  db.exec(`
@@ -535,7 +798,7 @@ function initSchema(db) {
535
798
  function openDb(dir) {
536
799
  if (_db) return _db;
537
800
  const dbPath = getDbPath(dir);
538
- mkdirSync(join3(dir, ".assist"), { recursive: true });
801
+ mkdirSync(join4(dir, ".assist"), { recursive: true });
539
802
  const db = new Database(dbPath);
540
803
  db.pragma("journal_mode = WAL");
541
804
  db.pragma("foreign_keys = ON");
@@ -584,14 +847,14 @@ function setBacklogDir(dir) {
584
847
  _backlogDir = dir;
585
848
  }
586
849
  function getBacklogDir() {
587
- return _backlogDir ?? process.cwd();
850
+ return _backlogDir ?? getProjectRoot();
588
851
  }
589
852
  function getBacklogPath() {
590
- return join4(getBacklogDir(), "assist.backlog.yml");
853
+ return join5(getBacklogDir(), "assist.backlog.yml");
591
854
  }
592
855
  function backlogExists() {
593
856
  const dir = getBacklogDir();
594
- return existsSync3(join4(dir, ".assist", "backlog.db")) || existsSync3(join4(dir, ".assist", "backlog.jsonl")) || existsSync3(join4(dir, "assist.backlog.yml"));
857
+ return existsSync5(join5(dir, ".assist", "backlog.db")) || existsSync5(join5(dir, ".assist", "backlog.jsonl")) || existsSync5(join5(dir, "assist.backlog.yml"));
595
858
  }
596
859
  function getDb() {
597
860
  const dir = getBacklogDir();
@@ -624,7 +887,7 @@ function loadAndFindItem(id) {
624
887
  const items = loadBacklog();
625
888
  const item = findItem(items, Number.parseInt(id, 10));
626
889
  if (!item) {
627
- console.log(chalk.red(`Item #${id} not found.`));
890
+ console.log(chalk2.red(`Item #${id} not found.`));
628
891
  return void 0;
629
892
  }
630
893
  return { items, item };
@@ -652,14 +915,10 @@ function removeItem(id) {
652
915
  exportToJsonl(db, getBacklogDir());
653
916
  return result.item.name;
654
917
  }
655
- function getNextId(items) {
656
- if (items.length === 0) return 1;
657
- return Math.max(...items.map((item) => item.id)) + 1;
658
- }
659
918
 
660
919
  // src/commands/backlog/acquireLock.ts
661
920
  function getLockPath(itemId) {
662
- return join5(getBacklogDir(), `.assist-lock-${itemId}.json`);
921
+ return join6(getBacklogDir(), `.assist-lock-${itemId}.json`);
663
922
  }
664
923
  function isProcessAlive(pid) {
665
924
  try {
@@ -671,9 +930,9 @@ function isProcessAlive(pid) {
671
930
  }
672
931
  function isLockedByOther(itemId) {
673
932
  const lockPath = getLockPath(itemId);
674
- if (!existsSync4(lockPath)) return false;
933
+ if (!existsSync6(lockPath)) return false;
675
934
  try {
676
- const lock = JSON.parse(readFileSync4(lockPath, "utf-8"));
935
+ const lock = JSON.parse(readFileSync5(lockPath, "utf-8"));
677
936
  if (lock.pid === process.pid) return false;
678
937
  return isProcessAlive(lock.pid);
679
938
  } catch {
@@ -681,7 +940,7 @@ function isLockedByOther(itemId) {
681
940
  }
682
941
  }
683
942
  function acquireLock(itemId) {
684
- writeFileSync3(
943
+ writeFileSync4(
685
944
  getLockPath(itemId),
686
945
  JSON.stringify({ pid: process.pid, timestamp: (/* @__PURE__ */ new Date()).toISOString() })
687
946
  );
@@ -695,30 +954,30 @@ function releaseLock(itemId) {
695
954
  }
696
955
 
697
956
  // src/commands/backlog/list/shared.ts
698
- import chalk2 from "chalk";
957
+ import chalk3 from "chalk";
699
958
  function statusIcon(status2) {
700
959
  switch (status2) {
701
960
  case "todo":
702
- return chalk2.dim("[ ]");
961
+ return chalk3.dim("[ ]");
703
962
  case "in-progress":
704
- return chalk2.yellow("[~]");
963
+ return chalk3.yellow("[~]");
705
964
  case "done":
706
- return chalk2.green("[x]");
965
+ return chalk3.green("[x]");
707
966
  case "wontdo":
708
- return chalk2.dim("[-]");
967
+ return chalk3.dim("[-]");
709
968
  }
710
969
  }
711
970
  function typeLabel(type) {
712
971
  switch (type) {
713
972
  case "bug":
714
- return chalk2.magenta("Bug");
973
+ return chalk3.magenta("Bug");
715
974
  case "story":
716
- return chalk2.cyan("Story");
975
+ return chalk3.cyan("Story");
717
976
  }
718
977
  }
719
978
  function phaseLabel(item) {
720
979
  if (!item.plan) return "";
721
- return chalk2.dim(` (phase ${item.currentPhase ?? 1}/${item.plan.length})`);
980
+ return chalk3.dim(` (phase ${item.currentPhase ?? 1}/${item.plan.length})`);
722
981
  }
723
982
  function isBlocked(item, items) {
724
983
  const deps2 = (item.links ?? []).filter((l) => l.type === "depends-on");
@@ -730,15 +989,15 @@ function isBlocked(item, items) {
730
989
  function dependencyLabel(item, items) {
731
990
  const deps2 = (item.links ?? []).filter((l) => l.type === "depends-on");
732
991
  if (deps2.length === 0) return "";
733
- if (isBlocked(item, items)) return chalk2.red(" [blocked]");
734
- return chalk2.dim(` [${deps2.length} dep${deps2.length > 1 ? "s" : ""}]`);
992
+ if (isBlocked(item, items)) return chalk3.red(" [blocked]");
993
+ return chalk3.dim(` [${deps2.length} dep${deps2.length > 1 ? "s" : ""}]`);
735
994
  }
736
995
  function printVerboseDetails(item) {
737
996
  if (item.description) {
738
- console.log(` ${chalk2.dim("Description:")} ${item.description}`);
997
+ console.log(` ${chalk3.dim("Description:")} ${item.description}`);
739
998
  }
740
999
  if (item.acceptanceCriteria.length > 0) {
741
- console.log(` ${chalk2.dim("Acceptance criteria:")}`);
1000
+ console.log(` ${chalk3.dim("Acceptance criteria:")}`);
742
1001
  for (const [i, criterion] of item.acceptanceCriteria.entries()) {
743
1002
  console.log(` ${i + 1}. ${criterion}`);
744
1003
  }
@@ -754,17 +1013,17 @@ function findResumable(items) {
754
1013
  }
755
1014
 
756
1015
  // src/commands/backlog/findUnblockedTodos.ts
757
- import chalk3 from "chalk";
1016
+ import chalk4 from "chalk";
758
1017
  function findUnblockedTodos(items) {
759
1018
  const todo = items.filter((i) => i.status === "todo");
760
1019
  if (todo.length === 0) {
761
- console.log(chalk3.green("All backlog items complete."));
1020
+ console.log(chalk4.green("All backlog items complete."));
762
1021
  return void 0;
763
1022
  }
764
1023
  const unblocked = todo.filter((i) => !isBlocked(i, items));
765
1024
  if (unblocked.length === 0) {
766
1025
  console.log(
767
- chalk3.yellow("All remaining todo items are blocked by dependencies.")
1026
+ chalk4.yellow("All remaining todo items are blocked by dependencies.")
768
1027
  );
769
1028
  return void 0;
770
1029
  }
@@ -772,7 +1031,7 @@ function findUnblockedTodos(items) {
772
1031
  }
773
1032
 
774
1033
  // src/commands/backlog/run.ts
775
- import chalk7 from "chalk";
1034
+ import chalk8 from "chalk";
776
1035
 
777
1036
  // src/commands/backlog/buildCommentLines.ts
778
1037
  function buildCommentLines(comments2) {
@@ -891,11 +1150,11 @@ function buildReviewPhase() {
891
1150
  }
892
1151
 
893
1152
  // src/commands/backlog/executePhase.ts
894
- import chalk5 from "chalk";
1153
+ import chalk6 from "chalk";
895
1154
 
896
1155
  // src/commands/backlog/resolvePhaseResult.ts
897
- import { existsSync as existsSync6, unlinkSync as unlinkSync2 } from "fs";
898
- import chalk4 from "chalk";
1156
+ import { existsSync as existsSync8, unlinkSync as unlinkSync2 } from "fs";
1157
+ import chalk5 from "chalk";
899
1158
 
900
1159
  // src/commands/backlog/handleIncompletePhase.ts
901
1160
  import enquirer from "enquirer";
@@ -914,28 +1173,28 @@ async function handleIncompletePhase() {
914
1173
  }
915
1174
 
916
1175
  // src/commands/backlog/readSignal.ts
917
- import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
1176
+ import { existsSync as existsSync7, readFileSync as readFileSync6 } from "fs";
918
1177
 
919
1178
  // src/commands/backlog/writeSignal.ts
920
- import { writeFileSync as writeFileSync4 } from "fs";
921
- import { join as join6 } from "path";
1179
+ import { writeFileSync as writeFileSync5 } from "fs";
1180
+ import { join as join7 } from "path";
922
1181
  function getSignalPath() {
923
1182
  const sessionId = process.env.ASSIST_SESSION_ID;
924
1183
  const filename = sessionId ? `.assist-signal-${sessionId}.json` : ".assist-signal.json";
925
- return join6(getBacklogDir(), filename);
1184
+ return join7(getBacklogDir(), filename);
926
1185
  }
927
1186
  function writeSignal(event, data) {
928
1187
  const sessionId = process.env.ASSIST_SESSION_ID;
929
1188
  const signal = { event, ...sessionId && { sessionId }, ...data };
930
- writeFileSync4(getSignalPath(), JSON.stringify(signal));
1189
+ writeFileSync5(getSignalPath(), JSON.stringify(signal));
931
1190
  }
932
1191
 
933
1192
  // src/commands/backlog/readSignal.ts
934
1193
  function readSignal() {
935
1194
  const path53 = getSignalPath();
936
- if (!existsSync5(path53)) return void 0;
1195
+ if (!existsSync7(path53)) return void 0;
937
1196
  try {
938
- return JSON.parse(readFileSync5(path53, "utf-8"));
1197
+ return JSON.parse(readFileSync6(path53, "utf-8"));
939
1198
  } catch {
940
1199
  return void 0;
941
1200
  }
@@ -944,7 +1203,7 @@ function readSignal() {
944
1203
  // src/commands/backlog/resolvePhaseResult.ts
945
1204
  function cleanupSignal() {
946
1205
  const statusPath = getSignalPath();
947
- if (existsSync6(statusPath)) {
1206
+ if (existsSync8(statusPath)) {
948
1207
  unlinkSync2(statusPath);
949
1208
  }
950
1209
  }
@@ -954,7 +1213,7 @@ function isTerminalStatus(itemId) {
954
1213
  return item?.status === "done" || item?.status === "wontdo";
955
1214
  }
956
1215
  async function resolvePhaseResult(phaseIndex, itemId) {
957
- if (!existsSync6(getSignalPath())) {
1216
+ if (!existsSync8(getSignalPath())) {
958
1217
  if (isTerminalStatus(itemId)) return -1;
959
1218
  const action = await handleIncompletePhase();
960
1219
  if (action === "abort") return -1;
@@ -965,12 +1224,12 @@ async function resolvePhaseResult(phaseIndex, itemId) {
965
1224
  if (signal?.event === "rewind") {
966
1225
  const targetPhase = signal.targetPhase;
967
1226
  const targetPhaseNumber = targetPhase + 1;
968
- console.log(chalk4.yellow(`
1227
+ console.log(chalk5.yellow(`
969
1228
  Rewinding to phase ${targetPhaseNumber}.`));
970
1229
  return targetPhase;
971
1230
  }
972
1231
  const phaseNumber = phaseIndex + 1;
973
- console.log(chalk4.green(`
1232
+ console.log(chalk5.green(`
974
1233
  Phase ${phaseNumber} completed.`));
975
1234
  return phaseIndex + 1;
976
1235
  }
@@ -993,11 +1252,11 @@ function spawnClaude(prompt, options2 = {}) {
993
1252
  }
994
1253
 
995
1254
  // src/commands/backlog/watchForMarker.ts
996
- import { existsSync as existsSync7, unwatchFile, watchFile } from "fs";
1255
+ import { existsSync as existsSync9, unwatchFile, watchFile } from "fs";
997
1256
  function watchForMarker(child) {
998
1257
  const statusPath = getSignalPath();
999
1258
  watchFile(statusPath, { interval: 1e3 }, () => {
1000
- if (!existsSync7(statusPath)) return;
1259
+ if (!existsSync9(statusPath)) return;
1001
1260
  const signal = readSignal();
1002
1261
  if (signal) {
1003
1262
  unwatchFile(statusPath);
@@ -1014,7 +1273,7 @@ async function executePhase(item, phaseIndex, phases, spawnOptions) {
1014
1273
  const phase = phases[phaseIndex];
1015
1274
  const phaseNumber = phaseIndex + 1;
1016
1275
  console.log(
1017
- chalk5.bold(
1276
+ chalk6.bold(
1018
1277
  `
1019
1278
  --- Phase ${phaseNumber}/${phases.length}: ${phase.name} ---
1020
1279
  `
@@ -1032,7 +1291,7 @@ async function executePhase(item, phaseIndex, phases, spawnOptions) {
1032
1291
  }
1033
1292
 
1034
1293
  // src/commands/backlog/prepareRun.ts
1035
- import chalk6 from "chalk";
1294
+ import chalk7 from "chalk";
1036
1295
 
1037
1296
  // src/commands/backlog/resolvePlan.ts
1038
1297
  function resolvePlan(item) {
@@ -1055,13 +1314,13 @@ function prepareRun(id) {
1055
1314
  const plan2 = resolvePlan(item);
1056
1315
  const startPhase = (item.currentPhase ?? 1) - 1;
1057
1316
  if (item.status === "done") {
1058
- console.log(chalk6.green(`Already done: #${id}: ${item.name}`));
1317
+ console.log(chalk7.green(`Already done: #${id}: ${item.name}`));
1059
1318
  return void 0;
1060
1319
  }
1061
1320
  if (startPhase > plan2.length) {
1062
1321
  setStatus(id, "done");
1063
1322
  console.log(
1064
- chalk6.green(`All phases already complete for #${id}: ${item.name}`)
1323
+ chalk7.green(`All phases already complete for #${id}: ${item.name}`)
1065
1324
  );
1066
1325
  return void 0;
1067
1326
  }
@@ -1086,13 +1345,13 @@ async function run(id, spawnOptions) {
1086
1345
  }
1087
1346
  }
1088
1347
  function logProgress(id, name, startPhase, total) {
1089
- console.log(chalk7.bold(`Running plan for #${id}: ${name}`));
1348
+ console.log(chalk8.bold(`Running plan for #${id}: ${name}`));
1090
1349
  if (startPhase > 0) {
1091
1350
  const phaseNumber = startPhase + 1;
1092
- console.log(chalk7.dim(`Resuming from phase ${phaseNumber}/${total}
1351
+ console.log(chalk8.dim(`Resuming from phase ${phaseNumber}/${total}
1093
1352
  `));
1094
1353
  } else {
1095
- console.log(chalk7.dim(`${total} phase(s)
1354
+ console.log(chalk8.dim(`${total} phase(s)
1096
1355
  `));
1097
1356
  }
1098
1357
  }
@@ -1125,7 +1384,7 @@ async function runReview(item, plan2, spawnOptions) {
1125
1384
  // src/commands/backlog/next.ts
1126
1385
  function toChoice(item, items) {
1127
1386
  const name = `${typeLabel(item.type)} #${item.id}: ${item.name}`;
1128
- return isBlocked(item, items) ? { name, disabled: chalk8.red("[blocked]") } : { name };
1387
+ return isBlocked(item, items) ? { name, disabled: chalk9.red("[blocked]") } : { name };
1129
1388
  }
1130
1389
  async function selectItem(todo, items) {
1131
1390
  const { selected } = await exitOnCancel(
@@ -1142,7 +1401,7 @@ async function pickItem(items, firstPick = false) {
1142
1401
  const resumable = findResumable(items);
1143
1402
  if (resumable) {
1144
1403
  console.log(
1145
- chalk8.bold(
1404
+ chalk9.bold(
1146
1405
  `Resuming in-progress item #${resumable.id}: ${resumable.name}`
1147
1406
  )
1148
1407
  );
@@ -1152,7 +1411,7 @@ async function pickItem(items, firstPick = false) {
1152
1411
  if (!unblocked) return void 0;
1153
1412
  if (firstPick && unblocked.length === 1) {
1154
1413
  const item = unblocked[0];
1155
- console.log(chalk8.bold(`Auto-selecting item #${item.id}: ${item.name}`));
1414
+ console.log(chalk9.bold(`Auto-selecting item #${item.id}: ${item.name}`));
1156
1415
  return String(item.id);
1157
1416
  }
1158
1417
  const todo = items.filter((i) => i.status === "todo");
@@ -1170,7 +1429,7 @@ async function next(options2) {
1170
1429
  }
1171
1430
 
1172
1431
  // src/commands/backlog/phaseDone.ts
1173
- import chalk9 from "chalk";
1432
+ import chalk10 from "chalk";
1174
1433
 
1175
1434
  // src/commands/backlog/addComment.ts
1176
1435
  function addComment(item, text, phase) {
@@ -1205,7 +1464,7 @@ function phaseDone(id, phase, summary) {
1205
1464
  });
1206
1465
  const result = loadAndFindItem(id);
1207
1466
  if (result?.item.status === "done") {
1208
- console.log(chalk9.dim(`Item #${id} already done, skipping phase advance.`));
1467
+ console.log(chalk10.dim(`Item #${id} already done, skipping phase advance.`));
1209
1468
  return;
1210
1469
  }
1211
1470
  if (result) {
@@ -1214,24 +1473,24 @@ function phaseDone(id, phase, summary) {
1214
1473
  }
1215
1474
  setCurrentPhase(id, phaseNumber + 1);
1216
1475
  console.log(
1217
- chalk9.green(`Phase ${phaseNumber} of item #${id} marked as complete.`)
1476
+ chalk10.green(`Phase ${phaseNumber} of item #${id} marked as complete.`)
1218
1477
  );
1219
1478
  }
1220
1479
 
1221
1480
  // src/commands/backlog/plan.ts
1222
- import chalk10 from "chalk";
1481
+ import chalk11 from "chalk";
1223
1482
  function plan(id) {
1224
1483
  const result = loadAndFindItem(id);
1225
1484
  if (!result) return;
1226
1485
  const { item } = result;
1227
1486
  if (!item.plan || item.plan.length === 0) {
1228
- console.log(chalk10.dim("No plan defined for this item."));
1487
+ console.log(chalk11.dim("No plan defined for this item."));
1229
1488
  return;
1230
1489
  }
1231
- console.log(chalk10.bold(item.name));
1490
+ console.log(chalk11.bold(item.name));
1232
1491
  console.log();
1233
1492
  for (const [i, phase] of item.plan.entries()) {
1234
- console.log(`${chalk10.bold(`Phase ${i + 1}:`)} ${phase.name}`);
1493
+ console.log(`${chalk11.bold(`Phase ${i + 1}:`)} ${phase.name}`);
1235
1494
  for (const task of phase.tasks) {
1236
1495
  console.log(` - ${task.task}`);
1237
1496
  }
@@ -1240,35 +1499,35 @@ function plan(id) {
1240
1499
  }
1241
1500
 
1242
1501
  // src/commands/backlog/show/index.ts
1243
- import chalk14 from "chalk";
1502
+ import chalk15 from "chalk";
1244
1503
 
1245
1504
  // src/commands/backlog/formatComment.ts
1246
- import chalk11 from "chalk";
1505
+ import chalk12 from "chalk";
1247
1506
  function formatComment(entry) {
1248
- const id = entry.id !== void 0 ? chalk11.dim(`#${entry.id} `) : "";
1249
- const tag = entry.type === "summary" ? chalk11.magenta("[summary]") : chalk11.cyan("[comment]");
1250
- const phase = entry.phase !== void 0 ? chalk11.dim(` (phase ${entry.phase})`) : "";
1251
- const time = chalk11.dim(entry.timestamp);
1507
+ const id = entry.id !== void 0 ? chalk12.dim(`#${entry.id} `) : "";
1508
+ const tag = entry.type === "summary" ? chalk12.magenta("[summary]") : chalk12.cyan("[comment]");
1509
+ const phase = entry.phase !== void 0 ? chalk12.dim(` (phase ${entry.phase})`) : "";
1510
+ const time = chalk12.dim(entry.timestamp);
1252
1511
  return `${id}${tag}${phase} ${time}
1253
1512
  ${entry.text}`;
1254
1513
  }
1255
1514
 
1256
1515
  // src/commands/backlog/show/printLinks.ts
1257
- import chalk12 from "chalk";
1516
+ import chalk13 from "chalk";
1258
1517
  function printLinks(item, items) {
1259
1518
  const links = item.links ?? [];
1260
1519
  if (links.length === 0) return;
1261
- console.log(chalk12.bold("Links"));
1520
+ console.log(chalk13.bold("Links"));
1262
1521
  for (const link3 of links) {
1263
1522
  const target = items.find((i) => i.id === link3.targetId);
1264
- const typeLabel2 = link3.type === "depends-on" ? chalk12.red("depends-on") : chalk12.blue("relates-to");
1523
+ const typeLabel2 = link3.type === "depends-on" ? chalk13.red("depends-on") : chalk13.blue("relates-to");
1265
1524
  if (target) {
1266
1525
  console.log(
1267
- ` ${typeLabel2} #${target.id} ${target.name} ${chalk12.dim(`(${target.status})`)}`
1526
+ ` ${typeLabel2} #${target.id} ${target.name} ${chalk13.dim(`(${target.status})`)}`
1268
1527
  );
1269
1528
  } else {
1270
1529
  console.log(
1271
- ` ${typeLabel2} #${link3.targetId} ${chalk12.dim("(not found)")}`
1530
+ ` ${typeLabel2} #${link3.targetId} ${chalk13.dim("(not found)")}`
1272
1531
  );
1273
1532
  }
1274
1533
  }
@@ -1276,15 +1535,15 @@ function printLinks(item, items) {
1276
1535
  }
1277
1536
 
1278
1537
  // src/commands/backlog/show/printPhaseTasks.ts
1279
- import chalk13 from "chalk";
1538
+ import chalk14 from "chalk";
1280
1539
  function printPhaseTasks(phase) {
1281
1540
  for (const task of phase.tasks) {
1282
1541
  console.log(` - ${task.task}`);
1283
1542
  }
1284
1543
  if (phase.manualChecks && phase.manualChecks.length > 0) {
1285
- console.log(` ${chalk13.dim("Manual checks:")}`);
1544
+ console.log(` ${chalk14.dim("Manual checks:")}`);
1286
1545
  for (const check2 of phase.manualChecks) {
1287
- console.log(` ${chalk13.dim(`- ${check2}`)}`);
1546
+ console.log(` ${chalk14.dim(`- ${check2}`)}`);
1288
1547
  }
1289
1548
  }
1290
1549
  }
@@ -1292,7 +1551,7 @@ function printPhaseTasks(phase) {
1292
1551
  // src/commands/backlog/show/index.ts
1293
1552
  function printPlan(item) {
1294
1553
  if (!item.plan || item.plan.length === 0) return;
1295
- console.log(chalk14.bold("Plan"));
1554
+ console.log(chalk15.bold("Plan"));
1296
1555
  for (const [i, phase] of item.plan.entries()) {
1297
1556
  const isCurrent = item.currentPhase === i + 1;
1298
1557
  printPhase(phase, i, isCurrent);
@@ -1301,8 +1560,8 @@ function printPlan(item) {
1301
1560
  }
1302
1561
  function phaseHeader(index, name, isCurrent) {
1303
1562
  const phaseNumber = index + 1;
1304
- const marker = isCurrent ? chalk14.green("\u25B6 ") : " ";
1305
- const label2 = isCurrent ? chalk14.green.bold(`Phase ${phaseNumber}: ${name}`) : `${chalk14.bold(`Phase ${phaseNumber}:`)} ${name}`;
1563
+ const marker = isCurrent ? chalk15.green("\u25B6 ") : " ";
1564
+ const label2 = isCurrent ? chalk15.green.bold(`Phase ${phaseNumber}: ${name}`) : `${chalk15.bold(`Phase ${phaseNumber}:`)} ${name}`;
1306
1565
  return `${marker}${label2}`;
1307
1566
  }
1308
1567
  function printPhase(phase, index, isCurrent) {
@@ -1310,15 +1569,15 @@ function printPhase(phase, index, isCurrent) {
1310
1569
  printPhaseTasks(phase);
1311
1570
  }
1312
1571
  function printHeader(item) {
1313
- console.log(chalk14.bold(`#${item.id} ${item.name}`));
1572
+ console.log(chalk15.bold(`#${item.id} ${item.name}`));
1314
1573
  console.log(
1315
- `${chalk14.dim("Type:")} ${item.type} ${chalk14.dim("Status:")} ${item.status}`
1574
+ `${chalk15.dim("Type:")} ${item.type} ${chalk15.dim("Status:")} ${item.status}`
1316
1575
  );
1317
1576
  console.log();
1318
1577
  }
1319
1578
  function printAcceptanceCriteria(criteria) {
1320
1579
  if (criteria.length === 0) return;
1321
- console.log(chalk14.bold("Acceptance Criteria"));
1580
+ console.log(chalk15.bold("Acceptance Criteria"));
1322
1581
  for (const [i, ac] of criteria.entries()) {
1323
1582
  console.log(` ${i + 1}. ${ac}`);
1324
1583
  }
@@ -1330,7 +1589,7 @@ function show(id) {
1330
1589
  const { item, items } = result;
1331
1590
  printHeader(item);
1332
1591
  if (item.description) {
1333
- console.log(chalk14.bold("Description"));
1592
+ console.log(chalk15.bold("Description"));
1334
1593
  console.log(item.description);
1335
1594
  console.log();
1336
1595
  }
@@ -1342,7 +1601,7 @@ function show(id) {
1342
1601
  function printComments(item) {
1343
1602
  const entries = item.comments ?? [];
1344
1603
  if (entries.length === 0) return;
1345
- console.log(chalk14.bold("Comments"));
1604
+ console.log(chalk15.bold("Comments"));
1346
1605
  for (const entry of entries) {
1347
1606
  console.log(` ${formatComment(entry)}`);
1348
1607
  }
@@ -1356,7 +1615,7 @@ import { WebSocketServer } from "ws";
1356
1615
  import {
1357
1616
  createServer
1358
1617
  } from "http";
1359
- import chalk15 from "chalk";
1618
+ import chalk16 from "chalk";
1360
1619
 
1361
1620
  // src/shared/openBrowser.ts
1362
1621
  import { exec } from "child_process";
@@ -1402,27 +1661,27 @@ function startWebServer(label2, port, handler, initialPath) {
1402
1661
  handler(req, res, port);
1403
1662
  });
1404
1663
  server.listen(port, () => {
1405
- console.log(chalk15.green(`${label2}: ${url}`));
1406
- console.log(chalk15.dim("Press Ctrl+C to stop"));
1664
+ console.log(chalk16.green(`${label2}: ${url}`));
1665
+ console.log(chalk16.dim("Press Ctrl+C to stop"));
1407
1666
  openBrowser(url);
1408
1667
  });
1409
1668
  return server;
1410
1669
  }
1411
1670
 
1412
1671
  // src/commands/sessions/web/handleRequest.ts
1413
- import { readFileSync as readFileSync7 } from "fs";
1672
+ import { readFileSync as readFileSync8 } from "fs";
1414
1673
  import { createRequire } from "module";
1415
1674
 
1416
1675
  // src/shared/createBundleHandler.ts
1417
- import { readFileSync as readFileSync6 } from "fs";
1418
- import { dirname, join as join7 } from "path";
1676
+ import { readFileSync as readFileSync7 } from "fs";
1677
+ import { dirname as dirname2, join as join8 } from "path";
1419
1678
  import { fileURLToPath } from "url";
1420
1679
  function createBundleHandler(importMetaUrl, bundlePath) {
1421
- const dir = dirname(fileURLToPath(importMetaUrl));
1680
+ const dir = dirname2(fileURLToPath(importMetaUrl));
1422
1681
  let cache;
1423
1682
  return (_req, res) => {
1424
1683
  if (!cache) {
1425
- cache = readFileSync6(join7(dir, bundlePath), "utf-8");
1684
+ cache = readFileSync7(join8(dir, bundlePath), "utf-8");
1426
1685
  }
1427
1686
  res.writeHead(200, { "Content-Type": "application/javascript" });
1428
1687
  res.end(cache);
@@ -1507,6 +1766,12 @@ function validateRewind(item, phase) {
1507
1766
  return void 0;
1508
1767
  }
1509
1768
 
1769
+ // src/commands/backlog/getNextId.ts
1770
+ function getNextId(items) {
1771
+ if (items.length === 0) return 1;
1772
+ return Math.max(...items.map((item) => item.id)) + 1;
1773
+ }
1774
+
1510
1775
  // src/commands/backlog/web/shared.ts
1511
1776
  function listItems(req, res) {
1512
1777
  const url = new URL(req.url ?? "/", "http://localhost");
@@ -1626,7 +1891,7 @@ function createCssHandler(packageEntry) {
1626
1891
  return (_req, res) => {
1627
1892
  if (!cache) {
1628
1893
  const resolved = require2.resolve(packageEntry);
1629
- cache = readFileSync7(resolved, "utf-8");
1894
+ cache = readFileSync8(resolved, "utf-8");
1630
1895
  }
1631
1896
  res.writeHead(200, { "Content-Type": "text/css" });
1632
1897
  res.end(cache);
@@ -1649,264 +1914,6 @@ var handleRequest = createFallbackHandler(
1649
1914
  handleItemRoute
1650
1915
  );
1651
1916
 
1652
- // src/shared/loadConfig.ts
1653
- import { existsSync as existsSync9, writeFileSync as writeFileSync5 } from "fs";
1654
- import { homedir } from "os";
1655
- import { dirname as dirname2, join as join8 } from "path";
1656
- import chalk16 from "chalk";
1657
- import { stringify as stringifyYaml } from "yaml";
1658
-
1659
- // src/shared/loadRawYaml.ts
1660
- import { existsSync as existsSync8, readFileSync as readFileSync8 } from "fs";
1661
- import { parse as parseYaml2 } from "yaml";
1662
- function loadRawYaml(path53) {
1663
- if (!existsSync8(path53)) return {};
1664
- try {
1665
- const content = readFileSync8(path53, "utf-8");
1666
- return parseYaml2(content) || {};
1667
- } catch {
1668
- return {};
1669
- }
1670
- }
1671
-
1672
- // src/shared/mergeDenyRules.ts
1673
- function mergeDenyRules(globalDeny, projectDeny) {
1674
- if (!globalDeny && !projectDeny) return void 0;
1675
- if (!globalDeny) return projectDeny;
1676
- if (!projectDeny) return globalDeny;
1677
- const projectPatterns = new Set(projectDeny.map((r) => r.pattern));
1678
- const globalOnly = globalDeny.filter((r) => !projectPatterns.has(r.pattern));
1679
- return [...globalOnly, ...projectDeny];
1680
- }
1681
- function mergeRawConfigs(globalRaw, projectRaw) {
1682
- const deny = mergeDenyRules(
1683
- globalRaw.deny,
1684
- projectRaw.deny
1685
- );
1686
- const merged = { ...globalRaw, ...projectRaw };
1687
- if (deny !== void 0) {
1688
- merged.deny = deny;
1689
- }
1690
- return merged;
1691
- }
1692
-
1693
- // src/shared/types.ts
1694
- import { z as z3 } from "zod";
1695
-
1696
- // src/shared/runConfigSchema.ts
1697
- import { z as z2 } from "zod";
1698
- var runParamSchema = z2.strictObject({
1699
- name: z2.string(),
1700
- required: z2.boolean().optional(),
1701
- default: z2.string().optional(),
1702
- description: z2.string().optional()
1703
- });
1704
- var runConfigSchema = z2.strictObject({
1705
- name: z2.string(),
1706
- command: z2.string(),
1707
- args: z2.array(z2.string()).optional(),
1708
- params: z2.array(runParamSchema).optional(),
1709
- env: z2.record(z2.string(), z2.string()).optional(),
1710
- filter: z2.string().optional(),
1711
- pre: z2.array(z2.string()).optional(),
1712
- cwd: z2.string().optional()
1713
- });
1714
- var runLinkSchema = z2.strictObject({
1715
- link: z2.string(),
1716
- prefix: z2.string()
1717
- });
1718
-
1719
- // src/shared/types.ts
1720
- var transcriptConfigSchema = z3.strictObject({
1721
- vttDir: z3.string(),
1722
- transcriptsDir: z3.string(),
1723
- summaryDir: z3.string()
1724
- });
1725
- var DEFAULT_WAKE_WORDS = ["computer"];
1726
- var DEFAULT_MODELS_DIR = "~/.assist/voice/models";
1727
- var assistConfigSchema = z3.strictObject({
1728
- commit: z3.strictObject({
1729
- conventional: z3.boolean().default(false),
1730
- pull: z3.boolean().default(false),
1731
- push: z3.boolean().default(false)
1732
- }).default({ conventional: false, pull: false, push: false }),
1733
- devlog: z3.strictObject({
1734
- name: z3.string().optional(),
1735
- ignore: z3.array(z3.string()).optional(),
1736
- skip: z3.record(z3.string(), z3.array(z3.string())).optional()
1737
- }).optional(),
1738
- notify: z3.strictObject({
1739
- enabled: z3.boolean().default(true)
1740
- }).default({ enabled: true }),
1741
- complexity: z3.strictObject({
1742
- ignore: z3.array(z3.string()).default(["**/*test.ts*"])
1743
- }).default({ ignore: ["**/*test.ts*"] }),
1744
- hardcodedColors: z3.strictObject({
1745
- ignore: z3.array(z3.string()).default([])
1746
- }).optional(),
1747
- restructure: z3.strictObject({
1748
- ignore: z3.array(z3.string()).default([])
1749
- }).optional(),
1750
- jira: z3.strictObject({
1751
- acField: z3.string().default("customfield_11937")
1752
- }).optional(),
1753
- roam: z3.strictObject({
1754
- clientId: z3.string(),
1755
- clientSecret: z3.string(),
1756
- accessToken: z3.string().optional(),
1757
- refreshToken: z3.string().optional(),
1758
- tokenExpiresAt: z3.number().optional()
1759
- }).optional(),
1760
- run: z3.array(z3.union([runConfigSchema, runLinkSchema])).optional(),
1761
- transcript: transcriptConfigSchema.optional(),
1762
- cliReadVerbs: z3.record(z3.string(), z3.array(z3.string())).optional(),
1763
- news: z3.strictObject({
1764
- feeds: z3.array(z3.string()).default([])
1765
- }).default({ feeds: [] }),
1766
- dotnet: z3.strictObject({
1767
- inspect: z3.strictObject({
1768
- suppress: z3.array(z3.string()).default([])
1769
- }).default({ suppress: [] })
1770
- }).optional(),
1771
- ravendb: z3.strictObject({
1772
- connections: z3.array(
1773
- z3.strictObject({
1774
- name: z3.string(),
1775
- url: z3.string(),
1776
- database: z3.string(),
1777
- apiKeyRef: z3.string()
1778
- })
1779
- ).default([]),
1780
- defaultConnection: z3.string().optional()
1781
- }).optional(),
1782
- seq: z3.strictObject({
1783
- connections: z3.array(
1784
- z3.strictObject({
1785
- name: z3.string(),
1786
- url: z3.string(),
1787
- apiToken: z3.string()
1788
- })
1789
- ).default([]),
1790
- defaultConnection: z3.string().optional()
1791
- }).optional(),
1792
- sql: z3.strictObject({
1793
- connections: z3.array(
1794
- z3.strictObject({
1795
- name: z3.string(),
1796
- server: z3.string(),
1797
- port: z3.number(),
1798
- user: z3.string(),
1799
- password: z3.string(),
1800
- database: z3.string()
1801
- })
1802
- ).default([]),
1803
- defaultConnection: z3.string().optional()
1804
- }).optional(),
1805
- screenshot: z3.strictObject({
1806
- outputDir: z3.string().default("./screenshots")
1807
- }).default({ outputDir: "./screenshots" }),
1808
- backlog: z3.strictObject({
1809
- autoCommit: z3.boolean().default(false)
1810
- }).default({ autoCommit: false }),
1811
- mermaid: z3.strictObject({
1812
- krokiUrl: z3.string().default("https://kroki.io")
1813
- }).default({ krokiUrl: "https://kroki.io" }),
1814
- deny: z3.array(
1815
- z3.strictObject({
1816
- pattern: z3.string(),
1817
- message: z3.string()
1818
- })
1819
- ).optional(),
1820
- sync: z3.strictObject({
1821
- autoConfirm: z3.boolean().default(false)
1822
- }).default({ autoConfirm: false }),
1823
- voice: z3.strictObject({
1824
- wakeWords: z3.array(z3.string()).default(DEFAULT_WAKE_WORDS),
1825
- mic: z3.string().optional(),
1826
- cwd: z3.string().optional(),
1827
- modelsDir: z3.string().default(DEFAULT_MODELS_DIR),
1828
- lockDir: z3.string().optional(),
1829
- submitWindows: z3.array(z3.string()).optional(),
1830
- models: z3.strictObject({
1831
- vad: z3.string().optional(),
1832
- smartTurn: z3.string().optional()
1833
- }).default({})
1834
- }).default({
1835
- wakeWords: DEFAULT_WAKE_WORDS,
1836
- modelsDir: DEFAULT_MODELS_DIR,
1837
- models: {}
1838
- })
1839
- });
1840
- function isRunLink(entry) {
1841
- return "link" in entry;
1842
- }
1843
-
1844
- // src/shared/loadConfig.ts
1845
- function findConfigUp(startDir) {
1846
- let current = startDir;
1847
- while (current !== dirname2(current)) {
1848
- const claudePath = join8(current, ".claude", "assist.yml");
1849
- if (existsSync9(claudePath)) return claudePath;
1850
- const rootPath = join8(current, "assist.yml");
1851
- if (existsSync9(rootPath)) return rootPath;
1852
- current = dirname2(current);
1853
- }
1854
- return null;
1855
- }
1856
- function getConfigPath() {
1857
- const found = findConfigUp(process.cwd());
1858
- if (found) return found;
1859
- return join8(process.cwd(), "assist.yml");
1860
- }
1861
- function getGlobalConfigPath() {
1862
- return join8(homedir(), ".assist.yml");
1863
- }
1864
- function getConfigDir() {
1865
- return dirname2(getConfigPath());
1866
- }
1867
- function loadConfig() {
1868
- const globalRaw = loadRawYaml(getGlobalConfigPath());
1869
- const projectRaw = loadRawYaml(getConfigPath());
1870
- const merged = mergeRawConfigs(globalRaw, projectRaw);
1871
- return assistConfigSchema.parse(merged);
1872
- }
1873
- function loadConfigFrom(startDir) {
1874
- const found = findConfigUp(startDir);
1875
- const configPath = found ?? join8(startDir, "assist.yml");
1876
- const globalRaw = loadRawYaml(getGlobalConfigPath());
1877
- const projectRaw = loadRawYaml(configPath);
1878
- const merged = mergeRawConfigs(globalRaw, projectRaw);
1879
- return {
1880
- config: assistConfigSchema.parse(merged),
1881
- configDir: dirname2(configPath)
1882
- };
1883
- }
1884
- function loadProjectConfig() {
1885
- return loadRawYaml(getConfigPath());
1886
- }
1887
- function loadGlobalConfigRaw() {
1888
- return loadRawYaml(getGlobalConfigPath());
1889
- }
1890
- function saveGlobalConfig(config) {
1891
- writeFileSync5(getGlobalConfigPath(), stringifyYaml(config, { lineWidth: 0 }));
1892
- }
1893
- function saveConfig(config) {
1894
- const configPath = getConfigPath();
1895
- writeFileSync5(configPath, stringifyYaml(config, { lineWidth: 0 }));
1896
- }
1897
- function getTranscriptConfig() {
1898
- const config = loadConfig();
1899
- if (!config.transcript) {
1900
- console.error(
1901
- chalk16.red(
1902
- "Transcript directories not configured. Run 'assist transcript configure' first."
1903
- )
1904
- );
1905
- process.exit(1);
1906
- }
1907
- return config.transcript;
1908
- }
1909
-
1910
1917
  // src/shared/resolveRunConfigs.ts
1911
1918
  import { dirname as dirname3, relative, resolve as resolve3 } from "path";
1912
1919
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@staff0rd/assist",
3
- "version": "0.209.0",
3
+ "version": "0.210.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "bin": {