@staff0rd/assist 0.374.2 → 0.376.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/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.374.2",
9
+ version: "0.376.0",
10
10
  type: "module",
11
11
  main: "dist/index.js",
12
12
  bin: {
@@ -4589,11 +4589,80 @@ async function recordBranchActivity(branchName) {
4589
4589
  ]);
4590
4590
  }
4591
4591
 
4592
+ // src/commands/branch/generateBranchSlug.ts
4593
+ import { execFile } from "child_process";
4594
+ import { promisify } from "util";
4595
+
4596
+ // src/shared/withSpinner.ts
4597
+ var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
4598
+ var TICK_MS = 80;
4599
+ async function withSpinner(text9, task) {
4600
+ if (!process.stderr.isTTY) return task();
4601
+ let frame = 0;
4602
+ const render = () => {
4603
+ process.stderr.write(`\r${SPINNER_FRAMES[frame]} ${text9}`);
4604
+ };
4605
+ render();
4606
+ const timer = setInterval(() => {
4607
+ frame = (frame + 1) % SPINNER_FRAMES.length;
4608
+ render();
4609
+ }, TICK_MS);
4610
+ try {
4611
+ return await task();
4612
+ } finally {
4613
+ clearInterval(timer);
4614
+ process.stderr.write("\r\x1B[K");
4615
+ }
4616
+ }
4617
+
4592
4618
  // src/commands/branch/deriveBranchSlug.ts
4619
+ var MAX_SLUG_WORDS = 5;
4593
4620
  function deriveBranchSlug(name) {
4594
- const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
4621
+ const words = name.toLowerCase().replace(/[^a-z0-9]+/g, "-").split("-").filter((word) => word.length > 0).filter((word) => !/^\d+$/.test(word));
4622
+ const slug = words.slice(0, MAX_SLUG_WORDS).join("-");
4595
4623
  return slug || "story";
4596
4624
  }
4625
+ function isConciseSlug(slug) {
4626
+ return slug.split("-").filter(Boolean).length <= MAX_SLUG_WORDS;
4627
+ }
4628
+
4629
+ // src/commands/branch/generateBranchSlug.ts
4630
+ var execFileAsync = promisify(execFile);
4631
+ async function generateBranchSlug(description) {
4632
+ const fallback = deriveBranchSlug(description);
4633
+ const suggestion = await requestSlug(description);
4634
+ if (!suggestion) return fallback;
4635
+ const slug = deriveBranchSlug(suggestion);
4636
+ return slug === "story" ? fallback : slug;
4637
+ }
4638
+ async function requestSlug(description) {
4639
+ try {
4640
+ const { stdout } = await withSpinner(
4641
+ "Generating a concise branch name",
4642
+ () => execFileAsync(
4643
+ "claude",
4644
+ ["-p", "--model", "haiku", buildPrompt(description)],
4645
+ { encoding: "utf8", timeout: 3e4 }
4646
+ )
4647
+ );
4648
+ const slug = stdout.trim().split("\n")[0].replace(/^["'`]|["'`]$/g, "").trim();
4649
+ return slug || void 0;
4650
+ } catch {
4651
+ return void 0;
4652
+ }
4653
+ }
4654
+ function buildPrompt(description) {
4655
+ return [
4656
+ "Suggest a concise git branch name for the work described below.",
4657
+ "Rules:",
4658
+ "- kebab-case: lowercase letters, digits, and single hyphens only",
4659
+ "- 2 to 4 words that capture the essence of the work",
4660
+ "- no ticket/issue numbers and no standalone numbers",
4661
+ "- reply with ONLY the branch name, no quotes or explanation",
4662
+ "",
4663
+ `Work: ${description}`
4664
+ ].join("\n");
4665
+ }
4597
4666
 
4598
4667
  // src/commands/backlog/ensureStoryBranch.ts
4599
4668
  async function ensureStoryBranch(item) {
@@ -4601,7 +4670,7 @@ async function ensureStoryBranch(item) {
4601
4670
  if (!config.prs?.required) return;
4602
4671
  if (hasBranchRef(item)) return;
4603
4672
  process.env.ASSIST_BACKLOG_ITEM_ID = String(item.id);
4604
- const slug = deriveBranchSlug(item.name);
4673
+ const slug = await generateBranchSlug(item.name);
4605
4674
  const { branchName } = await createBranch({ slug, jira: item.jiraKey });
4606
4675
  appendDaemonLog(
4607
4676
  `backlog run ${item.id}: prs.required set and no branch recorded; created ${branchName}`
@@ -6975,11 +7044,11 @@ function getHtml() {
6975
7044
  }
6976
7045
 
6977
7046
  // src/commands/sessions/web/getReviewSynthesis.ts
6978
- import { execFile } from "child_process";
7047
+ import { execFile as execFile2 } from "child_process";
6979
7048
  import { readFileSync as readFileSync17 } from "fs";
6980
7049
  import { homedir as homedir9 } from "os";
6981
7050
  import { basename as basename3, join as join22 } from "path";
6982
- import { promisify } from "util";
7051
+ import { promisify as promisify2 } from "util";
6983
7052
 
6984
7053
  // src/commands/sessions/web/findSynthesisForBranch.ts
6985
7054
  import { existsSync as existsSync24, readdirSync, statSync as statSync2 } from "fs";
@@ -7014,9 +7083,9 @@ function windowsCwdToWslPath(cwd) {
7014
7083
  }
7015
7084
 
7016
7085
  // src/commands/sessions/web/getReviewSynthesis.ts
7017
- var execFileAsync = promisify(execFile);
7086
+ var execFileAsync2 = promisify2(execFile2);
7018
7087
  function runGit(cwd, args) {
7019
- return execFileAsync("git", args, {
7088
+ return execFileAsync2("git", args, {
7020
7089
  encoding: "utf8",
7021
7090
  cwd: windowsCwdToWslPath(cwd)
7022
7091
  }).then((r) => r.stdout.trim());
@@ -7060,8 +7129,8 @@ function githubUrl(req, res) {
7060
7129
  }
7061
7130
 
7062
7131
  // src/commands/sessions/web/gitStatus.ts
7063
- import { execFile as execFile2 } from "child_process";
7064
- import { promisify as promisify2 } from "util";
7132
+ import { execFile as execFile3 } from "child_process";
7133
+ import { promisify as promisify3 } from "util";
7065
7134
 
7066
7135
  // src/commands/sessions/web/parseGitStatus.ts
7067
7136
  function extractPath(rest) {
@@ -7091,7 +7160,7 @@ function parseGitStatus(output) {
7091
7160
  }
7092
7161
 
7093
7162
  // src/commands/sessions/web/gitStatus.ts
7094
- var execFileAsync2 = promisify2(execFile2);
7163
+ var execFileAsync3 = promisify3(execFile3);
7095
7164
  async function gitStatus(req, res) {
7096
7165
  const cwd = getCwdParam(req, res);
7097
7166
  if (!cwd) return;
@@ -7104,7 +7173,7 @@ async function gitStatus(req, res) {
7104
7173
  function runGitStatus(cwd) {
7105
7174
  const args = ["status", "--porcelain", "--untracked-files=all"];
7106
7175
  const { file, argv } = /^[A-Za-z]:[\\/]/.test(cwd) ? { file: "git.exe", argv: ["-C", cwd, ...args] } : { file: "git", argv: args };
7107
- return execFileAsync2(file, argv, {
7176
+ return execFileAsync3(file, argv, {
7108
7177
  encoding: "utf8",
7109
7178
  ...file === "git" ? { cwd } : {}
7110
7179
  }).then((r) => r.stdout);
@@ -7303,8 +7372,8 @@ async function listUsageHistory(req, res) {
7303
7372
 
7304
7373
  // src/commands/sessions/web/openInCode.ts
7305
7374
  import { exec } from "child_process";
7306
- import { promisify as promisify3 } from "util";
7307
- var execAsync = promisify3(exec);
7375
+ import { promisify as promisify4 } from "util";
7376
+ var execAsync = promisify4(exec);
7308
7377
  async function openInCode(req, res) {
7309
7378
  const cwd = getCwdParam(req, res);
7310
7379
  if (!cwd) return;
@@ -7320,9 +7389,9 @@ async function openInCode(req, res) {
7320
7389
  }
7321
7390
 
7322
7391
  // src/commands/sessions/web/createCachedGhJson.ts
7323
- import { execFile as execFile3 } from "child_process";
7324
- import { promisify as promisify4 } from "util";
7325
- var execFileAsync3 = promisify4(execFile3);
7392
+ import { execFile as execFile4 } from "child_process";
7393
+ import { promisify as promisify5 } from "util";
7394
+ var execFileAsync4 = promisify5(execFile4);
7326
7395
  var CACHE_TTL_MS = 3e4;
7327
7396
  function createCachedGhJson(args, parse3, fallback, options2 = {}) {
7328
7397
  const { cacheFallback = true } = options2;
@@ -7334,7 +7403,7 @@ function createCachedGhJson(args, parse3, fallback, options2 = {}) {
7334
7403
  if (cached && cached.expires > now) return cached.value;
7335
7404
  let value = fallback;
7336
7405
  try {
7337
- const { stdout } = await execFileAsync3("gh", [...args, ...extraArgs], {
7406
+ const { stdout } = await execFileAsync4("gh", [...args, ...extraArgs], {
7338
7407
  encoding: "utf8",
7339
7408
  cwd: windowsCwdToWslPath(cwd)
7340
7409
  });
@@ -7378,9 +7447,9 @@ async function prList(req, res) {
7378
7447
  }
7379
7448
 
7380
7449
  // src/commands/sessions/web/prStatus.ts
7381
- import { execFile as execFile4 } from "child_process";
7382
- import { promisify as promisify5 } from "util";
7383
- var execFileAsync4 = promisify5(execFile4);
7450
+ import { execFile as execFile5 } from "child_process";
7451
+ import { promisify as promisify6 } from "util";
7452
+ var execFileAsync5 = promisify6(execFile5);
7384
7453
  function toPrSummary(parsed) {
7385
7454
  if (typeof parsed.number !== "number") return null;
7386
7455
  return {
@@ -7410,7 +7479,7 @@ var getOpenPrForBranch = createCachedGhJson(
7410
7479
  );
7411
7480
  async function getCurrentBranch(cwd) {
7412
7481
  try {
7413
- const { stdout } = await execFileAsync4(
7482
+ const { stdout } = await execFileAsync5(
7414
7483
  "git",
7415
7484
  ["rev-parse", "--abbrev-ref", "HEAD"],
7416
7485
  { encoding: "utf8", cwd: windowsCwdToWslPath(cwd) }
@@ -9984,14 +10053,18 @@ function validateSlug(slug) {
9984
10053
 
9985
10054
  // src/commands/branch/branch.ts
9986
10055
  async function branch(slug, options2) {
9987
- const slugError = validateSlug(slug);
10056
+ const conciseSlug = isConciseSlug(slug) ? slug : await generateBranchSlug(slug);
10057
+ if (conciseSlug !== slug) {
10058
+ console.log(`Shortened "${slug}" to "${conciseSlug}"`);
10059
+ }
10060
+ const slugError = validateSlug(conciseSlug);
9988
10061
  if (slugError) {
9989
10062
  console.error(`Error: ${slugError}`);
9990
10063
  process.exit(1);
9991
10064
  }
9992
10065
  try {
9993
10066
  const { branchName, defaultBranch } = await createBranch({
9994
- slug,
10067
+ slug: conciseSlug,
9995
10068
  jira: options2.jira
9996
10069
  });
9997
10070
  console.log(
@@ -18862,7 +18935,7 @@ function cachedReviewerResult(name, outputPath) {
18862
18935
  import { createLogUpdate as createLogUpdate2 } from "log-update";
18863
18936
 
18864
18937
  // src/commands/review/renderEntries.ts
18865
- var SPINNER_FRAMES = [
18938
+ var SPINNER_FRAMES2 = [
18866
18939
  "\u280B",
18867
18940
  "\u2819",
18868
18941
  "\u2839",
@@ -18889,11 +18962,11 @@ function renderEntries(entries, frame) {
18889
18962
  function renderEntry(entry, frame) {
18890
18963
  if (entry.state === "succeeded") return `\u2714 ${entry.text}`;
18891
18964
  if (entry.state === "failed") return `\u2716 ${entry.text}`;
18892
- return `${SPINNER_FRAMES[frame]} ${entry.text}`;
18965
+ return `${SPINNER_FRAMES2[frame]} ${entry.text}`;
18893
18966
  }
18894
18967
 
18895
18968
  // src/commands/review/MultiSpinner.ts
18896
- var TICK_MS = 80;
18969
+ var TICK_MS2 = 80;
18897
18970
  var MultiSpinner = class {
18898
18971
  entries = [];
18899
18972
  log = createLogUpdate2(process.stdout);
@@ -18943,9 +19016,9 @@ var MultiSpinner = class {
18943
19016
  if (this.timer || this.finished) return;
18944
19017
  this.render();
18945
19018
  this.timer = setInterval(() => {
18946
- this.frame = (this.frame + 1) % SPINNER_FRAMES.length;
19019
+ this.frame = (this.frame + 1) % SPINNER_FRAMES2.length;
18947
19020
  this.render();
18948
- }, TICK_MS);
19021
+ }, TICK_MS2);
18949
19022
  }
18950
19023
  render() {
18951
19024
  this.log(renderEntries(this.entries, this.frame));
@@ -20506,7 +20579,7 @@ function askQuestion(rl, question) {
20506
20579
  }
20507
20580
 
20508
20581
  // src/commands/transcript/configure.ts
20509
- function buildPrompt(label2, current) {
20582
+ function buildPrompt2(label2, current) {
20510
20583
  return current ? `${label2} [${current}]: ` : `${label2}: `;
20511
20584
  }
20512
20585
  function printExisting(existing) {
@@ -20519,15 +20592,15 @@ function printExisting(existing) {
20519
20592
  async function promptDirectories(rl, existing) {
20520
20593
  const vttDir = await askQuestion(
20521
20594
  rl,
20522
- buildPrompt("VTT directory", existing?.vttDir)
20595
+ buildPrompt2("VTT directory", existing?.vttDir)
20523
20596
  );
20524
20597
  const transcriptsDir = await askQuestion(
20525
20598
  rl,
20526
- buildPrompt("Transcripts directory", existing?.transcriptsDir)
20599
+ buildPrompt2("Transcripts directory", existing?.transcriptsDir)
20527
20600
  );
20528
20601
  const summaryDir = await askQuestion(
20529
20602
  rl,
20530
- buildPrompt("Summary directory", existing?.summaryDir)
20603
+ buildPrompt2("Summary directory", existing?.summaryDir)
20531
20604
  );
20532
20605
  return {
20533
20606
  vttDir: vttDir || existing?.vttDir || "",
@@ -24871,7 +24944,7 @@ function summariseSession(jsonlPath2) {
24871
24944
  if (!firstMessage && backlogIds.length === 0) {
24872
24945
  return void 0;
24873
24946
  }
24874
- const prompt = buildPrompt2(firstMessage, backlogIds);
24947
+ const prompt = buildPrompt3(firstMessage, backlogIds);
24875
24948
  try {
24876
24949
  const output = execFileSync10("claude", ["-p", "--model", "haiku", prompt], {
24877
24950
  encoding: "utf8",
@@ -24885,7 +24958,7 @@ function summariseSession(jsonlPath2) {
24885
24958
  return void 0;
24886
24959
  }
24887
24960
  }
24888
- function buildPrompt2(firstMessage, backlogIds) {
24961
+ function buildPrompt3(firstMessage, backlogIds) {
24889
24962
  const parts = [
24890
24963
  "Summarise this Claude Code session in ONE short sentence (under 100 chars).",
24891
24964
  "Return ONLY the summary, no quotes or explanation."
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@staff0rd/assist",
3
- "version": "0.374.2",
3
+ "version": "0.376.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "bin": {