@staff0rd/assist 0.381.0 → 0.382.1

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.381.0",
9
+ version: "0.382.1",
10
10
  type: "module",
11
11
  main: "dist/index.js",
12
12
  bin: {
@@ -27,15 +27,7 @@ var package_default = {
27
27
  build: "tsup",
28
28
  start: "node dist/index.js",
29
29
  "check:types": "tsc --noEmit",
30
- test: "vitest run",
31
- "verify:lint": "oxlint --config .oxlintrc.json src",
32
- "verify:format": "oxfmt .",
33
- "verify:knip": "knip --no-progress --treat-config-hints-as-errors",
34
- "verify:duplicate-code": 'jscpd --format typescript,tsx,python --exitCode 1 --ignore "**/*.test.*,**/.venv/**" -r consoleFull src',
35
- "verify:test": "vitest run --changed --passWithNoTests --reporter dot",
36
- "verify:config-keys": "tsx src/index.ts verify config-keys",
37
- "verify:react-build": "esbuild src/commands/sessions/web/ui/App.tsx --bundle --minify --format=iife --target=es2020 --outfile=dist/commands/sessions/web/bundle.verify.js --jsx=automatic --jsx-import-source=react --define:process.env.NODE_ENV='production'",
38
- "verify:circular-deps": "skott --displayMode=raw --showCircularDependencies --exitCodeOnCircularDependencies=1 src/index.ts"
30
+ test: "vitest run"
39
31
  },
40
32
  overrides: {
41
33
  lodash: "^4.18.0",
@@ -432,10 +424,10 @@ function getTranscriptConfig() {
432
424
  // src/shared/db/schema.ts
433
425
  import {
434
426
  foreignKey,
435
- integer as integer8,
436
- pgTable as pgTable8,
437
- primaryKey as primaryKey5,
438
- text as text7
427
+ integer as integer9,
428
+ pgTable as pgTable9,
429
+ primaryKey as primaryKey6,
430
+ text as text8
439
431
  } from "drizzle-orm/pg-core";
440
432
 
441
433
  // src/shared/db/backups.ts
@@ -536,82 +528,96 @@ var phaseUsage = pgTable6(
536
528
  (t) => [primaryKey3({ columns: [t.itemId, t.phaseIdx] })]
537
529
  );
538
530
 
531
+ // src/shared/db/phaseUsageMessages.ts
532
+ import { integer as integer7, pgTable as pgTable7, primaryKey as primaryKey4, text as text6 } from "drizzle-orm/pg-core";
533
+ var phaseUsageMessages = pgTable7(
534
+ "phase_usage_messages",
535
+ {
536
+ itemId: integer7("item_id").notNull(),
537
+ phaseIdx: integer7("phase_idx").notNull(),
538
+ messageId: text6("message_id").notNull()
539
+ },
540
+ (t) => [primaryKey4({ columns: [t.itemId, t.phaseIdx, t.messageId] })]
541
+ );
542
+
539
543
  // src/shared/db/usagePeaks.ts
540
544
  import {
541
545
  bigint as bigint3,
542
546
  boolean as boolean2,
543
547
  doublePrecision,
544
- integer as integer7,
545
- pgTable as pgTable7,
546
- primaryKey as primaryKey4,
547
- text as text6,
548
+ integer as integer8,
549
+ pgTable as pgTable8,
550
+ primaryKey as primaryKey5,
551
+ text as text7,
548
552
  timestamp as timestamp4
549
553
  } from "drizzle-orm/pg-core";
550
- var usagePeaks = pgTable7(
554
+ var usagePeaks = pgTable8(
551
555
  "usage_peaks",
552
556
  {
553
- window: text6().$type().notNull(),
557
+ window: text7().$type().notNull(),
554
558
  resetsAt: bigint3("resets_at", { mode: "number" }).notNull(),
555
- segment: integer7().notNull().default(0),
559
+ segment: integer8().notNull().default(0),
556
560
  usedPercentage: doublePrecision("used_percentage").notNull(),
557
561
  resetDetected: boolean2("reset_detected").notNull().default(false),
562
+ tokensUp: bigint3("tokens_up", { mode: "number" }).notNull().default(0),
563
+ tokensDown: bigint3("tokens_down", { mode: "number" }).notNull().default(0),
558
564
  createdAt: timestamp4("created_at", { withTimezone: true }).notNull().defaultNow()
559
565
  },
560
- (t) => [primaryKey4({ columns: [t.window, t.resetsAt, t.segment] })]
566
+ (t) => [primaryKey5({ columns: [t.window, t.resetsAt, t.segment] })]
561
567
  );
562
568
 
563
569
  // src/shared/db/schema.ts
564
- var comments = pgTable8("comments", {
565
- id: integer8().generatedByDefaultAsIdentity().primaryKey(),
566
- itemId: integer8("item_id").notNull().references(() => items.id, { onDelete: "cascade" }),
567
- idx: integer8().notNull(),
568
- text: text7().notNull(),
569
- phase: integer8(),
570
- timestamp: text7().notNull(),
571
- type: text7().notNull().default("comment")
570
+ var comments = pgTable9("comments", {
571
+ id: integer9().generatedByDefaultAsIdentity().primaryKey(),
572
+ itemId: integer9("item_id").notNull().references(() => items.id, { onDelete: "cascade" }),
573
+ idx: integer9().notNull(),
574
+ text: text8().notNull(),
575
+ phase: integer9(),
576
+ timestamp: text8().notNull(),
577
+ type: text8().notNull().default("comment")
572
578
  });
573
- var links = pgTable8(
579
+ var links = pgTable9(
574
580
  "links",
575
581
  {
576
- itemId: integer8("item_id").notNull().references(() => items.id, { onDelete: "cascade" }),
577
- type: text7().notNull(),
578
- targetId: integer8("target_id").notNull()
582
+ itemId: integer9("item_id").notNull().references(() => items.id, { onDelete: "cascade" }),
583
+ type: text8().notNull(),
584
+ targetId: integer9("target_id").notNull()
579
585
  },
580
- (t) => [primaryKey5({ columns: [t.itemId, t.type, t.targetId] })]
586
+ (t) => [primaryKey6({ columns: [t.itemId, t.type, t.targetId] })]
581
587
  );
582
- var planPhases = pgTable8(
588
+ var planPhases = pgTable9(
583
589
  "plan_phases",
584
590
  {
585
- itemId: integer8("item_id").notNull().references(() => items.id, { onDelete: "cascade" }),
586
- idx: integer8().notNull(),
587
- name: text7().notNull(),
588
- manualChecks: text7("manual_checks")
591
+ itemId: integer9("item_id").notNull().references(() => items.id, { onDelete: "cascade" }),
592
+ idx: integer9().notNull(),
593
+ name: text8().notNull(),
594
+ manualChecks: text8("manual_checks")
589
595
  },
590
- (t) => [primaryKey5({ columns: [t.itemId, t.idx] })]
596
+ (t) => [primaryKey6({ columns: [t.itemId, t.idx] })]
591
597
  );
592
- var planTasks = pgTable8(
598
+ var planTasks = pgTable9(
593
599
  "plan_tasks",
594
600
  {
595
- itemId: integer8("item_id").notNull(),
596
- phaseIdx: integer8("phase_idx").notNull(),
597
- idx: integer8().notNull(),
598
- task: text7().notNull()
601
+ itemId: integer9("item_id").notNull(),
602
+ phaseIdx: integer9("phase_idx").notNull(),
603
+ idx: integer9().notNull(),
604
+ task: text8().notNull()
599
605
  },
600
606
  (t) => [
601
- primaryKey5({ columns: [t.itemId, t.phaseIdx, t.idx] }),
607
+ primaryKey6({ columns: [t.itemId, t.phaseIdx, t.idx] }),
602
608
  foreignKey({
603
609
  columns: [t.itemId, t.phaseIdx],
604
610
  foreignColumns: [planPhases.itemId, planPhases.idx]
605
611
  }).onDelete("cascade")
606
612
  ]
607
613
  );
608
- var metadata = pgTable8("metadata", {
609
- key: text7().primaryKey(),
610
- value: text7().notNull()
614
+ var metadata = pgTable9("metadata", {
615
+ key: text8().primaryKey(),
616
+ value: text8().notNull()
611
617
  });
612
- var feeds = pgTable8("feeds", {
613
- id: integer8().generatedByDefaultAsIdentity().primaryKey(),
614
- url: text7().notNull().unique()
618
+ var feeds = pgTable9("feeds", {
619
+ id: integer9().generatedByDefaultAsIdentity().primaryKey(),
620
+ url: text8().notNull().unique()
615
621
  });
616
622
  var schema = {
617
623
  items,
@@ -626,7 +632,8 @@ var schema = {
626
632
  handovers,
627
633
  usagePeaks,
628
634
  backups,
629
- phaseUsage
635
+ phaseUsage,
636
+ phaseUsageMessages
630
637
  };
631
638
 
632
639
  // src/commands/backlog/addFeed.ts
@@ -662,30 +669,27 @@ async function seedNewsFeeds(db) {
662
669
  import { and as and2, eq as eq2 } from "drizzle-orm";
663
670
 
664
671
  // src/shared/db/recordWindowPeak.ts
665
- import { and, eq, gt, sql } from "drizzle-orm";
672
+ import { sql } from "drizzle-orm";
673
+
674
+ // src/shared/db/applyReading.ts
675
+ import { and, eq, gt } from "drizzle-orm";
666
676
  var RESET_DROP_THRESHOLD = 1;
667
677
  var at = ({ window, resetsAt }, segment) => segment === void 0 ? and(eq(usagePeaks.window, window), eq(usagePeaks.resetsAt, resetsAt)) : and(
668
678
  eq(usagePeaks.window, window),
669
679
  eq(usagePeaks.resetsAt, resetsAt),
670
680
  eq(usagePeaks.segment, segment)
671
681
  );
672
- var lock = ({ tx, window, resetsAt }) => tx.execute(
673
- sql`SELECT pg_advisory_xact_lock(hashtextextended(${`${window}:${resetsAt}`}, 0))`
674
- );
675
- var load = (c) => c.tx.select().from(usagePeaks).where(at(c)).orderBy(usagePeaks.segment);
676
682
  var insertSegment = (c, segment, usedPercentage) => c.tx.insert(usagePeaks).values({
677
683
  window: c.window,
678
684
  resetsAt: c.resetsAt,
679
685
  segment,
680
686
  usedPercentage
681
687
  });
682
- async function collapseToReached(c, reached, usedPercentage) {
683
- await c.tx.delete(usagePeaks).where(and(at(c), gt(usagePeaks.segment, reached.segment)));
684
- await c.tx.update(usagePeaks).set({ usedPercentage, resetDetected: false }).where(at(c, reached.segment));
685
- }
686
- async function openReset(c, active, usedPercentage) {
687
- await c.tx.update(usagePeaks).set({ resetDetected: true }).where(at(c, active.segment));
688
- await insertSegment(c, active.segment + 1, usedPercentage);
688
+ var load = (c) => c.tx.select().from(usagePeaks).where(at(c)).orderBy(usagePeaks.segment);
689
+ async function applyReading(c, usedPercentage) {
690
+ const segments = await load(c);
691
+ if (segments.length === 0) await insertSegment(c, 0, usedPercentage);
692
+ else await reconcile(c, segments, usedPercentage);
689
693
  }
690
694
  async function reconcile(c, segments, usedPercentage) {
691
695
  const reached = segments.find((s) => usedPercentage >= s.usedPercentage);
@@ -698,13 +702,24 @@ async function reconcile(c, segments, usedPercentage) {
698
702
  await openReset(c, active, usedPercentage);
699
703
  }
700
704
  }
705
+ async function collapseToReached(c, reached, usedPercentage) {
706
+ await c.tx.delete(usagePeaks).where(and(at(c), gt(usagePeaks.segment, reached.segment)));
707
+ await c.tx.update(usagePeaks).set({ usedPercentage, resetDetected: false }).where(at(c, reached.segment));
708
+ }
709
+ async function openReset(c, active, usedPercentage) {
710
+ await c.tx.update(usagePeaks).set({ resetDetected: true }).where(at(c, active.segment));
711
+ await insertSegment(c, active.segment + 1, usedPercentage);
712
+ }
713
+
714
+ // src/shared/db/recordWindowPeak.ts
715
+ var lock = ({ tx, window, resetsAt }) => tx.execute(
716
+ sql`SELECT pg_advisory_xact_lock(hashtextextended(${`${window}:${resetsAt}`}, 0))`
717
+ );
701
718
  async function recordWindowPeak(db, window, resetsAt, usedPercentage) {
702
719
  await db.transaction(async (tx) => {
703
720
  const c = { tx, window, resetsAt };
704
721
  await lock(c);
705
- const segments = await load(c);
706
- if (segments.length === 0) await insertSegment(c, 0, usedPercentage);
707
- else await reconcile(c, segments, usedPercentage);
722
+ await applyReading(c, usedPercentage);
708
723
  });
709
724
  }
710
725
 
@@ -859,6 +874,8 @@ var SCHEMA = `
859
874
  segment INTEGER NOT NULL DEFAULT 0,
860
875
  used_percentage DOUBLE PRECISION NOT NULL,
861
876
  reset_detected BOOLEAN NOT NULL DEFAULT false,
877
+ tokens_up BIGINT NOT NULL DEFAULT 0,
878
+ tokens_down BIGINT NOT NULL DEFAULT 0,
862
879
  created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
863
880
  PRIMARY KEY ("window", resets_at, segment)
864
881
  );
@@ -867,6 +884,8 @@ var SCHEMA = `
867
884
  ALTER TABLE usage_peaks ADD COLUMN IF NOT EXISTS reset_detected BOOLEAN NOT NULL DEFAULT false;
868
885
  ALTER TABLE usage_peaks ADD COLUMN IF NOT EXISTS segment INTEGER NOT NULL DEFAULT 0;
869
886
  ALTER TABLE usage_peaks ADD COLUMN IF NOT EXISTS created_at TIMESTAMPTZ NOT NULL DEFAULT now();
887
+ ALTER TABLE usage_peaks ADD COLUMN IF NOT EXISTS tokens_up BIGINT NOT NULL DEFAULT 0;
888
+ ALTER TABLE usage_peaks ADD COLUMN IF NOT EXISTS tokens_down BIGINT NOT NULL DEFAULT 0;
870
889
 
871
890
  -- Widen the primary key to include segment so a cycle can hold both its
872
891
  -- pre-reset peak and its post-reset continuation. Idempotent: only fires
@@ -907,6 +926,16 @@ var SCHEMA = `
907
926
  PRIMARY KEY (item_id, phase_idx)
908
927
  );
909
928
 
929
+ ALTER TABLE phase_usage ADD COLUMN IF NOT EXISTS last_total_in BIGINT;
930
+ ALTER TABLE phase_usage ADD COLUMN IF NOT EXISTS last_total_out BIGINT;
931
+
932
+ CREATE TABLE IF NOT EXISTS phase_usage_messages (
933
+ item_id INTEGER NOT NULL REFERENCES items(id) ON DELETE CASCADE,
934
+ phase_idx INTEGER NOT NULL,
935
+ message_id TEXT NOT NULL,
936
+ PRIMARY KEY (item_id, phase_idx, message_id)
937
+ );
938
+
910
939
  -- Git activity (branch/commit/PR) snapshotted for an item during a run. The
911
940
  -- (item_id, kind, ref) primary key makes re-snapshotting the same ref a no-op
912
941
  -- upsert rather than a duplicate row.
@@ -973,7 +1002,7 @@ function createPool() {
973
1002
  return pool;
974
1003
  }
975
1004
  async function initSchema(pool) {
976
- await ensureSchema((sql11) => pool.query(sql11));
1005
+ await ensureSchema((sql13) => pool.query(sql13));
977
1006
  const orm = makeOrmFromPool(pool);
978
1007
  await seedNewsFeeds(orm);
979
1008
  await runUsagePeakCleanup(orm);
@@ -2564,8 +2593,8 @@ function isHashCommentFile(filePath) {
2564
2593
  import fs13 from "fs";
2565
2594
 
2566
2595
  // src/commands/verify/blockComments/collectHashComments.ts
2567
- function blankNonNewline(text9) {
2568
- return text9.replace(/[^\n]/g, " ");
2596
+ function blankNonNewline(text10) {
2597
+ return text10.replace(/[^\n]/g, " ");
2569
2598
  }
2570
2599
  function isHeaderLine(line) {
2571
2600
  const trimmed = line.trim();
@@ -2642,23 +2671,23 @@ var MACHINE_DIRECTIVES = [
2642
2671
  "@vitest-environment",
2643
2672
  MAINTAINABILITY_OVERRIDE_MARKER
2644
2673
  ];
2645
- function isCommentExempt(text9) {
2646
- const lower = text9.toLowerCase();
2674
+ function isCommentExempt(text10) {
2675
+ const lower = text10.toLowerCase();
2647
2676
  return MACHINE_DIRECTIVES.some((d) => lower.includes(d));
2648
2677
  }
2649
2678
 
2650
2679
  // src/commands/verify/blockComments/collectSourceFindings.ts
2651
- function toSingleLine(text9) {
2652
- return text9.replace(/\s+/g, " ").trim();
2680
+ function toSingleLine(text10) {
2681
+ return text10.replace(/\s+/g, " ").trim();
2653
2682
  }
2654
2683
  function collectSourceFindings(file, lines, project) {
2655
2684
  const findings = [];
2656
2685
  const sourceFile = project.addSourceFileAtPath(file);
2657
- for (const { pos, text: text9 } of collectComments(sourceFile)) {
2686
+ for (const { pos, text: text10 } of collectComments(sourceFile)) {
2658
2687
  const { line } = sourceFile.getLineAndColumnAtPos(pos);
2659
2688
  if (!lines.has(line)) continue;
2660
- if (isCommentExempt(text9)) continue;
2661
- findings.push({ file, line, text: toSingleLine(text9) });
2689
+ if (isCommentExempt(text10)) continue;
2690
+ findings.push({ file, line, text: toSingleLine(text10) });
2662
2691
  }
2663
2692
  return findings;
2664
2693
  }
@@ -2678,10 +2707,10 @@ function collectYamlComments(content) {
2678
2707
  // src/commands/verify/blockComments/collectFileComments.ts
2679
2708
  function toFindings(file, lines, raw, exempt) {
2680
2709
  const findings = [];
2681
- for (const { line, text: text9 } of raw) {
2710
+ for (const { line, text: text10 } of raw) {
2682
2711
  if (!lines.has(line)) continue;
2683
- if (exempt && isCommentExempt(text9)) continue;
2684
- findings.push({ file, line, text: text9.replace(/\s+/g, " ").trim() });
2712
+ if (exempt && isCommentExempt(text10)) continue;
2713
+ findings.push({ file, line, text: text10.replace(/\s+/g, " ").trim() });
2685
2714
  }
2686
2715
  return findings;
2687
2716
  }
@@ -2779,8 +2808,8 @@ function blockComments() {
2779
2808
  process.exit(0);
2780
2809
  }
2781
2810
  console.log("Comments on changed lines:\n");
2782
- for (const { file, line, text: text9 } of findings) {
2783
- console.log(`${file}:${line} \u2192 ${text9}`);
2811
+ for (const { file, line, text: text10 } of findings) {
2812
+ console.log(`${file}:${line} \u2192 ${text10}`);
2784
2813
  }
2785
2814
  console.log(`
2786
2815
  Total: ${findings.length} comment(s)`);
@@ -4053,8 +4082,8 @@ async function buildDump(tables, copyOut) {
4053
4082
  // src/commands/backlog/dump/copyTableOut.ts
4054
4083
  import { to as copyTo } from "pg-copy-streams";
4055
4084
  async function copyTableOut(client, table) {
4056
- const sql11 = `COPY ${table.name} (${table.columns.join(", ")}) TO STDOUT`;
4057
- const stream = client.query(copyTo(sql11));
4085
+ const sql13 = `COPY ${table.name} (${table.columns.join(", ")}) TO STDOUT`;
4086
+ const stream = client.query(copyTo(sql13));
4058
4087
  const chunks = [];
4059
4088
  for await (const chunk of stream) {
4060
4089
  chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
@@ -4596,11 +4625,11 @@ import { promisify } from "util";
4596
4625
  // src/shared/withSpinner.ts
4597
4626
  var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
4598
4627
  var TICK_MS = 80;
4599
- async function withSpinner(text9, task) {
4628
+ async function withSpinner(text10, task) {
4600
4629
  if (!process.stderr.isTTY) return task();
4601
4630
  let frame = 0;
4602
4631
  const render = () => {
4603
- process.stderr.write(`\r${SPINNER_FRAMES[frame]} ${text9}`);
4632
+ process.stderr.write(`\r${SPINNER_FRAMES[frame]} ${text10}`);
4604
4633
  };
4605
4634
  render();
4606
4635
  const timer = setInterval(() => {
@@ -4937,6 +4966,18 @@ function attachSubtasks(item, rel, id) {
4937
4966
  if (subtasks.length > 0) item.subtasks = subtasks;
4938
4967
  }
4939
4968
 
4969
+ // src/commands/backlog/sumPhaseUsage.ts
4970
+ function sumPhaseUsage(usage) {
4971
+ return usage.reduce(
4972
+ (total, u) => ({
4973
+ tokensUp: total.tokensUp + u.tokensUp,
4974
+ tokensDown: total.tokensDown + u.tokensDown,
4975
+ activeMs: total.activeMs + u.activeMs
4976
+ }),
4977
+ { tokensUp: 0, tokensDown: 0, activeMs: 0 }
4978
+ );
4979
+ }
4980
+
4940
4981
  // src/commands/backlog/attachUsage.ts
4941
4982
  function rowToUsage(u) {
4942
4983
  return {
@@ -4948,7 +4989,10 @@ function rowToUsage(u) {
4948
4989
  }
4949
4990
  function attachUsage(item, rel, id) {
4950
4991
  const usage = (rel.usage.get(id) ?? []).map(rowToUsage);
4951
- if (usage.length > 0) item.phaseUsage = usage;
4992
+ if (usage.length > 0) {
4993
+ item.phaseUsage = usage;
4994
+ item.usageTotal = sumPhaseUsage(usage);
4995
+ }
4952
4996
  }
4953
4997
 
4954
4998
  // src/commands/backlog/buildPlan.ts
@@ -5062,6 +5106,11 @@ var phaseUsageSchema = z3.strictObject({
5062
5106
  tokensDown: z3.number(),
5063
5107
  activeMs: z3.number()
5064
5108
  });
5109
+ var phaseUsageTotalSchema = z3.strictObject({
5110
+ tokensUp: z3.number(),
5111
+ tokensDown: z3.number(),
5112
+ activeMs: z3.number()
5113
+ });
5065
5114
  var subtaskStatusSchema = z3.enum(["todo", "in-progress", "done"]);
5066
5115
  var subtaskSchema = z3.strictObject({
5067
5116
  title: z3.string(),
@@ -5104,6 +5153,7 @@ var backlogItemSchema = z3.strictObject({
5104
5153
  subtasks: z3.array(subtaskSchema).optional(),
5105
5154
  links: z3.array(backlogLinkSchema).optional(),
5106
5155
  phaseUsage: z3.array(phaseUsageSchema).optional(),
5156
+ usageTotal: phaseUsageTotalSchema.optional(),
5107
5157
  gitRefs: z3.array(gitRefSchema).optional(),
5108
5158
  origin: z3.string().optional(),
5109
5159
  jiraKey: z3.string().optional()
@@ -6014,11 +6064,11 @@ import chalk45 from "chalk";
6014
6064
 
6015
6065
  // src/commands/backlog/appendComment.ts
6016
6066
  import { sql as sql4 } from "drizzle-orm";
6017
- async function appendComment(orm, itemId2, text9, opts = {}) {
6067
+ async function appendComment(orm, itemId2, text10, opts = {}) {
6018
6068
  await orm.insert(comments).values({
6019
6069
  itemId: itemId2,
6020
6070
  idx: sql4`(SELECT COALESCE(MAX(${comments.idx}) + 1, 0) FROM ${comments} WHERE ${comments.itemId} = ${itemId2})`,
6021
- text: text9,
6071
+ text: text10,
6022
6072
  phase: opts.phase ?? null,
6023
6073
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
6024
6074
  type: opts.type ?? "comment"
@@ -6121,7 +6171,7 @@ function formatComment(entry) {
6121
6171
  // src/shared/renderMarkdownTerminal.ts
6122
6172
  import { Marked } from "marked";
6123
6173
  import { markedTerminal } from "marked-terminal";
6124
- function renderMarkdownTerminal(text9) {
6174
+ function renderMarkdownTerminal(text10) {
6125
6175
  const width = process.stdout.columns || 80;
6126
6176
  const marked = new Marked(markedTerminal({ width, reflowText: true }));
6127
6177
  marked.use({
@@ -6134,7 +6184,7 @@ function renderMarkdownTerminal(text9) {
6134
6184
  }
6135
6185
  }
6136
6186
  });
6137
- return marked.parse(text9).trimEnd();
6187
+ return marked.parse(text10).trimEnd();
6138
6188
  }
6139
6189
 
6140
6190
  // src/commands/backlog/show/printActivity.ts
@@ -6154,9 +6204,9 @@ function groupActivityRefs(refs, commitLimit = ACTIVITY_COMMIT_LIMIT) {
6154
6204
  }
6155
6205
 
6156
6206
  // src/commands/backlog/show/printActivity.ts
6157
- function printRef(label2, text9, ref) {
6207
+ function printRef(label2, text10, ref) {
6158
6208
  const url = ref.url ? ` ${chalk48.dim(ref.url)}` : "";
6159
- console.log(` ${chalk48.cyan(label2)} ${text9}${url}`);
6209
+ console.log(` ${chalk48.cyan(label2)} ${text10}${url}`);
6160
6210
  }
6161
6211
  function printActivity(item) {
6162
6212
  const { branches, commits: commits2, prs: prs2, hiddenCommits } = groupActivityRefs(
@@ -6605,12 +6655,57 @@ function createFallbackHandler(routes3, htmlHandler2, extra) {
6605
6655
  import { eq as eq12 } from "drizzle-orm";
6606
6656
 
6607
6657
  // src/commands/backlog/loadItemSummaries.ts
6608
- import { asc as asc5, eq as eq11, ne, sql as sql5 } from "drizzle-orm";
6609
- async function loadItemSummaries(orm, origin) {
6610
- const incompleteCounts = orm.select({
6658
+ import { asc as asc5, eq as eq11 } from "drizzle-orm";
6659
+
6660
+ // src/commands/backlog/incompleteSubtaskCounts.ts
6661
+ import { ne, sql as sql5 } from "drizzle-orm";
6662
+ function incompleteSubtaskCounts(orm) {
6663
+ return orm.select({
6611
6664
  itemId: itemSubtasks.itemId,
6612
6665
  count: sql5`count(*)::int`.as("incomplete_subtasks")
6613
6666
  }).from(itemSubtasks).where(ne(itemSubtasks.status, "done")).groupBy(itemSubtasks.itemId).as("incomplete_counts");
6667
+ }
6668
+
6669
+ // src/commands/backlog/phaseUsageTotals.ts
6670
+ import { sql as sql6 } from "drizzle-orm";
6671
+ function phaseUsageTotals(orm) {
6672
+ return orm.select({
6673
+ itemId: phaseUsage.itemId,
6674
+ tokensUp: sql6`sum(${phaseUsage.tokensUp})::bigint`.as(
6675
+ "total_tokens_up"
6676
+ ),
6677
+ tokensDown: sql6`sum(${phaseUsage.tokensDown})::bigint`.as(
6678
+ "total_tokens_down"
6679
+ ),
6680
+ activeMs: sql6`sum(${phaseUsage.activeMs})::bigint`.as(
6681
+ "total_active_ms"
6682
+ )
6683
+ }).from(phaseUsage).groupBy(phaseUsage.itemId).as("usage_totals");
6684
+ }
6685
+
6686
+ // src/commands/backlog/rowToItemSummary.ts
6687
+ function rowToItemSummary(row) {
6688
+ return {
6689
+ id: row.id,
6690
+ origin: row.origin,
6691
+ type: row.type,
6692
+ name: row.name,
6693
+ status: row.status,
6694
+ starred: row.starred,
6695
+ jiraKey: row.jiraKey ?? void 0,
6696
+ incompleteSubtasks: row.incompleteSubtasks ?? 0,
6697
+ usageTotal: row.tokensUp == null ? void 0 : {
6698
+ tokensUp: Number(row.tokensUp),
6699
+ tokensDown: Number(row.tokensDown),
6700
+ activeMs: Number(row.activeMs)
6701
+ }
6702
+ };
6703
+ }
6704
+
6705
+ // src/commands/backlog/loadItemSummaries.ts
6706
+ async function loadItemSummaries(orm, origin) {
6707
+ const incompleteCounts = incompleteSubtaskCounts(orm);
6708
+ const usageTotals = phaseUsageTotals(orm);
6614
6709
  const rows = await orm.select({
6615
6710
  id: items.id,
6616
6711
  origin: items.origin,
@@ -6619,18 +6714,12 @@ async function loadItemSummaries(orm, origin) {
6619
6714
  status: items.status,
6620
6715
  starred: items.starred,
6621
6716
  jiraKey: items.jiraKey,
6622
- incompleteSubtasks: incompleteCounts.count
6623
- }).from(items).leftJoin(incompleteCounts, eq11(incompleteCounts.itemId, items.id)).where(origin === void 0 ? void 0 : eq11(items.origin, origin)).orderBy(asc5(items.id));
6624
- return rows.map((row) => ({
6625
- id: row.id,
6626
- origin: row.origin,
6627
- type: row.type,
6628
- name: row.name,
6629
- status: row.status,
6630
- starred: row.starred,
6631
- jiraKey: row.jiraKey ?? void 0,
6632
- incompleteSubtasks: row.incompleteSubtasks ?? 0
6633
- }));
6717
+ incompleteSubtasks: incompleteCounts.count,
6718
+ tokensUp: usageTotals.tokensUp,
6719
+ tokensDown: usageTotals.tokensDown,
6720
+ activeMs: usageTotals.activeMs
6721
+ }).from(items).leftJoin(incompleteCounts, eq11(incompleteCounts.itemId, items.id)).leftJoin(usageTotals, eq11(usageTotals.itemId, items.id)).where(origin === void 0 ? void 0 : eq11(items.origin, origin)).orderBy(asc5(items.id));
6722
+ return rows.map(rowToItemSummary);
6634
6723
  }
6635
6724
 
6636
6725
  // src/commands/backlog/loadBacklogSummaries.ts
@@ -7258,9 +7347,9 @@ function excerpt(xml, ...tags) {
7258
7347
  for (const tag of tags) {
7259
7348
  const raw = extractText(xml, tag);
7260
7349
  if (!raw) continue;
7261
- const text9 = stripHtml(raw);
7262
- if (text9.length <= MAX_EXCERPT) return text9;
7263
- return `${text9.slice(0, MAX_EXCERPT)}\u2026`;
7350
+ const text10 = stripHtml(raw);
7351
+ if (text10.length <= MAX_EXCERPT) return text10;
7352
+ return `${text10.slice(0, MAX_EXCERPT)}\u2026`;
7264
7353
  }
7265
7354
  return "";
7266
7355
  }
@@ -8173,10 +8262,10 @@ function registerAssociateJiraCommand(cmd) {
8173
8262
 
8174
8263
  // src/commands/backlog/comment/index.ts
8175
8264
  import chalk63 from "chalk";
8176
- async function comment(id, text9) {
8265
+ async function comment(id, text10) {
8177
8266
  const found = await findOneItem(id);
8178
8267
  if (!found) process.exit(1);
8179
- await appendComment(found.orm, found.item.id, text9);
8268
+ await appendComment(found.orm, found.item.id, text10);
8180
8269
  console.log(
8181
8270
  chalk63.green(`Comment added to item ${formatItemId(found.item.id)}.`)
8182
8271
  );
@@ -8281,9 +8370,9 @@ function readLine(dump, start3) {
8281
8370
  return { text: dump.subarray(start3, eol).toString("utf8"), next: eol + 1 };
8282
8371
  }
8283
8372
  function parseHeader(dump) {
8284
- const { text: text9, next: next3 } = readLine(dump, 0);
8373
+ const { text: text10, next: next3 } = readLine(dump, 0);
8285
8374
  try {
8286
- return { header: JSON.parse(text9), bodyStart: next3 };
8375
+ return { header: JSON.parse(text10), bodyStart: next3 };
8287
8376
  } catch {
8288
8377
  return invalid("header is not valid JSON.");
8289
8378
  }
@@ -8292,9 +8381,9 @@ function parseSections(dump, bodyStart) {
8292
8381
  const sections = /* @__PURE__ */ new Map();
8293
8382
  let cursor = bodyStart;
8294
8383
  while (cursor < dump.length) {
8295
- const { text: text9, next: next3 } = readLine(dump, cursor);
8296
- const match = text9.match(/^@table (\S+) (\d+)$/);
8297
- if (!match) invalid(`malformed table marker "${text9}".`);
8384
+ const { text: text10, next: next3 } = readLine(dump, cursor);
8385
+ const match = text10.match(/^@table (\S+) (\d+)$/);
8386
+ if (!match) invalid(`malformed table marker "${text10}".`);
8298
8387
  const [, name, bytes] = match;
8299
8388
  const end = next3 + Number(bytes);
8300
8389
  if (end > dump.length) invalid(`section "${name}" overruns the dump.`);
@@ -8370,8 +8459,8 @@ async function readStdinBuffer() {
8370
8459
  import { finished } from "stream/promises";
8371
8460
  import { from as copyFrom } from "pg-copy-streams";
8372
8461
  async function copyTableIn(client, table, data) {
8373
- const sql11 = `COPY ${table.name} (${table.columns.join(", ")}) FROM STDIN`;
8374
- const stream = client.query(copyFrom(sql11));
8462
+ const sql13 = `COPY ${table.name} (${table.columns.join(", ")}) FROM STDIN`;
8463
+ const stream = client.query(copyFrom(sql13));
8375
8464
  stream.end(data);
8376
8465
  await finished(stream);
8377
8466
  }
@@ -8416,8 +8505,8 @@ async function replaceData(client, parsed) {
8416
8505
  await copyTableIn(client, table, data);
8417
8506
  }
8418
8507
  for (const col of await introspectIdentityColumns(client)) {
8419
- const { text: text9, values } = resyncIdentitySql(col);
8420
- await client.query(text9, values);
8508
+ const { text: text10, values } = resyncIdentitySql(col);
8509
+ await client.query(text10, values);
8421
8510
  }
8422
8511
  }
8423
8512
  async function restore(client, parsed) {
@@ -8498,11 +8587,11 @@ async function insertPhaseAt(orm, itemId2, phaseIdx, name, tasks, manualChecks,
8498
8587
  }
8499
8588
 
8500
8589
  // src/commands/backlog/insertSubtask.ts
8501
- import { sql as sql6 } from "drizzle-orm";
8590
+ import { sql as sql7 } from "drizzle-orm";
8502
8591
  async function insertSubtask(orm, itemId2, title, description) {
8503
8592
  await orm.insert(itemSubtasks).values({
8504
8593
  itemId: itemId2,
8505
- idx: sql6`(SELECT COALESCE(MAX(${itemSubtasks.idx}) + 1, 0) FROM ${itemSubtasks} WHERE ${itemSubtasks.itemId} = ${itemId2})`,
8594
+ idx: sql7`(SELECT COALESCE(MAX(${itemSubtasks.idx}) + 1, 0) FROM ${itemSubtasks} WHERE ${itemSubtasks.itemId} = ${itemId2})`,
8506
8595
  title,
8507
8596
  description: description ?? null,
8508
8597
  status: "todo"
@@ -10264,8 +10353,8 @@ var RAW_BUILTIN_DENIES = [
10264
10353
  regex
10265
10354
  }))
10266
10355
  ];
10267
- function matchBuiltinDeny(text9) {
10268
- return RAW_BUILTIN_DENIES.find((rule) => rule.regex.test(text9));
10356
+ function matchBuiltinDeny(text10) {
10357
+ return RAW_BUILTIN_DENIES.find((rule) => rule.regex.test(text10));
10269
10358
  }
10270
10359
  function toDecision(rule) {
10271
10360
  if (!rule) return void 0;
@@ -11005,8 +11094,8 @@ function formatHuman(cli, commands) {
11005
11094
  `];
11006
11095
  for (const cmd of sorted) {
11007
11096
  const full = `${cli} ${cmd.path.join(" ")}`;
11008
- const text9 = cmd.description ? `${full} \u2014 ${cmd.description}` : full;
11009
- lines.push(`${prefix(classifyVerb(cmd.path))}${text9}`);
11097
+ const text10 = cmd.description ? `${full} \u2014 ${cmd.description}` : full;
11098
+ lines.push(`${prefix(classifyVerb(cmd.path))}${text10}`);
11010
11099
  }
11011
11100
  return lines.join("\n");
11012
11101
  }
@@ -11285,38 +11374,38 @@ import chalk108 from "chalk";
11285
11374
  // src/commands/codeComment/validateCommentText.ts
11286
11375
  var MAX_COMMENT_LENGTH = 50;
11287
11376
  function validateCommentText(raw, isHash = false) {
11288
- const text9 = isHash ? raw.replace(/^#\s?/, "") : raw.replace(/^\/\/\s?/, "");
11289
- if (/[\r\n]/.test(text9)) {
11377
+ const text10 = isHash ? raw.replace(/^#\s?/, "") : raw.replace(/^\/\/\s?/, "");
11378
+ if (/[\r\n]/.test(text10)) {
11290
11379
  return {
11291
11380
  ok: false,
11292
11381
  reason: "Comment must be a single line \u2014 multi-line comments are not allowed."
11293
11382
  };
11294
11383
  }
11295
- if (!isHash && (text9.includes("/*") || text9.includes("*/"))) {
11384
+ if (!isHash && (text10.includes("/*") || text10.includes("*/"))) {
11296
11385
  return {
11297
11386
  ok: false,
11298
11387
  reason: "Block comments (/* */) are not allowed \u2014 only single-line // comments."
11299
11388
  };
11300
11389
  }
11301
- if (text9.length > MAX_COMMENT_LENGTH) {
11390
+ if (text10.length > MAX_COMMENT_LENGTH) {
11302
11391
  return {
11303
11392
  ok: false,
11304
- reason: `Comment text is ${text9.length} chars; the cap is ${MAX_COMMENT_LENGTH}. Make it shorter or, better, make the code self-documenting.`
11393
+ reason: `Comment text is ${text10.length} chars; the cap is ${MAX_COMMENT_LENGTH}. Make it shorter or, better, make the code self-documenting.`
11305
11394
  };
11306
11395
  }
11307
- return { ok: true, text: text9 };
11396
+ return { ok: true, text: text10 };
11308
11397
  }
11309
11398
 
11310
11399
  // src/commands/codeComment/issuePin.ts
11311
11400
  import { mkdirSync as mkdirSync13, writeFileSync as writeFileSync24 } from "fs";
11312
11401
  import { randomInt } from "crypto";
11313
- function issuePin(file, lineNumber, text9) {
11402
+ function issuePin(file, lineNumber, text10) {
11314
11403
  const pin = generatePin();
11315
11404
  mkdirSync13(getRestrictedDir(), { recursive: true });
11316
11405
  sweepRestrictedDir();
11317
11406
  writeFileSync24(
11318
11407
  getPinStatePath(pin),
11319
- JSON.stringify({ pin, file, line: lineNumber, text: text9 })
11408
+ JSON.stringify({ pin, file, line: lineNumber, text: text10 })
11320
11409
  );
11321
11410
  return showNotification({
11322
11411
  title: "assist code-comment pin",
@@ -11328,7 +11417,7 @@ function generatePin() {
11328
11417
  }
11329
11418
 
11330
11419
  // src/commands/codeComment/codeCommentSet.ts
11331
- function codeCommentSet(file, line, text9) {
11420
+ function codeCommentSet(file, line, text10) {
11332
11421
  const lineNumber = Number.parseInt(line, 10);
11333
11422
  if (!Number.isInteger(lineNumber) || lineNumber < 1) {
11334
11423
  console.error(chalk108.red(`Invalid line number: ${line}`));
@@ -11337,7 +11426,7 @@ function codeCommentSet(file, line, text9) {
11337
11426
  }
11338
11427
  const hash = isHashCommentFile(file);
11339
11428
  const marker = hash ? "#" : "//";
11340
- const validation = validateCommentText(text9, hash);
11429
+ const validation = validateCommentText(text10, hash);
11341
11430
  if (!validation.ok) {
11342
11431
  console.error(chalk108.red(`Refused: ${validation.reason}`));
11343
11432
  console.error(chalk108.red("No pin issued."));
@@ -13344,12 +13433,12 @@ function isSourceFile(filePath) {
13344
13433
  if (!filePath) return false;
13345
13434
  return SOURCE_EXTENSIONS.some((ext) => filePath.endsWith(ext));
13346
13435
  }
13347
- function blankNonNewline2(text9) {
13348
- return text9.replace(/[^\n]/g, " ");
13436
+ function blankNonNewline2(text10) {
13437
+ return text10.replace(/[^\n]/g, " ");
13349
13438
  }
13350
- function extractComments(text9) {
13439
+ function extractComments(text10) {
13351
13440
  const comments3 = [];
13352
- let work = text9.replace(
13441
+ let work = text10.replace(
13353
13442
  /"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|`(?:[^`\\]|\\.)*`/g,
13354
13443
  blankNonNewline2
13355
13444
  );
@@ -13364,11 +13453,11 @@ function extractComments(text9) {
13364
13453
  }
13365
13454
 
13366
13455
  // src/commands/editHook/extractYamlComments.ts
13367
- function blankNonNewline3(text9) {
13368
- return text9.replace(/[^\n]/g, " ");
13456
+ function blankNonNewline3(text10) {
13457
+ return text10.replace(/[^\n]/g, " ");
13369
13458
  }
13370
- function extractYamlComments(text9) {
13371
- const work = text9.replace(
13459
+ function extractYamlComments(text10) {
13460
+ const work = text10.replace(
13372
13461
  /"(?:[^"\\]|\\.)*"|'(?:[^']|'')*'/g,
13373
13462
  blankNonNewline3
13374
13463
  );
@@ -13384,8 +13473,8 @@ function isHeaderLine2(line) {
13384
13473
  const trimmed = line.trim();
13385
13474
  return trimmed === "" || trimmed.startsWith("#");
13386
13475
  }
13387
- function extractShellComments(text9) {
13388
- const lines = text9.split("\n");
13476
+ function extractShellComments(text10) {
13477
+ const lines = text10.split("\n");
13389
13478
  let firstCodeLine = 0;
13390
13479
  while (firstCodeLine < lines.length && isHeaderLine2(lines[firstCodeLine])) {
13391
13480
  firstCodeLine++;
@@ -13755,9 +13844,9 @@ function registerGithub(program2) {
13755
13844
  }
13756
13845
 
13757
13846
  // src/commands/handover/countPendingHandovers.ts
13758
- import { and as and16, eq as eq34, isNull, sql as sql7 } from "drizzle-orm";
13847
+ import { and as and16, eq as eq34, isNull, sql as sql8 } from "drizzle-orm";
13759
13848
  async function countPendingHandovers(orm, origin) {
13760
- const [row] = await orm.select({ count: sql7`count(*)::int` }).from(handovers).where(and16(eq34(handovers.origin, origin), isNull(handovers.recalledAt)));
13849
+ const [row] = await orm.select({ count: sql8`count(*)::int` }).from(handovers).where(and16(eq34(handovers.origin, origin), isNull(handovers.recalledAt)));
13761
13850
  return row?.count ?? 0;
13762
13851
  }
13763
13852
 
@@ -13980,9 +14069,9 @@ import chalk140 from "chalk";
13980
14069
 
13981
14070
  // src/commands/jira/adfToText.ts
13982
14071
  function renderInline(node) {
13983
- const text9 = node.text ?? "";
13984
- if (node.marks?.some((m) => m.type === "code")) return `\`${text9}\``;
13985
- return text9;
14072
+ const text10 = node.text ?? "";
14073
+ if (node.marks?.some((m) => m.type === "code")) return `\`${text10}\``;
14074
+ return text10;
13986
14075
  }
13987
14076
  function renderChildren(node, indent2) {
13988
14077
  return renderNodes(node.content ?? [], indent2);
@@ -14024,8 +14113,8 @@ function isListNode(node) {
14024
14113
  function renderListChild(child, indent2, pad, marker, isFirst) {
14025
14114
  if (isListNode(child)) return renderNodes([child], indent2 + 1);
14026
14115
  if (child.type !== "paragraph") return renderNode(child, indent2);
14027
- const text9 = renderChildren(child, indent2);
14028
- return isFirst ? `${pad}${marker} ${text9}` : `${pad} ${text9}`;
14116
+ const text10 = renderChildren(child, indent2);
14117
+ return isFirst ? `${pad}${marker} ${text10}` : `${pad} ${text10}`;
14029
14118
  }
14030
14119
  function renderListItem(node, indent2, marker) {
14031
14120
  const pad = " ".repeat(indent2);
@@ -14184,9 +14273,9 @@ function registerDescriptionLaunch(program2, name, slashCommand, description, al
14184
14273
  "[description]",
14185
14274
  `Text to forward to the /${slashCommand} slash command`
14186
14275
  ).description(description).option("--once", "Exit when the initial task completes").option("--resume-session <id>", RESUME_SESSION_FLAG).action(
14187
- (text9, opts) => launchMode(slashCommand, {
14276
+ (text10, opts) => launchMode(slashCommand, {
14188
14277
  once: opts.once,
14189
- description: text9,
14278
+ description: text10,
14190
14279
  resumeSessionId: opts.resumeSession
14191
14280
  })
14192
14281
  );
@@ -14688,8 +14777,8 @@ function dedupeLinks(links2) {
14688
14777
  }
14689
14778
 
14690
14779
  // src/commands/netcap/linkifyMentions.ts
14691
- function linkifyMentions(text9, mentions) {
14692
- let out = text9;
14780
+ function linkifyMentions(text10, mentions) {
14781
+ let out = text10;
14693
14782
  for (const m of mentions) {
14694
14783
  if (m.name && out.includes(m.name)) {
14695
14784
  out = out.replace(m.name, `[${m.name}](${m.url})`);
@@ -14716,13 +14805,13 @@ function resolveAuthor(mentionMap, author) {
14716
14805
  return mentionMap.get(author) ?? { slug: author, url: profileUrl(author) };
14717
14806
  }
14718
14807
  function buildPost(raw, mentionMap, author) {
14719
- const text9 = joinText(raw.text);
14720
- if (!text9) return void 0;
14808
+ const text10 = joinText(raw.text);
14809
+ if (!text10) return void 0;
14721
14810
  const mentions = resolveMentions(raw, mentionMap, author);
14722
14811
  const relatedPosts = [...new Set(raw.related)];
14723
14812
  return {
14724
- text: text9,
14725
- markdown: linkifyMentions(text9, mentions),
14813
+ text: text10,
14814
+ markdown: linkifyMentions(text10, mentions),
14726
14815
  mentions,
14727
14816
  hashtags: [...new Set(raw.hashtags)],
14728
14817
  links: dedupeLinks(raw.links),
@@ -14863,8 +14952,8 @@ function activityUrnOf(update3) {
14863
14952
  return typeof urn === "string" ? urn : void 0;
14864
14953
  }
14865
14954
  function buildVoyagerPost(update3, profiles) {
14866
- const text9 = commentaryText(update3);
14867
- if (!text9) return void 0;
14955
+ const text10 = commentaryText(update3);
14956
+ if (!text10) return void 0;
14868
14957
  const author = resolveVoyagerAuthor(update3);
14869
14958
  const { mentions, hashtags, links: links2 } = collectVoyagerAttributes(
14870
14959
  update3,
@@ -14873,8 +14962,8 @@ function buildVoyagerPost(update3, profiles) {
14873
14962
  );
14874
14963
  const activityUrn = activityUrnOf(update3);
14875
14964
  return {
14876
- text: text9,
14877
- markdown: linkifyMentions(text9, mentions),
14965
+ text: text10,
14966
+ markdown: linkifyMentions(text10, mentions),
14878
14967
  mentions,
14879
14968
  hashtags,
14880
14969
  links: links2,
@@ -15378,8 +15467,8 @@ function isWallOfText(lines) {
15378
15467
  if (lines.some(isListLine)) {
15379
15468
  return false;
15380
15469
  }
15381
- const text9 = lines.join(" ").trim();
15382
- return text9.length > MAX_PARAGRAPH_CHARS || countSentences(text9) > MAX_PARAGRAPH_SENTENCES;
15470
+ const text10 = lines.join(" ").trim();
15471
+ return text10.length > MAX_PARAGRAPH_CHARS || countSentences(text10) > MAX_PARAGRAPH_SENTENCES;
15383
15472
  }
15384
15473
  function validatePrContent(title, body) {
15385
15474
  if (title.toLowerCase().includes("claude")) {
@@ -17133,9 +17222,9 @@ function resolveImports(target, dependencies, sourceFile, statements = []) {
17133
17222
  function extractTexts(target, allFunctions, statements) {
17134
17223
  const stmtTexts = statements.map((v) => v.getFullText().trim());
17135
17224
  const fnTexts = allFunctions.map((fn) => {
17136
- const text9 = fn.getFullText().trim();
17137
- if (fn === target && !text9.startsWith("export ")) return `export ${text9}`;
17138
- return text9;
17225
+ const text10 = fn.getFullText().trim();
17226
+ if (fn === target && !text10.startsWith("export ")) return `export ${text10}`;
17227
+ return text10;
17139
17228
  });
17140
17229
  return [...stmtTexts, ...fnTexts];
17141
17230
  }
@@ -18985,9 +19074,9 @@ var MultiSpinner = class {
18985
19074
  elapsedPrefix: prefix2
18986
19075
  });
18987
19076
  }
18988
- failRemaining(text9) {
19077
+ failRemaining(text10) {
18989
19078
  for (const entry of this.entries) {
18990
- if (entry.state === "running") this.resolve(entry, "failed", text9);
19079
+ if (entry.state === "running") this.resolve(entry, "failed", text10);
18991
19080
  }
18992
19081
  }
18993
19082
  add(entry) {
@@ -19000,14 +19089,14 @@ var MultiSpinner = class {
19000
19089
  set text(value) {
19001
19090
  entry.text = value;
19002
19091
  },
19003
- succeed: (text9) => this.resolve(entry, "succeeded", text9),
19004
- fail: (text9) => this.resolve(entry, "failed", text9)
19092
+ succeed: (text10) => this.resolve(entry, "succeeded", text10),
19093
+ fail: (text10) => this.resolve(entry, "failed", text10)
19005
19094
  };
19006
19095
  }
19007
- resolve(entry, state, text9) {
19096
+ resolve(entry, state, text10) {
19008
19097
  if (entry.state !== "running") return;
19009
19098
  entry.state = state;
19010
- if (text9 !== void 0) entry.text = text9;
19099
+ if (text10 !== void 0) entry.text = text10;
19011
19100
  entry.elapsedStart = void 0;
19012
19101
  this.render();
19013
19102
  this.maybeFinish();
@@ -19082,12 +19171,12 @@ function skippedCodexResult(outputPath) {
19082
19171
  // src/commands/review/formatReviewerFailure.ts
19083
19172
  var FAST_FAIL_MS = 1e3;
19084
19173
  var STDOUT_TAIL_LINES = 20;
19085
- function indent(text9) {
19086
- return text9.split(/\r?\n/).map((line) => ` ${line}`);
19174
+ function indent(text10) {
19175
+ return text10.split(/\r?\n/).map((line) => ` ${line}`);
19087
19176
  }
19088
- function tailLines(text9, maxLines) {
19089
- const lines = text9.split(/\r?\n/);
19090
- return lines.length <= maxLines ? text9 : lines.slice(-maxLines).join("\n");
19177
+ function tailLines(text10, maxLines) {
19178
+ const lines = text10.split(/\r?\n/);
19179
+ return lines.length <= maxLines ? text10 : lines.slice(-maxLines).join("\n");
19091
19180
  }
19092
19181
  function isFastFail(input) {
19093
19182
  return input.exitCode !== 0 && input.elapsedMs !== void 0 && input.elapsedMs < FAST_FAIL_MS;
@@ -20022,8 +20111,8 @@ function filterToSql(filter) {
20022
20111
  // src/commands/seq/fetchSeqData.ts
20023
20112
  function buildDataParams(filter, count8, from, to) {
20024
20113
  const sqlFilter = filterToSql(filter);
20025
- const sql11 = `select @Timestamp, @Level, @Exception, @Message from stream where ${sqlFilter} order by @Timestamp desc limit ${count8}`;
20026
- const params = new URLSearchParams({ q: sql11 });
20114
+ const sql13 = `select @Timestamp, @Level, @Exception, @Message from stream where ${sqlFilter} order by @Timestamp desc limit ${count8}`;
20115
+ const params = new URLSearchParams({ q: sql13 });
20027
20116
  if (from) params.set("rangeStartUtc", from);
20028
20117
  if (to) params.set("rangeEndUtc", to);
20029
20118
  return params;
@@ -20284,26 +20373,26 @@ import chalk187 from "chalk";
20284
20373
  // src/commands/sql/loadConnections.ts
20285
20374
  function loadConnections3() {
20286
20375
  const raw = loadGlobalConfigRaw();
20287
- const sql11 = raw.sql;
20288
- return sql11?.connections ?? [];
20376
+ const sql13 = raw.sql;
20377
+ return sql13?.connections ?? [];
20289
20378
  }
20290
20379
  function saveConnections3(connections) {
20291
20380
  const raw = loadGlobalConfigRaw();
20292
- const sql11 = raw.sql ?? {};
20293
- sql11.connections = connections;
20294
- raw.sql = sql11;
20381
+ const sql13 = raw.sql ?? {};
20382
+ sql13.connections = connections;
20383
+ raw.sql = sql13;
20295
20384
  saveGlobalConfig(raw);
20296
20385
  }
20297
20386
  function getDefaultConnection2() {
20298
20387
  const raw = loadGlobalConfigRaw();
20299
- const sql11 = raw.sql;
20300
- return sql11?.defaultConnection;
20388
+ const sql13 = raw.sql;
20389
+ return sql13?.defaultConnection;
20301
20390
  }
20302
20391
  function setDefaultConnection2(name) {
20303
20392
  const raw = loadGlobalConfigRaw();
20304
- const sql11 = raw.sql ?? {};
20305
- sql11.defaultConnection = name;
20306
- raw.sql = sql11;
20393
+ const sql13 = raw.sql ?? {};
20394
+ sql13.defaultConnection = name;
20395
+ raw.sql = sql13;
20307
20396
  saveGlobalConfig(raw);
20308
20397
  }
20309
20398
 
@@ -20374,9 +20463,9 @@ function resolveConnection3(name) {
20374
20463
  }
20375
20464
 
20376
20465
  // src/commands/sql/sqlConnect.ts
20377
- import sql8 from "mssql";
20466
+ import sql9 from "mssql";
20378
20467
  async function sqlConnect(conn) {
20379
- return await sql8.connect({
20468
+ return await sql9.connect({
20380
20469
  server: conn.server,
20381
20470
  port: conn.port,
20382
20471
  user: conn.user,
@@ -20453,11 +20542,11 @@ var MUTATION_PATTERN = new RegExp(
20453
20542
  `\\b(${MUTATION_KEYWORDS.join("|")})\\b`,
20454
20543
  "i"
20455
20544
  );
20456
- function stripComments(sql11) {
20457
- return sql11.replace(/\/\*[\s\S]*?\*\//g, " ").replace(/--[^\n]*/g, " ");
20545
+ function stripComments(sql13) {
20546
+ return sql13.replace(/\/\*[\s\S]*?\*\//g, " ").replace(/--[^\n]*/g, " ");
20458
20547
  }
20459
- function isMutation(sql11) {
20460
- const stripped = stripComments(sql11);
20548
+ function isMutation(sql13) {
20549
+ const stripped = stripComments(sql13);
20461
20550
  if (MUTATION_PATTERN.test(stripped)) return true;
20462
20551
  return /\bSELECT\b[\s\S]+\bINTO\s+\w/i.test(stripped);
20463
20552
  }
@@ -20656,8 +20745,8 @@ import {
20656
20745
  import { basename as basename10, join as join51 } from "path";
20657
20746
 
20658
20747
  // src/commands/transcript/cleanText.ts
20659
- function cleanText(text9) {
20660
- const words = text9.split(/\s+/);
20748
+ function cleanText(text10) {
20749
+ const words = text10.split(/\s+/);
20661
20750
  const cleaned = [];
20662
20751
  for (let i = 0; i < words.length; i++) {
20663
20752
  let isRepeat = false;
@@ -20677,8 +20766,8 @@ function cleanText(text9) {
20677
20766
  }
20678
20767
 
20679
20768
  // src/commands/transcript/convert/parseVtt/deduplicateCues/removeSubstringDuplicates.ts
20680
- function normalizeText(text9) {
20681
- return text9.toLowerCase().trim();
20769
+ function normalizeText(text10) {
20770
+ return text10.toLowerCase().trim();
20682
20771
  }
20683
20772
  function checkSubstringRelation(textI, textJ) {
20684
20773
  if (textI.includes(textJ) && textI.length > textJ.length)
@@ -20807,13 +20896,13 @@ function parseTimestampLine(line) {
20807
20896
  return { startMs: parseTimestamp(startStr), endMs: parseTimestamp(endStr) };
20808
20897
  }
20809
20898
  function buildCue(startMs, endMs, fullText) {
20810
- const { speaker, text: text9 } = extractSpeaker(fullText);
20811
- return text9 ? { startMs, endMs, speaker, text: text9 } : null;
20899
+ const { speaker, text: text10 } = extractSpeaker(fullText);
20900
+ return text10 ? { startMs, endMs, speaker, text: text10 } : null;
20812
20901
  }
20813
20902
  function parseCueLine(lines, i) {
20814
20903
  const { startMs, endMs } = parseTimestampLine(lines[i]);
20815
- const { text: text9, nextIndex: nextIndex2 } = collectTextLines(lines, i + 1);
20816
- return { cue: buildCue(startMs, endMs, text9), nextIndex: nextIndex2 };
20904
+ const { text: text10, nextIndex: nextIndex2 } = collectTextLines(lines, i + 1);
20905
+ return { cue: buildCue(startMs, endMs, text10), nextIndex: nextIndex2 };
20817
20906
  }
20818
20907
  function isCueSeparator(line) {
20819
20908
  return line.trim().includes("-->");
@@ -21396,8 +21485,8 @@ async function exchangeToken(params) {
21396
21485
  body: body.toString()
21397
21486
  });
21398
21487
  if (!response.ok) {
21399
- const text9 = await response.text();
21400
- throw new Error(`Token exchange failed (${response.status}): ${text9}`);
21488
+ const text10 = await response.text();
21489
+ throw new Error(`Token exchange failed (${response.status}): ${text10}`);
21401
21490
  }
21402
21491
  return response.json();
21403
21492
  }
@@ -22531,10 +22620,22 @@ function broadcastSessions(sessions, clients, windowsSessions = [], active) {
22531
22620
  });
22532
22621
  }
22533
22622
 
22534
- // src/shared/db/recordUsagePeak.ts
22623
+ // src/shared/activeWindows.ts
22535
22624
  var WINDOWS = ["five_hour", "seven_day"];
22536
- async function recordUsagePeak(db, rateLimits) {
22625
+ function activeWindows(rateLimits) {
22626
+ if (!rateLimits) return [];
22627
+ const out = [];
22537
22628
  for (const window of WINDOWS) {
22629
+ const resetsAt = rateLimits[window]?.resets_at;
22630
+ if (typeof resetsAt === "number") out.push({ window, resetsAt });
22631
+ }
22632
+ return out;
22633
+ }
22634
+
22635
+ // src/shared/db/recordUsagePeak.ts
22636
+ var WINDOWS2 = ["five_hour", "seven_day"];
22637
+ async function recordUsagePeak(db, rateLimits) {
22638
+ for (const window of WINDOWS2) {
22538
22639
  const w = rateLimits[window];
22539
22640
  if (!w || typeof w.resets_at !== "number" || typeof w.used_percentage !== "number")
22540
22641
  continue;
@@ -22567,6 +22668,9 @@ var ClientHub = class extends Set {
22567
22668
  broadcast(this, { type: "limits", rateLimits });
22568
22669
  this.persistPeak?.(rateLimits);
22569
22670
  }
22671
+ currentWindows() {
22672
+ return activeWindows(this.latestLimits);
22673
+ }
22570
22674
  greet(client) {
22571
22675
  if (this.latestLimits) {
22572
22676
  sendTo(client, { type: "limits", rateLimits: this.latestLimits });
@@ -22781,13 +22885,13 @@ function logSpawnedSession(session) {
22781
22885
  }
22782
22886
 
22783
22887
  // src/shared/db/recordPhaseActiveMs.ts
22784
- import { sql as sql9 } from "drizzle-orm";
22888
+ import { sql as sql10 } from "drizzle-orm";
22785
22889
  async function recordPhaseActiveMs(db, itemId2, phaseIdx, activeMs) {
22786
22890
  if (activeMs <= 0) return;
22787
22891
  await db.insert(phaseUsage).values({ itemId: itemId2, phaseIdx, activeMs }).onConflictDoUpdate({
22788
22892
  target: [phaseUsage.itemId, phaseUsage.phaseIdx],
22789
22893
  set: {
22790
- activeMs: sql9`${phaseUsage.activeMs} + excluded.active_ms`
22894
+ activeMs: sql10`${phaseUsage.activeMs} + excluded.active_ms`
22791
22895
  }
22792
22896
  });
22793
22897
  }
@@ -22929,38 +23033,114 @@ function makeStatusChangeHandler(dismiss, notify2, reuseForRun) {
22929
23033
  return (s, status2, exitCode) => applyStatusChange(s, status2, exitCode, dismiss, notify2, reuseForRun);
22930
23034
  }
22931
23035
 
22932
- // src/shared/db/recordPhaseTokens.ts
22933
- import { sql as sql10 } from "drizzle-orm";
22934
- async function recordPhaseTokens(db, itemId2, phaseIdx, totalIn, totalOut) {
22935
- await db.insert(phaseUsage).values({ itemId: itemId2, phaseIdx, lastTotalIn: totalIn, lastTotalOut: totalOut }).onConflictDoUpdate({
22936
- target: [phaseUsage.itemId, phaseUsage.phaseIdx],
22937
- set: {
22938
- tokensDown: sql10`${phaseUsage.tokensDown} + CASE WHEN ${phaseUsage.lastTotalIn} IS NULL THEN 0 ELSE GREATEST(excluded.last_total_in - ${phaseUsage.lastTotalIn}, 0) END`,
22939
- tokensUp: sql10`${phaseUsage.tokensUp} + CASE WHEN ${phaseUsage.lastTotalOut} IS NULL THEN 0 ELSE GREATEST(excluded.last_total_out - ${phaseUsage.lastTotalOut}, 0) END`,
22940
- lastTotalIn: sql10`excluded.last_total_in`,
22941
- lastTotalOut: sql10`excluded.last_total_out`
22942
- }
23036
+ // src/shared/db/recordPhaseTranscriptUsage.ts
23037
+ import { sql as sql11 } from "drizzle-orm";
23038
+ async function recordPhaseTranscriptUsage(db, itemId2, phaseIdx, responses) {
23039
+ const byId = /* @__PURE__ */ new Map();
23040
+ for (const r of responses) byId.set(r.messageId, r);
23041
+ const unique = [...byId.values()];
23042
+ if (unique.length === 0) return { tokensUp: 0, tokensDown: 0 };
23043
+ return db.transaction(async (tx) => {
23044
+ const inserted = await tx.insert(phaseUsageMessages).values(unique.map((r) => ({ itemId: itemId2, phaseIdx, messageId: r.messageId }))).onConflictDoNothing().returning({ messageId: phaseUsageMessages.messageId });
23045
+ let tokensUp = 0;
23046
+ let tokensDown = 0;
23047
+ for (const { messageId } of inserted) {
23048
+ const r = byId.get(messageId);
23049
+ if (!r) continue;
23050
+ tokensUp += r.outputTokens;
23051
+ tokensDown += r.inputTokens;
23052
+ }
23053
+ if (tokensUp <= 0 && tokensDown <= 0) return { tokensUp: 0, tokensDown: 0 };
23054
+ await tx.insert(phaseUsage).values({ itemId: itemId2, phaseIdx, tokensUp, tokensDown }).onConflictDoUpdate({
23055
+ target: [phaseUsage.itemId, phaseUsage.phaseIdx],
23056
+ set: {
23057
+ tokensUp: sql11`${phaseUsage.tokensUp} + ${tokensUp}`,
23058
+ tokensDown: sql11`${phaseUsage.tokensDown} + ${tokensDown}`
23059
+ }
23060
+ });
23061
+ return { tokensUp, tokensDown };
22943
23062
  });
22944
23063
  }
22945
23064
 
23065
+ // src/shared/db/recordWindowTokens.ts
23066
+ import { and as and19, eq as eq37, sql as sql12 } from "drizzle-orm";
23067
+ async function recordWindowTokens(db, window, resetsAt, tokensUp, tokensDown) {
23068
+ if (tokensUp <= 0 && tokensDown <= 0) return;
23069
+ await db.update(usagePeaks).set({
23070
+ tokensUp: sql12`${usagePeaks.tokensUp} + ${tokensUp}`,
23071
+ tokensDown: sql12`${usagePeaks.tokensDown} + ${tokensDown}`
23072
+ }).where(
23073
+ and19(
23074
+ eq37(usagePeaks.window, window),
23075
+ eq37(usagePeaks.resetsAt, resetsAt),
23076
+ eq37(usagePeaks.segment, 0)
23077
+ )
23078
+ );
23079
+ }
23080
+
23081
+ // src/commands/sessions/shared/transcriptUsage.ts
23082
+ import * as fs31 from "fs";
23083
+ function transcriptUsage(lines) {
23084
+ const byId = /* @__PURE__ */ new Map();
23085
+ for (const line of lines) {
23086
+ if (!line.trim()) continue;
23087
+ let entry;
23088
+ try {
23089
+ entry = JSON.parse(line);
23090
+ } catch {
23091
+ continue;
23092
+ }
23093
+ if (entry.type !== "assistant") continue;
23094
+ const id = entry.message?.id;
23095
+ const usage = entry.message?.usage;
23096
+ if (!id || !usage) continue;
23097
+ byId.set(id, {
23098
+ messageId: id,
23099
+ inputTokens: usage.input_tokens ?? 0,
23100
+ outputTokens: usage.output_tokens ?? 0
23101
+ });
23102
+ }
23103
+ return [...byId.values()];
23104
+ }
23105
+ async function readTranscriptUsage(transcriptPath) {
23106
+ const content = await fs31.promises.readFile(transcriptPath, "utf8");
23107
+ return transcriptUsage(content.split("\n"));
23108
+ }
23109
+
22946
23110
  // src/commands/sessions/daemon/persistPhaseTokens.ts
22947
- async function persistPhaseTokens(itemId2, phaseIdx, totalIn, totalOut) {
23111
+ async function persistPhaseTokens(itemId2, phaseIdx, transcriptPath, windows) {
22948
23112
  try {
22949
23113
  if (!process.env.ASSIST_DATABASE_URL && !loadConfig().database.url) return;
22950
- await recordPhaseTokens(await getDb(), itemId2, phaseIdx, totalIn, totalOut);
23114
+ const responses = await readTranscriptUsage(transcriptPath);
23115
+ if (responses.length === 0) return;
23116
+ const db = await getDb();
23117
+ const { tokensUp, tokensDown } = await recordPhaseTranscriptUsage(
23118
+ db,
23119
+ itemId2,
23120
+ phaseIdx,
23121
+ responses
23122
+ );
23123
+ for (const { window, resetsAt } of windows) {
23124
+ await recordWindowTokens(db, window, resetsAt, tokensUp, tokensDown);
23125
+ }
22951
23126
  } catch (error) {
22952
23127
  daemonLog(`phase-tokens persist failed: ${String(error)}`);
22953
23128
  }
22954
23129
  }
22955
23130
 
22956
23131
  // src/commands/sessions/daemon/recordSessionUsage.ts
22957
- function recordSessionUsage(sessions, claudeSessionId, totalIn, totalOut, usedPct) {
23132
+ function recordSessionUsage(sessions, claudeSessionId, transcriptPath, usedPct, windows) {
22958
23133
  for (const session of sessions) {
22959
23134
  if (session.claudeSessionId !== claudeSessionId) continue;
22960
23135
  session.usedPct = usedPct;
22961
23136
  const phase = sessionBacklogPhase(session);
22962
- if (phase)
22963
- void persistPhaseTokens(phase.itemId, phase.phaseIdx, totalIn, totalOut);
23137
+ if (phase && transcriptPath)
23138
+ void persistPhaseTokens(
23139
+ phase.itemId,
23140
+ phase.phaseIdx,
23141
+ transcriptPath,
23142
+ windows
23143
+ );
22964
23144
  return true;
22965
23145
  }
22966
23146
  return false;
@@ -22982,10 +23162,10 @@ function applyReviewPause(session, activity2) {
22982
23162
  }
22983
23163
 
22984
23164
  // src/shared/db/getPhaseActiveMs.ts
22985
- import { and as and19, eq as eq37 } from "drizzle-orm";
23165
+ import { and as and20, eq as eq38 } from "drizzle-orm";
22986
23166
  async function getPhaseActiveMs(db, itemId2, phaseIdx) {
22987
23167
  const [row] = await db.select({ activeMs: phaseUsage.activeMs }).from(phaseUsage).where(
22988
- and19(eq37(phaseUsage.itemId, itemId2), eq37(phaseUsage.phaseIdx, phaseIdx))
23168
+ and20(eq38(phaseUsage.itemId, itemId2), eq38(phaseUsage.phaseIdx, phaseIdx))
22989
23169
  );
22990
23170
  return row?.activeMs ?? 0;
22991
23171
  }
@@ -24249,13 +24429,13 @@ var SessionManager = class {
24249
24429
  }
24250
24430
  /* why: the status line relays token totals keyed by Claude's session id; join
24251
24431
  * it to the running backlog phase so the spend accumulates against that row. */
24252
- recordUsage(claudeSessionId, totalIn, totalOut, usedPct) {
24432
+ recordUsage(claudeSessionId, transcriptPath, usedPct) {
24253
24433
  if (recordSessionUsage(
24254
24434
  this.sessions.values(),
24255
24435
  claudeSessionId,
24256
- totalIn,
24257
- totalOut,
24258
- usedPct
24436
+ transcriptPath,
24437
+ usedPct,
24438
+ this.clients.currentWindows()
24259
24439
  ))
24260
24440
  this.notify();
24261
24441
  }
@@ -24279,12 +24459,12 @@ import * as net3 from "net";
24279
24459
  import { createInterface as createInterface9 } from "readline";
24280
24460
 
24281
24461
  // src/commands/sessions/shared/discoverSessions.ts
24282
- import * as fs32 from "fs";
24462
+ import * as fs33 from "fs";
24283
24463
  import * as os from "os";
24284
24464
  import * as path52 from "path";
24285
24465
 
24286
24466
  // src/commands/sessions/shared/parseSessionFile.ts
24287
- import * as fs31 from "fs";
24467
+ import * as fs32 from "fs";
24288
24468
  import * as path51 from "path";
24289
24469
 
24290
24470
  // src/commands/sessions/shared/deriveHistoryFields.ts
@@ -24315,8 +24495,8 @@ function deriveSessionType(commandName, name) {
24315
24495
  }
24316
24496
 
24317
24497
  // src/commands/sessions/shared/backlogRunMarkers.ts
24318
- function backlogRunMarkers(text9) {
24319
- const match = text9.match(/backlog item a(\d+):[ \t]*([^\n]*)/);
24498
+ function backlogRunMarkers(text10) {
24499
+ const match = text10.match(/backlog item a(\d+):[ \t]*([^\n]*)/);
24320
24500
  if (!match) return { commandName: "", commandArgs: "" };
24321
24501
  const title = match[2].trim();
24322
24502
  return {
@@ -24370,11 +24550,11 @@ function messageText(entry) {
24370
24550
  const content = msg?.content;
24371
24551
  return typeof content === "string" ? content : Array.isArray(content) ? content.find((c) => c.type === "text")?.text ?? "" : "";
24372
24552
  }
24373
- function stripMarkers(text9) {
24374
- return text9.replace(/<command-[^>]*>[^<]*<\/command-[^>]*>/g, "").trim().slice(0, 80);
24553
+ function stripMarkers(text10) {
24554
+ return text10.replace(/<command-[^>]*>[^<]*<\/command-[^>]*>/g, "").trim().slice(0, 80);
24375
24555
  }
24376
- function matchMarker(text9, tag) {
24377
- const match = text9.match(new RegExp(`<${tag}>([\\s\\S]*?)</${tag}>`));
24556
+ function matchMarker(text10, tag) {
24557
+ const match = text10.match(new RegExp(`<${tag}>([\\s\\S]*?)</${tag}>`));
24378
24558
  return match ? match[1].trim() : "";
24379
24559
  }
24380
24560
 
@@ -24382,10 +24562,10 @@ function matchMarker(text9, tag) {
24382
24562
  async function parseSessionFile(filePath, origin = "wsl") {
24383
24563
  let handle;
24384
24564
  try {
24385
- handle = await fs31.promises.open(filePath, "r");
24565
+ handle = await fs32.promises.open(filePath, "r");
24386
24566
  const meta = extractSessionMeta(await readHeadLines(handle));
24387
24567
  if (!meta.sessionId) return null;
24388
- const timestamp5 = meta.timestamp || (await fs31.promises.stat(filePath)).mtime.toISOString();
24568
+ const timestamp5 = meta.timestamp || (await fs32.promises.stat(filePath)).mtime.toISOString();
24389
24569
  return {
24390
24570
  sessionId: meta.sessionId,
24391
24571
  name: meta.name || `Session ${meta.sessionId.slice(0, 8)}`,
@@ -24431,7 +24611,7 @@ async function discoverSessionJsonlPaths() {
24431
24611
  sessionRoots().map(async ({ dir, origin }) => {
24432
24612
  let projectDirs;
24433
24613
  try {
24434
- projectDirs = await fs32.promises.readdir(dir);
24614
+ projectDirs = await fs33.promises.readdir(dir);
24435
24615
  } catch {
24436
24616
  return;
24437
24617
  }
@@ -24440,7 +24620,7 @@ async function discoverSessionJsonlPaths() {
24440
24620
  const dirPath = path52.join(dir, dirName);
24441
24621
  let entries;
24442
24622
  try {
24443
- entries = await fs32.promises.readdir(dirPath);
24623
+ entries = await fs33.promises.readdir(dirPath);
24444
24624
  } catch {
24445
24625
  return;
24446
24626
  }
@@ -24473,7 +24653,7 @@ async function discoverSessions() {
24473
24653
  }
24474
24654
 
24475
24655
  // src/commands/sessions/shared/parseTranscript.ts
24476
- import * as fs33 from "fs";
24656
+ import * as fs34 from "fs";
24477
24657
 
24478
24658
  // src/commands/sessions/shared/toolTarget.ts
24479
24659
  function toolTarget(input) {
@@ -24492,17 +24672,17 @@ function entryMessages(entry) {
24492
24672
  return [];
24493
24673
  }
24494
24674
  function userMessages(content) {
24495
- if (typeof content === "string") return text8("user", cleanUserText(content));
24675
+ if (typeof content === "string") return text9("user", cleanUserText(content));
24496
24676
  if (!Array.isArray(content)) return [];
24497
- return content.filter((c) => c.type === "text").flatMap((c) => text8("user", cleanUserText(c.text ?? "")));
24677
+ return content.filter((c) => c.type === "text").flatMap((c) => text9("user", cleanUserText(c.text ?? "")));
24498
24678
  }
24499
24679
  function assistantMessages(content) {
24500
- if (typeof content === "string") return text8("assistant", content.trim());
24680
+ if (typeof content === "string") return text9("assistant", content.trim());
24501
24681
  if (!Array.isArray(content)) return [];
24502
24682
  return content.flatMap((c) => assistantItem(c));
24503
24683
  }
24504
24684
  function assistantItem(c) {
24505
- if (c.type === "text") return text8("assistant", (c.text ?? "").trim());
24685
+ if (c.type === "text") return text9("assistant", (c.text ?? "").trim());
24506
24686
  if (c.type === "tool_use")
24507
24687
  return [
24508
24688
  {
@@ -24513,7 +24693,7 @@ function assistantItem(c) {
24513
24693
  ];
24514
24694
  return [];
24515
24695
  }
24516
- function text8(role, value) {
24696
+ function text9(role, value) {
24517
24697
  return value ? [{ role, text: value }] : [];
24518
24698
  }
24519
24699
  function cleanUserText(value) {
@@ -24540,7 +24720,7 @@ async function parseTranscript(sessionId) {
24540
24720
  const filePath = await findSessionJsonlPath(sessionId);
24541
24721
  if (!filePath) return [];
24542
24722
  try {
24543
- const raw = await fs33.promises.readFile(filePath, "utf8");
24723
+ const raw = await fs34.promises.readFile(filePath, "utf8");
24544
24724
  return parseTranscriptLines(raw.split("\n"));
24545
24725
  } catch {
24546
24726
  return [];
@@ -24638,8 +24818,7 @@ var messageHandlers = {
24638
24818
  limits: (_client, m, d) => m.clients.updateLimits(d.rateLimits),
24639
24819
  usage: (_client, m, d) => m.recordUsage(
24640
24820
  d.claudeSessionId,
24641
- d.totalIn,
24642
- d.totalOut,
24821
+ d.transcriptPath,
24643
24822
  d.usedPct
24644
24823
  ),
24645
24824
  input: routed(
@@ -24850,17 +25029,17 @@ function registerDaemon(program2) {
24850
25029
  }
24851
25030
 
24852
25031
  // src/commands/sessions/summarise/index.ts
24853
- import * as fs36 from "fs";
25032
+ import * as fs37 from "fs";
24854
25033
  import chalk193 from "chalk";
24855
25034
 
24856
25035
  // src/commands/sessions/summarise/shared.ts
24857
- import * as fs34 from "fs";
25036
+ import * as fs35 from "fs";
24858
25037
  function writeSummary(jsonlPath2, summary) {
24859
- fs34.writeFileSync(summaryPathFor(jsonlPath2), `${summary.trim()}
25038
+ fs35.writeFileSync(summaryPathFor(jsonlPath2), `${summary.trim()}
24860
25039
  `, "utf8");
24861
25040
  }
24862
25041
  function hasSummary(jsonlPath2) {
24863
- return fs34.existsSync(summaryPathFor(jsonlPath2));
25042
+ return fs35.existsSync(summaryPathFor(jsonlPath2));
24864
25043
  }
24865
25044
  function summaryPathFor(jsonlPath2) {
24866
25045
  return jsonlPath2.replace(/\.jsonl$/, ".summary");
@@ -24870,7 +25049,7 @@ function summaryPathFor(jsonlPath2) {
24870
25049
  import { execFileSync as execFileSync10 } from "child_process";
24871
25050
 
24872
25051
  // src/commands/sessions/summarise/iterateUserMessages.ts
24873
- import * as fs35 from "fs";
25052
+ import * as fs36 from "fs";
24874
25053
 
24875
25054
  // src/commands/sessions/summarise/parseUserLine.ts
24876
25055
  function parseUserLine(line) {
@@ -24883,16 +25062,16 @@ function parseUserLine(line) {
24883
25062
  if (entry.type !== "user") return void 0;
24884
25063
  const msg = entry.message;
24885
25064
  const c = msg?.content;
24886
- let text9;
25065
+ let text10;
24887
25066
  if (typeof c === "string") {
24888
- text9 = c;
25067
+ text10 = c;
24889
25068
  } else if (Array.isArray(c)) {
24890
25069
  const collected = c.filter((b) => b.type === "text").map((b) => b.text ?? "").join("\n");
24891
- text9 = collected || void 0;
25070
+ text10 = collected || void 0;
24892
25071
  }
24893
- if (!text9) return void 0;
25072
+ if (!text10) return void 0;
24894
25073
  return {
24895
- text: text9,
25074
+ text: text10,
24896
25075
  entrypoint: typeof entry.entrypoint === "string" ? entry.entrypoint : void 0
24897
25076
  };
24898
25077
  }
@@ -24901,13 +25080,13 @@ function parseUserLine(line) {
24901
25080
  function* iterateUserMessages(filePath, maxBytes = 65536) {
24902
25081
  let content;
24903
25082
  try {
24904
- const fd = fs35.openSync(filePath, "r");
25083
+ const fd = fs36.openSync(filePath, "r");
24905
25084
  try {
24906
25085
  const buf = Buffer.alloc(maxBytes);
24907
- const bytesRead = fs35.readSync(fd, buf, 0, buf.length, 0);
25086
+ const bytesRead = fs36.readSync(fd, buf, 0, buf.length, 0);
24908
25087
  content = buf.toString("utf8", 0, bytesRead);
24909
25088
  } finally {
24910
- fs35.closeSync(fd);
25089
+ fs36.closeSync(fd);
24911
25090
  }
24912
25091
  } catch {
24913
25092
  return;
@@ -24921,13 +25100,13 @@ function* iterateUserMessages(filePath, maxBytes = 65536) {
24921
25100
 
24922
25101
  // src/commands/sessions/summarise/extractFirstUserMessage.ts
24923
25102
  function extractFirstUserMessage(filePath) {
24924
- for (const text9 of iterateUserMessages(filePath)) {
24925
- return truncate3(text9);
25103
+ for (const text10 of iterateUserMessages(filePath)) {
25104
+ return truncate3(text10);
24926
25105
  }
24927
25106
  return void 0;
24928
25107
  }
24929
- function truncate3(text9, maxChars = 500) {
24930
- const trimmed = text9.trim();
25108
+ function truncate3(text10, maxChars = 500) {
25109
+ const trimmed = text10.trim();
24931
25110
  if (trimmed.length <= maxChars) return trimmed;
24932
25111
  return `${trimmed.slice(0, maxChars)}\u2026`;
24933
25112
  }
@@ -24935,28 +25114,28 @@ function truncate3(text9, maxChars = 500) {
24935
25114
  // src/commands/sessions/summarise/scanSessionBacklogRefs.ts
24936
25115
  function scanSessionBacklogRefs(filePath) {
24937
25116
  const ids = /* @__PURE__ */ new Set();
24938
- for (const text9 of iterateUserMessages(filePath, Number.MAX_SAFE_INTEGER)) {
24939
- for (const id of extractBacklogIds(text9)) {
25117
+ for (const text10 of iterateUserMessages(filePath, Number.MAX_SAFE_INTEGER)) {
25118
+ for (const id of extractBacklogIds(text10)) {
24940
25119
  ids.add(id);
24941
25120
  }
24942
25121
  }
24943
25122
  return [...ids].sort((a, b) => a - b);
24944
25123
  }
24945
- function extractBacklogIds(text9) {
25124
+ function extractBacklogIds(text10) {
24946
25125
  const ids = [];
24947
- for (const m of text9.matchAll(/backlog\s+run\s+a(\d+)/gi)) {
25126
+ for (const m of text10.matchAll(/backlog\s+run\s+a(\d+)/gi)) {
24948
25127
  ids.push(Number.parseInt(m[1], 10));
24949
25128
  }
24950
- for (const m of text9.matchAll(/backlog\s+(?:item\s+)?a(\d+)/gi)) {
25129
+ for (const m of text10.matchAll(/backlog\s+(?:item\s+)?a(\d+)/gi)) {
24951
25130
  ids.push(Number.parseInt(m[1], 10));
24952
25131
  }
24953
- for (const m of text9.matchAll(/backlog\s+phase-done\s+a(\d+)/gi)) {
25132
+ for (const m of text10.matchAll(/backlog\s+phase-done\s+a(\d+)/gi)) {
24954
25133
  ids.push(Number.parseInt(m[1], 10));
24955
25134
  }
24956
- for (const m of text9.matchAll(/backlog\s+comment\s+a(\d+)/gi)) {
25135
+ for (const m of text10.matchAll(/backlog\s+comment\s+a(\d+)/gi)) {
24957
25136
  ids.push(Number.parseInt(m[1], 10));
24958
25137
  }
24959
- for (const m of text9.matchAll(/(?:^|[\s(])a(\d{1,4})(?=[\s).,;:!?]|$)/gm)) {
25138
+ for (const m of text10.matchAll(/(?:^|[\s(])a(\d{1,4})(?=[\s).,;:!?]|$)/gm)) {
24960
25139
  ids.push(Number.parseInt(m[1], 10));
24961
25140
  }
24962
25141
  return ids;
@@ -25027,7 +25206,7 @@ function selectCandidates(files, options2) {
25027
25206
  const candidates = options2.force ? files : files.filter((f) => !hasSummary(f));
25028
25207
  candidates.sort((a, b) => {
25029
25208
  try {
25030
- return fs36.statSync(b).mtimeMs - fs36.statSync(a).mtimeMs;
25209
+ return fs37.statSync(b).mtimeMs - fs37.statSync(a).mtimeMs;
25031
25210
  } catch {
25032
25211
  return 0;
25033
25212
  }
@@ -25207,14 +25386,13 @@ async function relayRateLimits(rateLimits) {
25207
25386
  }
25208
25387
 
25209
25388
  // src/commands/relayUsage.ts
25210
- async function relayUsage(claudeSessionId, totalIn, totalOut, usedPct) {
25389
+ async function relayUsage(claudeSessionId, transcriptPath, usedPct) {
25211
25390
  if (!claudeSessionId) return;
25212
25391
  try {
25213
25392
  await sendToDaemon({
25214
25393
  type: "usage",
25215
25394
  claudeSessionId,
25216
- totalIn,
25217
- totalOut,
25395
+ transcriptPath,
25218
25396
  usedPct
25219
25397
  });
25220
25398
  } catch {
@@ -25247,28 +25425,27 @@ async function statusLine() {
25247
25425
  await relayRateLimits(data.rate_limits);
25248
25426
  await relayUsage(
25249
25427
  data.session_id,
25250
- totalIn,
25251
- totalOut,
25428
+ data.transcript_path,
25252
25429
  data.context_window.used_percentage
25253
25430
  );
25254
25431
  }
25255
25432
 
25256
25433
  // src/commands/sync.ts
25257
- import * as fs39 from "fs";
25434
+ import * as fs40 from "fs";
25258
25435
  import * as os2 from "os";
25259
25436
  import * as path56 from "path";
25260
25437
  import { fileURLToPath as fileURLToPath7 } from "url";
25261
25438
 
25262
25439
  // src/commands/sync/syncClaudeMd.ts
25263
- import * as fs37 from "fs";
25440
+ import * as fs38 from "fs";
25264
25441
  import * as path54 from "path";
25265
25442
  import chalk196 from "chalk";
25266
25443
  async function syncClaudeMd(claudeDir, targetBase, options2) {
25267
25444
  const source = path54.join(claudeDir, "CLAUDE.md");
25268
25445
  const target = path54.join(targetBase, "CLAUDE.md");
25269
- const sourceContent = fs37.readFileSync(source, "utf8");
25270
- if (fs37.existsSync(target)) {
25271
- const targetContent = fs37.readFileSync(target, "utf8");
25446
+ const sourceContent = fs38.readFileSync(source, "utf8");
25447
+ if (fs38.existsSync(target)) {
25448
+ const targetContent = fs38.readFileSync(target, "utf8");
25272
25449
  if (sourceContent !== targetContent) {
25273
25450
  console.log(
25274
25451
  chalk196.yellow("\n\u26A0\uFE0F Warning: CLAUDE.md differs from existing file")
@@ -25285,21 +25462,21 @@ async function syncClaudeMd(claudeDir, targetBase, options2) {
25285
25462
  }
25286
25463
  }
25287
25464
  }
25288
- fs37.copyFileSync(source, target);
25465
+ fs38.copyFileSync(source, target);
25289
25466
  console.log("Copied CLAUDE.md to ~/.claude/CLAUDE.md");
25290
25467
  }
25291
25468
 
25292
25469
  // src/commands/sync/syncSettings.ts
25293
- import * as fs38 from "fs";
25470
+ import * as fs39 from "fs";
25294
25471
  import * as path55 from "path";
25295
25472
  import chalk197 from "chalk";
25296
25473
  async function syncSettings(claudeDir, targetBase, options2) {
25297
25474
  const source = path55.join(claudeDir, "settings.json");
25298
25475
  const target = path55.join(targetBase, "settings.json");
25299
- const sourceContent = fs38.readFileSync(source, "utf8");
25476
+ const sourceContent = fs39.readFileSync(source, "utf8");
25300
25477
  const sourceSettings = JSON.parse(sourceContent);
25301
- const targetExists = fs38.existsSync(target);
25302
- const targetContent = targetExists ? fs38.readFileSync(target, "utf8") : "";
25478
+ const targetExists = fs39.existsSync(target);
25479
+ const targetContent = targetExists ? fs39.readFileSync(target, "utf8") : "";
25303
25480
  const preservedUserSettings = targetExists ? JSON.parse(targetContent) : {};
25304
25481
  const mergedContent = JSON.stringify(
25305
25482
  { ...preservedUserSettings, ...sourceSettings },
@@ -25328,7 +25505,7 @@ async function syncSettings(claudeDir, targetBase, options2) {
25328
25505
  }
25329
25506
  }
25330
25507
  }
25331
- fs38.writeFileSync(target, mergedContent);
25508
+ fs39.writeFileSync(target, mergedContent);
25332
25509
  console.log("Copied settings.json to ~/.claude/settings.json");
25333
25510
  }
25334
25511
 
@@ -25347,10 +25524,10 @@ async function sync(options2) {
25347
25524
  function syncCommands(claudeDir, targetBase) {
25348
25525
  const sourceDir = path56.join(claudeDir, "commands");
25349
25526
  const targetDir = path56.join(targetBase, "commands");
25350
- fs39.mkdirSync(targetDir, { recursive: true });
25351
- const files = fs39.readdirSync(sourceDir);
25527
+ fs40.mkdirSync(targetDir, { recursive: true });
25528
+ const files = fs40.readdirSync(sourceDir);
25352
25529
  for (const file of files) {
25353
- fs39.copyFileSync(path56.join(sourceDir, file), path56.join(targetDir, file));
25530
+ fs40.copyFileSync(path56.join(sourceDir, file), path56.join(targetDir, file));
25354
25531
  console.log(`Copied ${file} to ${targetDir}`);
25355
25532
  }
25356
25533
  console.log(`Synced ${files.length} command(s) to ~/.claude/commands`);