bb-cc-lite 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/README.md +45 -6
  2. package/assets/statusline-demo.gif +0 -0
  3. package/dist/baseline-builder.d.ts +13 -0
  4. package/dist/baseline-builder.js +666 -0
  5. package/dist/baseline-builder.js.map +1 -0
  6. package/dist/baseline.d.ts +113 -0
  7. package/dist/baseline.js +383 -0
  8. package/dist/baseline.js.map +1 -0
  9. package/dist/cli.js +41 -50
  10. package/dist/cli.js.map +1 -1
  11. package/dist/decision-presentation.d.ts +11 -0
  12. package/dist/decision-presentation.js +12 -0
  13. package/dist/decision-presentation.js.map +1 -0
  14. package/dist/doctor.d.ts +17 -0
  15. package/dist/doctor.js +161 -8
  16. package/dist/doctor.js.map +1 -1
  17. package/dist/event-store-persistence.d.ts +5 -0
  18. package/dist/event-store-persistence.js +172 -0
  19. package/dist/event-store-persistence.js.map +1 -0
  20. package/dist/event-store-queries.d.ts +9 -0
  21. package/dist/event-store-queries.js +51 -0
  22. package/dist/event-store-queries.js.map +1 -0
  23. package/dist/hook-payload.d.ts +3 -0
  24. package/dist/hook-payload.js +90 -0
  25. package/dist/hook-payload.js.map +1 -0
  26. package/dist/hook-summary.d.ts +12 -0
  27. package/dist/hook-summary.js +25 -0
  28. package/dist/hook-summary.js.map +1 -0
  29. package/dist/hooks.d.ts +2 -14
  30. package/dist/hooks.js +2 -126
  31. package/dist/hooks.js.map +1 -1
  32. package/dist/paths.d.ts +1 -0
  33. package/dist/paths.js +3 -0
  34. package/dist/paths.js.map +1 -1
  35. package/dist/renderer.d.ts +2 -2
  36. package/dist/renderer.js +15 -7
  37. package/dist/renderer.js.map +1 -1
  38. package/dist/session.d.ts +3 -0
  39. package/dist/session.js +9 -0
  40. package/dist/session.js.map +1 -0
  41. package/dist/settings.d.ts +2 -1
  42. package/dist/settings.js +23 -42
  43. package/dist/settings.js.map +1 -1
  44. package/dist/signals.d.ts +2 -1
  45. package/dist/signals.js +156 -6
  46. package/dist/signals.js.map +1 -1
  47. package/dist/statusline.d.ts +1 -0
  48. package/dist/statusline.js +34 -0
  49. package/dist/statusline.js.map +1 -0
  50. package/dist/store.d.ts +3 -10
  51. package/dist/store.js +3 -84
  52. package/dist/store.js.map +1 -1
  53. package/dist/tool-metadata.d.ts +8 -0
  54. package/dist/tool-metadata.js +52 -0
  55. package/dist/tool-metadata.js.map +1 -0
  56. package/dist/transcript-reader.d.ts +9 -0
  57. package/dist/transcript-reader.js +45 -0
  58. package/dist/transcript-reader.js.map +1 -0
  59. package/dist/transcript.d.ts +2 -3
  60. package/dist/transcript.js +54 -66
  61. package/dist/transcript.js.map +1 -1
  62. package/dist/types.d.ts +56 -0
  63. package/dist/why.d.ts +7 -0
  64. package/dist/why.js +32 -0
  65. package/dist/why.js.map +1 -0
  66. package/package.json +7 -7
@@ -0,0 +1,172 @@
1
+ import { chmod, mkdir, readFile, rename, writeFile } from "node:fs/promises";
2
+ import { dirname } from "node:path";
3
+ import { eventStorePath } from "./paths.js";
4
+ export const STORE_LIMIT = 100;
5
+ export const HOOK_STORE_LIMIT = 500;
6
+ export async function readStore(storePath = eventStorePath()) {
7
+ try {
8
+ const parsed = JSON.parse(await readFile(storePath, "utf8"));
9
+ return {
10
+ version: 1,
11
+ updatedAt: typeof parsed.updatedAt === "string" ? parsed.updatedAt : new Date(0).toISOString(),
12
+ decisions: Array.isArray(parsed.decisions)
13
+ ? parsed.decisions.flatMap((decision) => sanitizeStoredDecision(decision) ?? []).slice(-STORE_LIMIT)
14
+ : [],
15
+ hookEvents: Array.isArray(parsed.hookEvents)
16
+ ? parsed.hookEvents.flatMap((event) => sanitizeStoredHookEvent(event) ?? []).slice(-HOOK_STORE_LIMIT)
17
+ : []
18
+ };
19
+ }
20
+ catch {
21
+ return { version: 1, updatedAt: new Date(0).toISOString(), decisions: [], hookEvents: [] };
22
+ }
23
+ }
24
+ export async function writeStore(store, storePath) {
25
+ await mkdir(dirname(storePath), { recursive: true, mode: 0o700 });
26
+ const tempPath = `${storePath}.${process.pid}.tmp`;
27
+ await writeFile(tempPath, `${JSON.stringify(store, null, 2)}\n`, { encoding: "utf8", mode: 0o600 });
28
+ await chmod(tempPath, 0o600);
29
+ await rename(tempPath, storePath);
30
+ }
31
+ function sanitizeStoredDecision(value) {
32
+ const record = asRecord(value);
33
+ if (!record || containsForbiddenRawDataKey(record)) {
34
+ return undefined;
35
+ }
36
+ const id = stringField(record.id);
37
+ const state = decisionState(record.state);
38
+ const action = stringField(record.action);
39
+ if (!id || !state || !action) {
40
+ return undefined;
41
+ }
42
+ return {
43
+ id,
44
+ state,
45
+ reasonCode: stringField(record.reasonCode) || "unknown",
46
+ diagnosisCode: stringField(record.diagnosisCode),
47
+ diagnosis: stringField(record.diagnosis),
48
+ confidence: confidence(record.confidence),
49
+ baselineNote: stringField(record.baselineNote),
50
+ primaryEvidence: stringField(record.primaryEvidence) || "stored decision",
51
+ evidence: sanitizeEvidence(record.evidence),
52
+ impact: stringField(record.impact) || "",
53
+ action,
54
+ costUsd: numberField(record.costUsd),
55
+ costSource: costSource(record.costSource),
56
+ contextPercent: numberField(record.contextPercent),
57
+ rateLimitPercent: numberField(record.rateLimitPercent),
58
+ sessionKey: stringField(record.sessionKey),
59
+ createdAt: stringField(record.createdAt) || new Date(0).toISOString()
60
+ };
61
+ }
62
+ function sanitizeStoredHookEvent(value) {
63
+ const record = asRecord(value);
64
+ if (!record || containsForbiddenRawDataKey(record)) {
65
+ return undefined;
66
+ }
67
+ const id = stringField(record.id);
68
+ const kind = hookKind(record.kind);
69
+ const timestamp = stringField(record.timestamp);
70
+ if (!id || !kind || !timestamp) {
71
+ return undefined;
72
+ }
73
+ return {
74
+ id,
75
+ kind,
76
+ timestamp,
77
+ hookEventName: stringField(record.hookEventName) || "unknown",
78
+ sessionKey: stringField(record.sessionKey),
79
+ toolName: stringField(record.toolName),
80
+ purpose: stringField(record.purpose),
81
+ toolCount: numberField(record.toolCount)
82
+ };
83
+ }
84
+ function sanitizeEvidence(value) {
85
+ if (!Array.isArray(value)) {
86
+ return [];
87
+ }
88
+ return value.flatMap((item) => {
89
+ const record = asRecord(item);
90
+ const label = stringField(record?.label);
91
+ if (!record || containsForbiddenRawDataKey(record) || !label) {
92
+ return [];
93
+ }
94
+ const detail = stringField(record.detail);
95
+ return detail ? [{ label, detail }] : [{ label }];
96
+ });
97
+ }
98
+ function asRecord(value) {
99
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : undefined;
100
+ }
101
+ function stringField(value) {
102
+ return typeof value === "string" && value.length > 0 ? value : undefined;
103
+ }
104
+ function numberField(value) {
105
+ return typeof value === "number" && Number.isFinite(value) ? value : undefined;
106
+ }
107
+ function decisionState(value) {
108
+ return value === "Healthy" || value === "Careful" || value === "Stop" ? value : undefined;
109
+ }
110
+ function confidence(value) {
111
+ return value === "low" || value === "medium" || value === "high" ? value : undefined;
112
+ }
113
+ function costSource(value) {
114
+ return value === "claude" || value === "estimated" ? value : undefined;
115
+ }
116
+ function hookKind(value) {
117
+ return value === "tool_success" ||
118
+ value === "tool_failure" ||
119
+ value === "tool_batch" ||
120
+ value === "compaction" ||
121
+ value === "stop" ||
122
+ value === "session_end"
123
+ ? value
124
+ : undefined;
125
+ }
126
+ function containsForbiddenRawDataKey(value) {
127
+ if (Array.isArray(value)) {
128
+ return value.some((item) => containsForbiddenRawDataKey(item));
129
+ }
130
+ const record = asRecord(value);
131
+ if (!record) {
132
+ return false;
133
+ }
134
+ for (const [key, child] of Object.entries(record)) {
135
+ if (FORBIDDEN_RAW_DATA_KEYS_NORMALIZED.has(normalizeKey(key)) || containsForbiddenRawDataKey(child)) {
136
+ return true;
137
+ }
138
+ }
139
+ return false;
140
+ }
141
+ const FORBIDDEN_RAW_DATA_KEYS_NORMALIZED = new Set([
142
+ "assistantText",
143
+ "command",
144
+ "commands",
145
+ "cwd",
146
+ "cwds",
147
+ "fileContent",
148
+ "fileContents",
149
+ "prompt",
150
+ "prompts",
151
+ "promptText",
152
+ "rawCommand",
153
+ "rawCommands",
154
+ "rawPrompt",
155
+ "rawPrompts",
156
+ "rawSessionId",
157
+ "rawSessionIds",
158
+ "rawToolOutput",
159
+ "rawToolOutputs",
160
+ "sessionId",
161
+ "sessionIds",
162
+ "toolOutput",
163
+ "toolOutputs",
164
+ "transcriptPath",
165
+ "transcriptPaths",
166
+ "workspacePath",
167
+ "workspacePaths"
168
+ ].map(normalizeKey));
169
+ function normalizeKey(value) {
170
+ return value.replaceAll(/[_-]/gu, "").toLowerCase();
171
+ }
172
+ //# sourceMappingURL=event-store-persistence.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-store-persistence.js","sourceRoot":"","sources":["../src/event-store-persistence.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7E,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAG5C,MAAM,CAAC,MAAM,WAAW,GAAG,GAAG,CAAC;AAC/B,MAAM,CAAC,MAAM,gBAAgB,GAAG,GAAG,CAAC;AAEpC,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,SAAS,GAAG,cAAc,EAAE;IAC1D,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC,CAA4B,CAAC;QACxF,OAAO;YACL,OAAO,EAAE,CAAC;YACV,SAAS,EAAE,OAAO,MAAM,CAAC,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE;YAC9F,SAAS,EAAE,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC;gBACxC,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,sBAAsB,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,WAAW,CAAC;gBACpG,CAAC,CAAC,EAAE;YACN,UAAU,EAAE,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC;gBAC1C,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,uBAAuB,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,gBAAgB,CAAC;gBACrG,CAAC,CAAC,EAAE;SACP,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,SAAS,EAAE,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE,CAAC;IAC7F,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,KAAqB,EAAE,SAAiB;IACvE,MAAM,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAClE,MAAM,QAAQ,GAAG,GAAG,SAAS,IAAI,OAAO,CAAC,GAAG,MAAM,CAAC;IACnD,MAAM,SAAS,CAAC,QAAQ,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACpG,MAAM,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;IAC7B,MAAM,MAAM,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;AACpC,CAAC;AAED,SAAS,sBAAsB,CAAC,KAAc;IAC5C,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IAC/B,IAAI,CAAC,MAAM,IAAI,2BAA2B,CAAC,MAAM,CAAC,EAAE,CAAC;QACnD,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,MAAM,EAAE,GAAG,WAAW,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IAClC,MAAM,KAAK,GAAG,aAAa,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC1C,MAAM,MAAM,GAAG,WAAW,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAC1C,IAAI,CAAC,EAAE,IAAI,CAAC,KAAK,IAAI,CAAC,MAAM,EAAE,CAAC;QAC7B,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,OAAO;QACL,EAAE;QACF,KAAK;QACL,UAAU,EAAE,WAAW,CAAC,MAAM,CAAC,UAAU,CAAC,IAAI,SAAS;QACvD,aAAa,EAAE,WAAW,CAAC,MAAM,CAAC,aAAa,CAAC;QAChD,SAAS,EAAE,WAAW,CAAC,MAAM,CAAC,SAAS,CAAC;QACxC,UAAU,EAAE,UAAU,CAAC,MAAM,CAAC,UAAU,CAAC;QACzC,YAAY,EAAE,WAAW,CAAC,MAAM,CAAC,YAAY,CAAC;QAC9C,eAAe,EAAE,WAAW,CAAC,MAAM,CAAC,eAAe,CAAC,IAAI,iBAAiB;QACzE,QAAQ,EAAE,gBAAgB,CAAC,MAAM,CAAC,QAAQ,CAAC;QAC3C,MAAM,EAAE,WAAW,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE;QACxC,MAAM;QACN,OAAO,EAAE,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC;QACpC,UAAU,EAAE,UAAU,CAAC,MAAM,CAAC,UAAU,CAAC;QACzC,cAAc,EAAE,WAAW,CAAC,MAAM,CAAC,cAAc,CAAC;QAClD,gBAAgB,EAAE,WAAW,CAAC,MAAM,CAAC,gBAAgB,CAAC;QACtD,UAAU,EAAE,WAAW,CAAC,MAAM,CAAC,UAAU,CAAC;QAC1C,SAAS,EAAE,WAAW,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE;KACtE,CAAC;AACJ,CAAC;AAED,SAAS,uBAAuB,CAAC,KAAc;IAC7C,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IAC/B,IAAI,CAAC,MAAM,IAAI,2BAA2B,CAAC,MAAM,CAAC,EAAE,CAAC;QACnD,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,MAAM,EAAE,GAAG,WAAW,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IAClC,MAAM,IAAI,GAAG,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACnC,MAAM,SAAS,GAAG,WAAW,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IAChD,IAAI,CAAC,EAAE,IAAI,CAAC,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;QAC/B,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,OAAO;QACL,EAAE;QACF,IAAI;QACJ,SAAS;QACT,aAAa,EAAE,WAAW,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,SAAS;QAC7D,UAAU,EAAE,WAAW,CAAC,MAAM,CAAC,UAAU,CAAC;QAC1C,QAAQ,EAAE,WAAW,CAAC,MAAM,CAAC,QAAQ,CAAC;QACtC,OAAO,EAAE,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC;QACpC,SAAS,EAAE,WAAW,CAAC,MAAM,CAAC,SAAS,CAAC;KACzC,CAAC;AACJ,CAAC;AAED,SAAS,gBAAgB,CAAC,KAAc;IACtC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QAC1B,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,OAAO,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE;QAC5B,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;QAC9B,MAAM,KAAK,GAAG,WAAW,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;QACzC,IAAI,CAAC,MAAM,IAAI,2BAA2B,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;YAC7D,OAAO,EAAE,CAAC;QACZ,CAAC;QACD,MAAM,MAAM,GAAG,WAAW,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAC1C,OAAO,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;IACpD,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAE,KAAiC,CAAC,CAAC,CAAC,SAAS,CAAC;AAC/H,CAAC;AAED,SAAS,WAAW,CAAC,KAAc;IACjC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;AAC3E,CAAC;AAED,SAAS,WAAW,CAAC,KAAc;IACjC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;AACjF,CAAC;AAED,SAAS,aAAa,CAAC,KAAc;IACnC,OAAO,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;AAC5F,CAAC;AAED,SAAS,UAAU,CAAC,KAAc;IAChC,OAAO,KAAK,KAAK,KAAK,IAAI,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;AACvF,CAAC;AAED,SAAS,UAAU,CAAC,KAAc;IAChC,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,WAAW,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;AACzE,CAAC;AAED,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,KAAK,KAAK,cAAc;QAC7B,KAAK,KAAK,cAAc;QACxB,KAAK,KAAK,YAAY;QACtB,KAAK,KAAK,YAAY;QACtB,KAAK,KAAK,MAAM;QAChB,KAAK,KAAK,aAAa;QACvB,CAAC,CAAC,KAAK;QACP,CAAC,CAAC,SAAS,CAAC;AAChB,CAAC;AAED,SAAS,2BAA2B,CAAC,KAAc;IACjD,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,2BAA2B,CAAC,IAAI,CAAC,CAAC,CAAC;IACjE,CAAC;IACD,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IAC/B,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO,KAAK,CAAC;IACf,CAAC;IACD,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAClD,IAAI,kCAAkC,CAAC,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,IAAI,2BAA2B,CAAC,KAAK,CAAC,EAAE,CAAC;YACpG,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,kCAAkC,GAAG,IAAI,GAAG,CAChD;IACE,eAAe;IACf,SAAS;IACT,UAAU;IACV,KAAK;IACL,MAAM;IACN,aAAa;IACb,cAAc;IACd,QAAQ;IACR,SAAS;IACT,YAAY;IACZ,YAAY;IACZ,aAAa;IACb,WAAW;IACX,YAAY;IACZ,cAAc;IACd,eAAe;IACf,eAAe;IACf,gBAAgB;IAChB,WAAW;IACX,YAAY;IACZ,YAAY;IACZ,aAAa;IACb,gBAAgB;IAChB,iBAAiB;IACjB,eAAe;IACf,gBAAgB;CACjB,CAAC,GAAG,CAAC,YAAY,CAAC,CACpB,CAAC;AAEF,SAAS,YAAY,CAAC,KAAa;IACjC,OAAO,KAAK,CAAC,UAAU,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;AACtD,CAAC"}
@@ -0,0 +1,9 @@
1
+ import type { StoredDecision, ToolFailureSummary } from "./types.js";
2
+ export declare function latestDecision(sessionKey?: string, storePath?: string): Promise<StoredDecision | undefined>;
3
+ export declare function hookSummary(sessionKey: string | undefined, storePath?: string): Promise<{
4
+ failedToolResults: number;
5
+ toolCalls: number;
6
+ compactionEvents: number;
7
+ repeatedFailures: ToolFailureSummary[];
8
+ latestTimestamp?: string;
9
+ }>;
@@ -0,0 +1,51 @@
1
+ import { readStore } from "./event-store-persistence.js";
2
+ export async function latestDecision(sessionKey, storePath) {
3
+ const store = await readStore(storePath);
4
+ const decisions = sessionKey ? store.decisions.filter((decision) => decision.sessionKey === sessionKey) : store.decisions;
5
+ return decisions.at(-1);
6
+ }
7
+ export async function hookSummary(sessionKey, storePath) {
8
+ const store = await readStore(storePath);
9
+ const events = store.hookEvents.filter((event) => !sessionKey || event.sessionKey === sessionKey);
10
+ const failures = new Map();
11
+ let failedToolResults = 0;
12
+ let toolCalls = 0;
13
+ let compactionEvents = 0;
14
+ let latestTimestamp;
15
+ for (const event of events) {
16
+ latestTimestamp = !latestTimestamp || event.timestamp > latestTimestamp ? event.timestamp : latestTimestamp;
17
+ if (event.kind === "tool_failure") {
18
+ failedToolResults += 1;
19
+ toolCalls += 1;
20
+ const toolName = event.toolName || "tool";
21
+ const key = failureKey(toolName, event.purpose);
22
+ const existing = failures.get(key);
23
+ failures.set(key, {
24
+ toolName,
25
+ purpose: event.purpose,
26
+ count: (existing?.count || 0) + 1
27
+ });
28
+ }
29
+ else if (event.kind === "tool_success") {
30
+ toolCalls += 1;
31
+ failures.delete(failureKey(event.toolName || "tool", event.purpose));
32
+ }
33
+ else if (event.kind === "tool_batch") {
34
+ toolCalls += event.toolCount || 0;
35
+ }
36
+ else if (event.kind === "compaction") {
37
+ compactionEvents += 1;
38
+ }
39
+ }
40
+ return {
41
+ failedToolResults,
42
+ toolCalls,
43
+ compactionEvents,
44
+ repeatedFailures: [...failures.values()].filter((failure) => failure.count >= 2),
45
+ latestTimestamp
46
+ };
47
+ }
48
+ function failureKey(toolName, purpose) {
49
+ return `${toolName}:${purpose || ""}`;
50
+ }
51
+ //# sourceMappingURL=event-store-queries.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-store-queries.js","sourceRoot":"","sources":["../src/event-store-queries.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { DerivedHookEvent } from "./types.js";
2
+ export declare const SAFE_HOOK_EVENTS: readonly ["PostToolUse", "PostToolUseFailure", "PostToolBatch", "PreCompact", "PostCompact", "Stop", "SessionEnd"];
3
+ export declare function parseHookPayload(raw: string, fallbackEventName?: string): DerivedHookEvent | undefined;
@@ -0,0 +1,90 @@
1
+ import { hashValue } from "./paths.js";
2
+ import { asRecord, numberField, stringField } from "./status-input.js";
3
+ import { classifyToolPurpose, safeToolName } from "./tool-metadata.js";
4
+ export const SAFE_HOOK_EVENTS = [
5
+ "PostToolUse",
6
+ "PostToolUseFailure",
7
+ "PostToolBatch",
8
+ "PreCompact",
9
+ "PostCompact",
10
+ "Stop",
11
+ "SessionEnd"
12
+ ];
13
+ export function parseHookPayload(raw, fallbackEventName) {
14
+ let parsed;
15
+ try {
16
+ parsed = JSON.parse(raw.trim() || "{}");
17
+ }
18
+ catch {
19
+ return undefined;
20
+ }
21
+ const root = asRecord(parsed);
22
+ if (!root) {
23
+ return undefined;
24
+ }
25
+ const hookEventName = stringField(root.hook_event_name) || stringField(root.event) || fallbackEventName || "unknown";
26
+ const sessionId = stringField(root.session_id) || stringField(root.sessionId);
27
+ const base = {
28
+ timestamp: stringField(root.timestamp) || new Date().toISOString(),
29
+ hookEventName,
30
+ sessionKey: hashValue(sessionId)
31
+ };
32
+ if (hookEventName === "PostToolUseFailure") {
33
+ const toolName = safeToolName(stringField(root.tool_name) || stringField(root.toolName));
34
+ return {
35
+ ...base,
36
+ kind: "tool_failure",
37
+ toolName,
38
+ purpose: classifyToolPurpose(toolName, root.tool_input ?? root.toolInput)
39
+ };
40
+ }
41
+ if (hookEventName === "PostToolUse") {
42
+ const toolName = safeToolName(stringField(root.tool_name) || stringField(root.toolName));
43
+ return {
44
+ ...base,
45
+ kind: "tool_success",
46
+ toolName,
47
+ purpose: classifyToolPurpose(toolName, root.tool_input ?? root.toolInput)
48
+ };
49
+ }
50
+ if (hookEventName === "PostToolBatch") {
51
+ return {
52
+ ...base,
53
+ kind: "tool_batch",
54
+ toolCount: countBatchTools(root)
55
+ };
56
+ }
57
+ if (hookEventName === "PreCompact" || hookEventName === "PostCompact") {
58
+ return {
59
+ ...base,
60
+ kind: "compaction"
61
+ };
62
+ }
63
+ if (hookEventName === "Stop" || hookEventName === "StopFailure") {
64
+ return {
65
+ ...base,
66
+ kind: "stop"
67
+ };
68
+ }
69
+ if (hookEventName === "SessionEnd") {
70
+ return {
71
+ ...base,
72
+ kind: "session_end"
73
+ };
74
+ }
75
+ return undefined;
76
+ }
77
+ function countBatchTools(root) {
78
+ const values = [root.tools, root.tool_uses, root.toolUses, root.results, root.tool_results, root.toolResults];
79
+ for (const value of values) {
80
+ if (Array.isArray(value)) {
81
+ return value.length;
82
+ }
83
+ const count = numberField(value);
84
+ if (count !== undefined) {
85
+ return Math.max(0, Math.floor(count));
86
+ }
87
+ }
88
+ return 0;
89
+ }
90
+ //# sourceMappingURL=hook-payload.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hook-payload.js","sourceRoot":"","sources":["../src/hook-payload.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ import type { TranscriptSummary } from "./types.js";
2
+ export declare function mergeHookSummary(transcript: TranscriptSummary, hookData: {
3
+ failedToolResults: number;
4
+ toolCalls: number;
5
+ compactionEvents: number;
6
+ repeatedFailures: Array<{
7
+ toolName: string;
8
+ count: number;
9
+ purpose?: string;
10
+ }>;
11
+ latestTimestamp?: string;
12
+ }): TranscriptSummary;
@@ -0,0 +1,25 @@
1
+ export function mergeHookSummary(transcript, hookData) {
2
+ const repeatedFailures = new Map();
3
+ for (const failure of [...transcript.repeatedFailures, ...hookData.repeatedFailures]) {
4
+ const key = `${failure.toolName}:${failure.purpose || ""}`;
5
+ const existing = repeatedFailures.get(key);
6
+ repeatedFailures.set(key, {
7
+ toolName: failure.toolName,
8
+ purpose: failure.purpose,
9
+ count: Math.max(existing?.count || 0, failure.count)
10
+ });
11
+ }
12
+ return {
13
+ ...transcript,
14
+ toolCalls: Math.max(transcript.toolCalls, hookData.toolCalls),
15
+ failedToolResults: Math.max(transcript.failedToolResults, hookData.failedToolResults),
16
+ repeatedFailures: [...repeatedFailures.values()].filter((failure) => failure.count >= 2),
17
+ compactionEvents: Math.max(transcript.compactionEvents, hookData.compactionEvents),
18
+ latestTimestamp: transcript.latestTimestamp && hookData.latestTimestamp
19
+ ? transcript.latestTimestamp > hookData.latestTimestamp
20
+ ? transcript.latestTimestamp
21
+ : hookData.latestTimestamp
22
+ : transcript.latestTimestamp || hookData.latestTimestamp
23
+ };
24
+ }
25
+ //# sourceMappingURL=hook-summary.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hook-summary.js","sourceRoot":"","sources":["../src/hook-summary.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,gBAAgB,CAC9B,UAA6B,EAC7B,QAMC;IAED,MAAM,gBAAgB,GAAG,IAAI,GAAG,EAAiE,CAAC;IAClG,KAAK,MAAM,OAAO,IAAI,CAAC,GAAG,UAAU,CAAC,gBAAgB,EAAE,GAAG,QAAQ,CAAC,gBAAgB,CAAC,EAAE,CAAC;QACrF,MAAM,GAAG,GAAG,GAAG,OAAO,CAAC,QAAQ,IAAI,OAAO,CAAC,OAAO,IAAI,EAAE,EAAE,CAAC;QAC3D,MAAM,QAAQ,GAAG,gBAAgB,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC3C,gBAAgB,CAAC,GAAG,CAAC,GAAG,EAAE;YACxB,QAAQ,EAAE,OAAO,CAAC,QAAQ;YAC1B,OAAO,EAAE,OAAO,CAAC,OAAO;YACxB,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,KAAK,IAAI,CAAC,EAAE,OAAO,CAAC,KAAK,CAAC;SACrD,CAAC,CAAC;IACL,CAAC;IAED,OAAO;QACL,GAAG,UAAU;QACb,SAAS,EAAE,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,SAAS,EAAE,QAAQ,CAAC,SAAS,CAAC;QAC7D,iBAAiB,EAAE,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,iBAAiB,EAAE,QAAQ,CAAC,iBAAiB,CAAC;QACrF,gBAAgB,EAAE,CAAC,GAAG,gBAAgB,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,IAAI,CAAC,CAAC;QACxF,gBAAgB,EAAE,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,gBAAgB,EAAE,QAAQ,CAAC,gBAAgB,CAAC;QAClF,eAAe,EACb,UAAU,CAAC,eAAe,IAAI,QAAQ,CAAC,eAAe;YACpD,CAAC,CAAC,UAAU,CAAC,eAAe,GAAG,QAAQ,CAAC,eAAe;gBACrD,CAAC,CAAC,UAAU,CAAC,eAAe;gBAC5B,CAAC,CAAC,QAAQ,CAAC,eAAe;YAC5B,CAAC,CAAC,UAAU,CAAC,eAAe,IAAI,QAAQ,CAAC,eAAe;KAC7D,CAAC;AACJ,CAAC"}
package/dist/hooks.d.ts CHANGED
@@ -1,14 +1,2 @@
1
- import type { DerivedHookEvent, TranscriptSummary } from "./types.js";
2
- export declare const SAFE_HOOK_EVENTS: readonly ["PostToolUse", "PostToolUseFailure", "PostToolBatch", "PreCompact", "PostCompact", "Stop", "SessionEnd"];
3
- export declare function parseHookPayload(raw: string, fallbackEventName?: string): DerivedHookEvent | undefined;
4
- export declare function mergeHookSummary(transcript: TranscriptSummary, hookData: {
5
- failedToolResults: number;
6
- toolCalls: number;
7
- compactionEvents: number;
8
- repeatedFailures: Array<{
9
- toolName: string;
10
- count: number;
11
- purpose?: string;
12
- }>;
13
- latestTimestamp?: string;
14
- }): TranscriptSummary;
1
+ export { SAFE_HOOK_EVENTS, parseHookPayload } from "./hook-payload.js";
2
+ export { mergeHookSummary } from "./hook-summary.js";
package/dist/hooks.js CHANGED
@@ -1,127 +1,3 @@
1
- import { hashValue } from "./paths.js";
2
- import { asRecord, numberField, stringField } from "./status-input.js";
3
- const TEST_COMMAND_RE = /\b(npm|pnpm|yarn|bun)\s+(run\s+)?(test|vitest|jest)|\b(vitest|jest|mocha|pytest|cargo\s+test|go\s+test|rspec|playwright\s+test)\b/i;
4
- export const SAFE_HOOK_EVENTS = [
5
- "PostToolUse",
6
- "PostToolUseFailure",
7
- "PostToolBatch",
8
- "PreCompact",
9
- "PostCompact",
10
- "Stop",
11
- "SessionEnd"
12
- ];
13
- export function parseHookPayload(raw, fallbackEventName) {
14
- let parsed;
15
- try {
16
- parsed = JSON.parse(raw.trim() || "{}");
17
- }
18
- catch {
19
- return undefined;
20
- }
21
- const root = asRecord(parsed);
22
- if (!root) {
23
- return undefined;
24
- }
25
- const hookEventName = stringField(root.hook_event_name) || stringField(root.event) || fallbackEventName || "unknown";
26
- const sessionId = stringField(root.session_id) || stringField(root.sessionId);
27
- const base = {
28
- timestamp: stringField(root.timestamp) || new Date().toISOString(),
29
- hookEventName,
30
- sessionKey: hashValue(sessionId)
31
- };
32
- if (hookEventName === "PostToolUseFailure") {
33
- const toolName = safeToolName(stringField(root.tool_name) || stringField(root.toolName));
34
- return {
35
- ...base,
36
- kind: "tool_failure",
37
- toolName,
38
- purpose: classifyToolPurpose(toolName, root.tool_input ?? root.toolInput)
39
- };
40
- }
41
- if (hookEventName === "PostToolUse") {
42
- const toolName = safeToolName(stringField(root.tool_name) || stringField(root.toolName));
43
- return {
44
- ...base,
45
- kind: "tool_success",
46
- toolName,
47
- purpose: classifyToolPurpose(toolName, root.tool_input ?? root.toolInput)
48
- };
49
- }
50
- if (hookEventName === "PostToolBatch") {
51
- return {
52
- ...base,
53
- kind: "tool_batch",
54
- toolCount: countBatchTools(root)
55
- };
56
- }
57
- if (hookEventName === "PreCompact" || hookEventName === "PostCompact") {
58
- return {
59
- ...base,
60
- kind: "compaction"
61
- };
62
- }
63
- if (hookEventName === "Stop" || hookEventName === "StopFailure") {
64
- return {
65
- ...base,
66
- kind: "stop"
67
- };
68
- }
69
- if (hookEventName === "SessionEnd") {
70
- return {
71
- ...base,
72
- kind: "session_end"
73
- };
74
- }
75
- return undefined;
76
- }
77
- export function mergeHookSummary(transcript, hookData) {
78
- const repeatedFailures = new Map();
79
- for (const failure of [...transcript.repeatedFailures, ...hookData.repeatedFailures]) {
80
- const key = `${failure.toolName}:${failure.purpose || ""}`;
81
- const existing = repeatedFailures.get(key);
82
- repeatedFailures.set(key, {
83
- toolName: failure.toolName,
84
- purpose: failure.purpose,
85
- count: Math.max(existing?.count || 0, failure.count)
86
- });
87
- }
88
- return {
89
- ...transcript,
90
- toolCalls: Math.max(transcript.toolCalls, hookData.toolCalls),
91
- failedToolResults: Math.max(transcript.failedToolResults, hookData.failedToolResults),
92
- repeatedFailures: [...repeatedFailures.values()].filter((failure) => failure.count >= 2),
93
- compactionEvents: Math.max(transcript.compactionEvents, hookData.compactionEvents),
94
- latestTimestamp: transcript.latestTimestamp && hookData.latestTimestamp
95
- ? transcript.latestTimestamp > hookData.latestTimestamp
96
- ? transcript.latestTimestamp
97
- : hookData.latestTimestamp
98
- : transcript.latestTimestamp || hookData.latestTimestamp
99
- };
100
- }
101
- function classifyToolPurpose(toolName, input) {
102
- if (toolName !== "Bash") {
103
- return undefined;
104
- }
105
- const command = stringField(asRecord(input)?.command);
106
- return command && TEST_COMMAND_RE.test(command) ? "tests" : undefined;
107
- }
108
- function countBatchTools(root) {
109
- const values = [root.tools, root.tool_uses, root.toolUses, root.results, root.tool_results, root.toolResults];
110
- for (const value of values) {
111
- if (Array.isArray(value)) {
112
- return value.length;
113
- }
114
- const count = numberField(value);
115
- if (count !== undefined) {
116
- return Math.max(0, Math.floor(count));
117
- }
118
- }
119
- return 0;
120
- }
121
- function safeToolName(toolName) {
122
- if (!toolName) {
123
- return "tool";
124
- }
125
- return /^[A-Za-z][A-Za-z0-9_-]{0,32}$/u.test(toolName) ? toolName : "tool";
126
- }
1
+ export { SAFE_HOOK_EVENTS, parseHookPayload } from "./hook-payload.js";
2
+ export { mergeHookSummary } from "./hook-summary.js";
127
3
  //# sourceMappingURL=hooks.js.map
package/dist/hooks.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"hooks.js","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"hooks.js","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACvE,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC"}
package/dist/paths.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  export declare const APP_DIR_NAME = "bb-cc-lite";
2
2
  export declare function appHome(homeDir?: string): string;
3
3
  export declare function eventStorePath(): string;
4
+ export declare function baselinePath(homeDir?: string): string;
4
5
  export declare function backupDir(homeDir?: string): string;
5
6
  export declare function pricingCachePath(): string;
6
7
  export declare function cliPath(): string;
package/dist/paths.js CHANGED
@@ -9,6 +9,9 @@ export function appHome(homeDir = homedir()) {
9
9
  export function eventStorePath() {
10
10
  return process.env.BB_CC_LITE_STORE || join(appHome(), "events.json");
11
11
  }
12
+ export function baselinePath(homeDir) {
13
+ return join(appHome(homeDir), "baseline.json");
14
+ }
12
15
  export function backupDir(homeDir) {
13
16
  return join(appHome(homeDir), "backups");
14
17
  }
package/dist/paths.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"paths.js","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,MAAM,CAAC,MAAM,YAAY,GAAG,YAAY,CAAC;AAEzC,MAAM,UAAU,OAAO,CAAC,OAAO,GAAG,OAAO,EAAE;IACzC,OAAO,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,IAAI,CAAC,OAAO,EAAE,SAAS,EAAE,YAAY,CAAC,CAAC;AAC/E,CAAC;AAED,MAAM,UAAU,cAAc;IAC5B,OAAO,OAAO,CAAC,GAAG,CAAC,gBAAgB,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,aAAa,CAAC,CAAC;AACxE,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,OAAgB;IACxC,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,SAAS,CAAC,CAAC;AAC3C,CAAC;AAED,MAAM,UAAU,gBAAgB;IAC9B,OAAO,OAAO,CAAC,GAAG,CAAC,wBAAwB,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,sBAAsB,CAAC,CAAC;AACzF,CAAC;AAED,MAAM,UAAU,OAAO;IACrB,OAAO,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC;QACzD,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,CAAC;QAC5D,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;AACrC,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,KAAyB;IACjD,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AACvE,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,KAAa;IACtC,OAAO,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,EAAE,OAAO,CAAC,GAAG,CAAC;AAC/C,CAAC"}
1
+ {"version":3,"file":"paths.js","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,MAAM,CAAC,MAAM,YAAY,GAAG,YAAY,CAAC;AAEzC,MAAM,UAAU,OAAO,CAAC,OAAO,GAAG,OAAO,EAAE;IACzC,OAAO,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,IAAI,CAAC,OAAO,EAAE,SAAS,EAAE,YAAY,CAAC,CAAC;AAC/E,CAAC;AAED,MAAM,UAAU,cAAc;IAC5B,OAAO,OAAO,CAAC,GAAG,CAAC,gBAAgB,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,aAAa,CAAC,CAAC;AACxE,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,OAAgB;IAC3C,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,eAAe,CAAC,CAAC;AACjD,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,OAAgB;IACxC,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,SAAS,CAAC,CAAC;AAC3C,CAAC;AAED,MAAM,UAAU,gBAAgB;IAC9B,OAAO,OAAO,CAAC,GAAG,CAAC,wBAAwB,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,sBAAsB,CAAC,CAAC;AACzF,CAAC;AAED,MAAM,UAAU,OAAO;IACrB,OAAO,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC;QACzD,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,CAAC;QAC5D,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;AACrC,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,KAAyB;IACjD,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AACvE,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,KAAa;IACtC,OAAO,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,EAAE,OAAO,CAAC,GAAG,CAAC;AAC/C,CAAC"}
@@ -1,2 +1,2 @@
1
- import type { Decision } from "./types.js";
2
- export declare function renderStatusLine(decision: Decision, width?: number): string;
1
+ import type { DecisionPresentation } from "./decision-presentation.js";
2
+ export declare function renderStatusLine(decision: DecisionPresentation, width?: number): string;
package/dist/renderer.js CHANGED
@@ -17,18 +17,26 @@ export function renderStatusLine(decision, width) {
17
17
  }
18
18
  function defaultCandidates(decision) {
19
19
  const evidence = decision.evidence.map((item) => item.label);
20
+ const headline = decision.diagnosis || decision.primaryEvidence;
21
+ const badge = decision.baselineNote || "";
22
+ if (decision.diagnosis) {
23
+ return [
24
+ [`bb: ${decision.state}`, headline, badge, decision.action],
25
+ [`bb: ${decision.state}`, headline, decision.action],
26
+ [`bb: ${decision.state}`, headline]
27
+ ];
28
+ }
20
29
  return [
21
- [`bb: ${decision.state}`, ...evidence, decision.action],
22
- [`bb: ${decision.state}`, decision.primaryEvidence, decision.action],
23
- [`bb: ${decision.state}`, decision.action]
30
+ [`bb: ${decision.state}`, headline, badge, ...evidence.filter((item) => item !== headline), decision.action],
31
+ [`bb: ${decision.state}`, headline, decision.action],
32
+ [`bb: ${decision.state}`, headline]
24
33
  ];
25
34
  }
26
35
  function stopCandidates(decision) {
27
36
  const costEvidence = decision.evidence.filter((item) => item.detail).map((item) => item.label);
28
- const fullWhy = decision.impact && decision.impact !== decision.primaryEvidence
29
- ? `why: ${decision.primaryEvidence}; ${decision.impact}`
30
- : `why: ${decision.primaryEvidence}`;
31
- const shortWhy = `why: ${decision.primaryEvidence}`;
37
+ const headline = decision.diagnosis || decision.primaryEvidence;
38
+ const fullWhy = decision.impact && decision.impact !== headline ? `why: ${headline}; ${decision.impact}` : `why: ${headline}`;
39
+ const shortWhy = `why: ${headline}`;
32
40
  const action = `do: ${decision.action}`;
33
41
  return [
34
42
  [`bb: ${decision.state}`, fullWhy, ...costEvidence, action],