bb-cc-lite 0.1.9 → 0.1.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +78 -9
- package/dist/cli.js +42 -14
- package/dist/cli.js.map +1 -1
- package/dist/decision-presentation.d.ts +2 -1
- package/dist/decision-presentation.js +3 -2
- package/dist/decision-presentation.js.map +1 -1
- package/dist/doctor.js +3 -1
- package/dist/doctor.js.map +1 -1
- package/dist/event-store-persistence.d.ts +8 -0
- package/dist/event-store-persistence.js +132 -5
- package/dist/event-store-persistence.js.map +1 -1
- package/dist/event-store-queries.d.ts +7 -0
- package/dist/event-store-queries.js +52 -0
- package/dist/event-store-queries.js.map +1 -1
- package/dist/failure-episodes.d.ts +6 -2
- package/dist/failure-episodes.js +8 -8
- package/dist/failure-episodes.js.map +1 -1
- package/dist/feedback-outcomes.d.ts +9 -0
- package/dist/feedback-outcomes.js +190 -0
- package/dist/feedback-outcomes.js.map +1 -0
- package/dist/feedback-policy.d.ts +43 -0
- package/dist/feedback-policy.js +245 -0
- package/dist/feedback-policy.js.map +1 -0
- package/dist/hook-control.d.ts +11 -0
- package/dist/hook-control.js +145 -0
- package/dist/hook-control.js.map +1 -0
- package/dist/hook-payload.d.ts +4 -1
- package/dist/hook-payload.js +7 -3
- package/dist/hook-payload.js.map +1 -1
- package/dist/hook-response.d.ts +3 -0
- package/dist/hook-response.js +27 -0
- package/dist/hook-response.js.map +1 -0
- package/dist/hook-summary.d.ts +7 -0
- package/dist/hook-summary.js +7 -0
- package/dist/hook-summary.js.map +1 -1
- package/dist/memory-lessons.d.ts +54 -0
- package/dist/memory-lessons.js +211 -0
- package/dist/memory-lessons.js.map +1 -0
- package/dist/project-config.d.ts +6 -0
- package/dist/project-config.js +93 -0
- package/dist/project-config.js.map +1 -0
- package/dist/renderer.js +7 -5
- package/dist/renderer.js.map +1 -1
- package/dist/settings.d.ts +3 -0
- package/dist/settings.js +56 -15
- package/dist/settings.js.map +1 -1
- package/dist/statusline.js +17 -3
- package/dist/statusline.js.map +1 -1
- package/dist/store.d.ts +13 -1
- package/dist/store.js +76 -20
- package/dist/store.js.map +1 -1
- package/dist/tool-metadata.d.ts +2 -0
- package/dist/tool-metadata.js +20 -2
- package/dist/tool-metadata.js.map +1 -1
- package/dist/transcript.d.ts +7 -2
- package/dist/transcript.js +7 -7
- package/dist/transcript.js.map +1 -1
- package/dist/types.d.ts +23 -1
- package/dist/why.d.ts +15 -2
- package/dist/why.js +130 -5
- package/dist/why.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { chmod, mkdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { appHome } from "./paths.js";
|
|
4
|
+
export const LESSON_MEMORY_DIR_NAME = "project-lessons";
|
|
5
|
+
export const LESSON_MEMORY_SCHEMA = "bb-cc-lite.lesson-memory.v1";
|
|
6
|
+
export const LESSON_CARD_SCHEMA = "bb-cc-lite.lesson-card.v1";
|
|
7
|
+
export const LESSON_MEMORY_VERSION = 1;
|
|
8
|
+
const LESSON_MEMORY_MAX_BYTES = 64 * 1024;
|
|
9
|
+
const LESSON_DECAY_MS = 30 * 24 * 60 * 60 * 1000;
|
|
10
|
+
export function lessonMemoryPath(options) {
|
|
11
|
+
assertProjectKey(options.projectKey);
|
|
12
|
+
return join(options.appHomePath ?? appHome(options.homeDir), LESSON_MEMORY_DIR_NAME, `${options.projectKey}.json`);
|
|
13
|
+
}
|
|
14
|
+
export async function recordLessonFromSummary(options) {
|
|
15
|
+
const candidate = lessonCandidate(options.projectKey, options.summary, options.now ?? new Date());
|
|
16
|
+
if (!candidate) {
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
const memoryPath = lessonMemoryPath(options);
|
|
20
|
+
const existing = await readLessonMemory({ ...options, projectKey: options.projectKey });
|
|
21
|
+
const lessons = existing?.lessons.filter((lesson) => lesson.lessonId !== candidate.lessonId) ?? [];
|
|
22
|
+
const previous = existing?.lessons.find((lesson) => lesson.lessonId === candidate.lessonId);
|
|
23
|
+
const next = {
|
|
24
|
+
...candidate,
|
|
25
|
+
createdAt: previous?.createdAt || candidate.createdAt,
|
|
26
|
+
evidenceCounts: {
|
|
27
|
+
failures: Math.max(previous?.evidenceCounts.failures || 0, candidate.evidenceCounts.failures),
|
|
28
|
+
sessions: Math.max(previous?.evidenceCounts.sessions || 0, candidate.evidenceCounts.sessions)
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
const memory = {
|
|
32
|
+
schema: LESSON_MEMORY_SCHEMA,
|
|
33
|
+
version: LESSON_MEMORY_VERSION,
|
|
34
|
+
projectKey: options.projectKey,
|
|
35
|
+
updatedAt: next.updatedAt,
|
|
36
|
+
lessons: [...lessons, next].sort((left, right) => left.lessonId.localeCompare(right.lessonId))
|
|
37
|
+
};
|
|
38
|
+
await writeLessonMemory(memory, memoryPath);
|
|
39
|
+
return next;
|
|
40
|
+
}
|
|
41
|
+
export async function lessonContextForProject(options) {
|
|
42
|
+
const now = options.now ?? new Date();
|
|
43
|
+
const memory = await readLessonMemory(options);
|
|
44
|
+
if (!memory) {
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
const active = memory.lessons.filter((lesson) => !isExpired(lesson, now));
|
|
48
|
+
if (active.length !== memory.lessons.length) {
|
|
49
|
+
await writeLessonMemory({ ...memory, updatedAt: now.toISOString(), lessons: active }, lessonMemoryPath(options));
|
|
50
|
+
}
|
|
51
|
+
const selected = active
|
|
52
|
+
.filter((lesson) => lesson.confidence !== "low" && lesson.evidenceCounts.failures >= 3)
|
|
53
|
+
.sort((left, right) => right.evidenceCounts.failures - left.evidenceCounts.failures)[0];
|
|
54
|
+
if (!selected) {
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
return lessonMessage(selected.wordingKey);
|
|
58
|
+
}
|
|
59
|
+
export async function clearLessonMemory(options = {}) {
|
|
60
|
+
await rm(join(options.appHomePath ?? appHome(options.homeDir), LESSON_MEMORY_DIR_NAME), { recursive: true, force: true });
|
|
61
|
+
}
|
|
62
|
+
async function readLessonMemory(options) {
|
|
63
|
+
try {
|
|
64
|
+
const path = lessonMemoryPath(options);
|
|
65
|
+
const fileStat = await stat(path);
|
|
66
|
+
if (!fileStat.isFile() || fileStat.size > LESSON_MEMORY_MAX_BYTES) {
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
const parsed = JSON.parse(await readFile(path, "utf8"));
|
|
70
|
+
return isProjectLessonMemory(parsed, options.projectKey) ? parsed : undefined;
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
async function writeLessonMemory(memory, path) {
|
|
77
|
+
await mkdir(dirname(path), { recursive: true, mode: 0o700 });
|
|
78
|
+
const tempPath = `${path}.${process.pid}.${Date.now()}.tmp`;
|
|
79
|
+
await writeFile(tempPath, `${JSON.stringify(memory, null, 2)}\n`, { encoding: "utf8", mode: 0o600 });
|
|
80
|
+
await chmod(tempPath, 0o600);
|
|
81
|
+
await rename(tempPath, path);
|
|
82
|
+
}
|
|
83
|
+
function lessonCandidate(projectKey, summary, now) {
|
|
84
|
+
assertProjectKey(projectKey);
|
|
85
|
+
const strongest = strongestValidationFailure(summary);
|
|
86
|
+
if (!strongest || strongest.failures < 2) {
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
89
|
+
const timestamp = now.toISOString();
|
|
90
|
+
const confidence = strongest.failures >= 3 ? "high" : "low";
|
|
91
|
+
return {
|
|
92
|
+
schema: LESSON_CARD_SCHEMA,
|
|
93
|
+
lessonId: `validation_repeated:${strongest.safeCategory}`,
|
|
94
|
+
projectKey,
|
|
95
|
+
reasonCode: "validation_repeated",
|
|
96
|
+
safeCategory: strongest.safeCategory,
|
|
97
|
+
confidence,
|
|
98
|
+
evidenceCounts: {
|
|
99
|
+
failures: strongest.failures,
|
|
100
|
+
sessions: 1
|
|
101
|
+
},
|
|
102
|
+
createdAt: timestamp,
|
|
103
|
+
updatedAt: timestamp,
|
|
104
|
+
decayAt: new Date(now.getTime() + LESSON_DECAY_MS).toISOString(),
|
|
105
|
+
wordingKey: `validation_repeated:${strongest.safeCategory}`
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
function strongestValidationFailure(summary) {
|
|
109
|
+
const blindRetry = summary.blindRetry;
|
|
110
|
+
const blindRetryCategory = lessonCategory(blindRetry?.category);
|
|
111
|
+
const blindRetryCandidate = blindRetry && blindRetryCategory
|
|
112
|
+
? {
|
|
113
|
+
safeCategory: blindRetryCategory,
|
|
114
|
+
failures: blindRetry.blindRetryFailureCount
|
|
115
|
+
}
|
|
116
|
+
: undefined;
|
|
117
|
+
const repeatedCandidate = summary.repeatedFailures
|
|
118
|
+
.flatMap((failure) => {
|
|
119
|
+
const category = failure.toolName === "Bash" ? lessonCategory(failure.purpose) : undefined;
|
|
120
|
+
return category ? [{ safeCategory: category, failures: failure.count }] : [];
|
|
121
|
+
})
|
|
122
|
+
.sort((left, right) => right.failures - left.failures)[0];
|
|
123
|
+
if (blindRetryCandidate && repeatedCandidate) {
|
|
124
|
+
return blindRetryCandidate.failures >= repeatedCandidate.failures ? blindRetryCandidate : repeatedCandidate;
|
|
125
|
+
}
|
|
126
|
+
return blindRetryCandidate || repeatedCandidate;
|
|
127
|
+
}
|
|
128
|
+
function lessonCategory(value) {
|
|
129
|
+
return value === "tests" || value === "lint" || value === "typecheck" || value === "build" ? value : undefined;
|
|
130
|
+
}
|
|
131
|
+
function isExpired(lesson, now) {
|
|
132
|
+
const decayAtMs = Date.parse(lesson.decayAt);
|
|
133
|
+
return !Number.isFinite(decayAtMs) || decayAtMs <= now.getTime();
|
|
134
|
+
}
|
|
135
|
+
function lessonMessage(wordingKey) {
|
|
136
|
+
if (wordingKey.startsWith("validation_repeated:")) {
|
|
137
|
+
return "bb-cc-lite lesson: similar validation retries in this project rarely recovered after repeated failures. Inspect the first failure, make one targeted fix, then run one focused check.";
|
|
138
|
+
}
|
|
139
|
+
return undefined;
|
|
140
|
+
}
|
|
141
|
+
function isProjectLessonMemory(value, projectKey) {
|
|
142
|
+
const root = asRecord(value);
|
|
143
|
+
if (!root ||
|
|
144
|
+
!hasOnlyKeys(root, MEMORY_KEYS) ||
|
|
145
|
+
root.schema !== LESSON_MEMORY_SCHEMA ||
|
|
146
|
+
root.version !== LESSON_MEMORY_VERSION ||
|
|
147
|
+
root.projectKey !== projectKey ||
|
|
148
|
+
!isIsoTimestamp(root.updatedAt) ||
|
|
149
|
+
!Array.isArray(root.lessons)) {
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
return root.lessons.every((lesson) => isLessonCard(lesson, projectKey));
|
|
153
|
+
}
|
|
154
|
+
function isLessonCard(value, projectKey) {
|
|
155
|
+
const root = asRecord(value);
|
|
156
|
+
if (!root ||
|
|
157
|
+
!hasOnlyKeys(root, CARD_KEYS) ||
|
|
158
|
+
root.schema !== LESSON_CARD_SCHEMA ||
|
|
159
|
+
root.projectKey !== projectKey ||
|
|
160
|
+
typeof root.lessonId !== "string" ||
|
|
161
|
+
typeof root.reasonCode !== "string" ||
|
|
162
|
+
!lessonCategory(String(root.safeCategory)) ||
|
|
163
|
+
!isConfidence(root.confidence) ||
|
|
164
|
+
typeof root.wordingKey !== "string" ||
|
|
165
|
+
!isIsoTimestamp(root.createdAt) ||
|
|
166
|
+
!isIsoTimestamp(root.updatedAt) ||
|
|
167
|
+
!isIsoTimestamp(root.decayAt)) {
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
const counts = asRecord(root.evidenceCounts);
|
|
171
|
+
return Boolean(counts &&
|
|
172
|
+
hasOnlyKeys(counts, EVIDENCE_KEYS) &&
|
|
173
|
+
isNonNegativeInteger(counts.failures) &&
|
|
174
|
+
isNonNegativeInteger(counts.sessions));
|
|
175
|
+
}
|
|
176
|
+
function asRecord(value) {
|
|
177
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : undefined;
|
|
178
|
+
}
|
|
179
|
+
function hasOnlyKeys(value, allowed) {
|
|
180
|
+
return Object.keys(value).every((key) => allowed.has(key));
|
|
181
|
+
}
|
|
182
|
+
function isIsoTimestamp(value) {
|
|
183
|
+
return typeof value === "string" && Number.isFinite(Date.parse(value));
|
|
184
|
+
}
|
|
185
|
+
function isConfidence(value) {
|
|
186
|
+
return value === "low" || value === "medium" || value === "high";
|
|
187
|
+
}
|
|
188
|
+
function isNonNegativeInteger(value) {
|
|
189
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 0;
|
|
190
|
+
}
|
|
191
|
+
function assertProjectKey(projectKey) {
|
|
192
|
+
if (!/^[a-f0-9]{64}$/u.test(projectKey)) {
|
|
193
|
+
throw new Error("invalid project key");
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
const MEMORY_KEYS = new Set(["schema", "version", "projectKey", "updatedAt", "lessons"]);
|
|
197
|
+
const CARD_KEYS = new Set([
|
|
198
|
+
"schema",
|
|
199
|
+
"lessonId",
|
|
200
|
+
"projectKey",
|
|
201
|
+
"reasonCode",
|
|
202
|
+
"safeCategory",
|
|
203
|
+
"confidence",
|
|
204
|
+
"evidenceCounts",
|
|
205
|
+
"createdAt",
|
|
206
|
+
"updatedAt",
|
|
207
|
+
"decayAt",
|
|
208
|
+
"wordingKey"
|
|
209
|
+
]);
|
|
210
|
+
const EVIDENCE_KEYS = new Set(["failures", "sessions"]);
|
|
211
|
+
//# sourceMappingURL=memory-lessons.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory-lessons.js","sourceRoot":"","sources":["../src/memory-lessons.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export type ValidationCommandCategory = "tests" | "lint" | "typecheck" | "build";
|
|
2
|
+
export interface ProjectConfig {
|
|
3
|
+
validationCommands: Partial<Record<ValidationCommandCategory, string[]>>;
|
|
4
|
+
}
|
|
5
|
+
export declare function loadProjectConfig(cwd: string | undefined): Promise<ProjectConfig>;
|
|
6
|
+
export declare function classifyConfiguredValidationCommand(command: string | undefined, config: ProjectConfig | undefined): ValidationCommandCategory | undefined;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { readFile, stat } from "node:fs/promises";
|
|
2
|
+
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
3
|
+
const CONFIG_FILE = ".bb-cc-lite.json";
|
|
4
|
+
const MAX_SEARCH_DEPTH = 20;
|
|
5
|
+
const MAX_CONFIG_BYTES = 16 * 1024;
|
|
6
|
+
const MAX_COMMANDS_PER_CATEGORY = 20;
|
|
7
|
+
const MAX_COMMAND_LENGTH = 200;
|
|
8
|
+
const VALIDATION_CATEGORIES = ["tests", "lint", "typecheck", "build"];
|
|
9
|
+
const EMPTY_CONFIG = { validationCommands: {} };
|
|
10
|
+
export async function loadProjectConfig(cwd) {
|
|
11
|
+
const configPath = await findProjectConfig(cwd);
|
|
12
|
+
if (!configPath) {
|
|
13
|
+
return EMPTY_CONFIG;
|
|
14
|
+
}
|
|
15
|
+
try {
|
|
16
|
+
const fileStat = await stat(configPath);
|
|
17
|
+
if (!fileStat.isFile() || fileStat.size > MAX_CONFIG_BYTES) {
|
|
18
|
+
return EMPTY_CONFIG;
|
|
19
|
+
}
|
|
20
|
+
return sanitizeConfig(JSON.parse(await readFile(configPath, "utf8")));
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return EMPTY_CONFIG;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export function classifyConfiguredValidationCommand(command, config) {
|
|
27
|
+
const normalizedCommand = normalizeCommand(command);
|
|
28
|
+
if (!normalizedCommand || !config) {
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
for (const category of VALIDATION_CATEGORIES) {
|
|
32
|
+
for (const configured of config.validationCommands[category] || []) {
|
|
33
|
+
const normalizedConfigured = normalizeCommand(configured);
|
|
34
|
+
if (normalizedConfigured &&
|
|
35
|
+
(normalizedCommand === normalizedConfigured || normalizedCommand.startsWith(`${normalizedConfigured} `))) {
|
|
36
|
+
return category;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
async function findProjectConfig(cwd) {
|
|
43
|
+
if (!cwd) {
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
let current = isAbsolute(cwd) ? cwd : resolve(cwd);
|
|
47
|
+
for (let depth = 0; depth < MAX_SEARCH_DEPTH; depth += 1) {
|
|
48
|
+
const candidate = join(current, CONFIG_FILE);
|
|
49
|
+
try {
|
|
50
|
+
const fileStat = await stat(candidate);
|
|
51
|
+
if (fileStat.isFile()) {
|
|
52
|
+
return candidate;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
// Keep walking upward; invalid/missing config must not break statusline.
|
|
57
|
+
}
|
|
58
|
+
const parent = dirname(current);
|
|
59
|
+
if (parent === current) {
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
current = parent;
|
|
63
|
+
}
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
function sanitizeConfig(value) {
|
|
67
|
+
if (!isRecord(value) || !isRecord(value.validationCommands)) {
|
|
68
|
+
return EMPTY_CONFIG;
|
|
69
|
+
}
|
|
70
|
+
const validationCommands = {};
|
|
71
|
+
for (const category of VALIDATION_CATEGORIES) {
|
|
72
|
+
const commands = value.validationCommands[category];
|
|
73
|
+
if (!Array.isArray(commands)) {
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
const safeCommands = commands
|
|
77
|
+
.flatMap((command) => (typeof command === "string" ? [normalizeCommand(command)] : []))
|
|
78
|
+
.filter((command) => typeof command === "string" && command.length > 0 && command.length <= MAX_COMMAND_LENGTH)
|
|
79
|
+
.slice(0, MAX_COMMANDS_PER_CATEGORY);
|
|
80
|
+
if (safeCommands.length > 0) {
|
|
81
|
+
validationCommands[category] = safeCommands;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return { validationCommands };
|
|
85
|
+
}
|
|
86
|
+
function normalizeCommand(value) {
|
|
87
|
+
const normalized = value?.trim().replace(/\s+/gu, " ");
|
|
88
|
+
return normalized || undefined;
|
|
89
|
+
}
|
|
90
|
+
function isRecord(value) {
|
|
91
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=project-config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project-config.js","sourceRoot":"","sources":["../src/project-config.ts"],"names":[],"mappings":"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"}
|
package/dist/renderer.js
CHANGED
|
@@ -19,23 +19,25 @@ function defaultCandidates(decision) {
|
|
|
19
19
|
const evidence = decision.evidence.map((item) => item.label);
|
|
20
20
|
const headline = decision.diagnosis || decision.primaryEvidence;
|
|
21
21
|
const badge = decision.baselineNote || "";
|
|
22
|
+
const feedbackNote = decision.feedbackNote || "";
|
|
22
23
|
if (decision.diagnosis) {
|
|
23
24
|
return [
|
|
24
|
-
[`bb: ${decision.state}`, headline, badge, decision.action],
|
|
25
|
-
[`bb: ${decision.state}`, headline, decision.action],
|
|
25
|
+
[`bb: ${decision.state}`, headline, badge, feedbackNote, decision.action],
|
|
26
|
+
[`bb: ${decision.state}`, headline, feedbackNote, decision.action],
|
|
26
27
|
[`bb: ${decision.state}`, headline]
|
|
27
28
|
];
|
|
28
29
|
}
|
|
29
30
|
return [
|
|
30
|
-
[`bb: ${decision.state}`, headline, badge, ...evidence.filter((item) => item !== headline), decision.action],
|
|
31
|
-
[`bb: ${decision.state}`, headline, decision.action],
|
|
31
|
+
[`bb: ${decision.state}`, headline, badge, feedbackNote, ...evidence.filter((item) => item !== headline), decision.action],
|
|
32
|
+
[`bb: ${decision.state}`, headline, feedbackNote, decision.action],
|
|
32
33
|
[`bb: ${decision.state}`, headline]
|
|
33
34
|
];
|
|
34
35
|
}
|
|
35
36
|
function stopCandidates(decision) {
|
|
36
37
|
const costEvidence = decision.evidence.filter((item) => item.detail).map((item) => item.label);
|
|
37
38
|
const headline = decision.diagnosis || decision.primaryEvidence;
|
|
38
|
-
const
|
|
39
|
+
const feedbackNote = decision.feedbackNote ? `; ${decision.feedbackNote}` : "";
|
|
40
|
+
const fullWhy = decision.impact && decision.impact !== headline ? `why: ${headline}; ${decision.impact}${feedbackNote}` : `why: ${headline}${feedbackNote}`;
|
|
39
41
|
const shortWhy = `why: ${headline}`;
|
|
40
42
|
const action = `do: ${decision.action}`;
|
|
41
43
|
return [
|
package/dist/renderer.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"renderer.js","sourceRoot":"","sources":["../src/renderer.ts"],"names":[],"mappings":"AAEA,MAAM,aAAa,GAAG,GAAG,CAAC;AAC1B,MAAM,KAAK,GAAG,WAAW,CAAC;AAC1B,MAAM,MAAM,GAAkD;IAC5D,OAAO,EAAE,YAAY;IACrB,OAAO,EAAE,YAAY;IACrB,IAAI,EAAE,cAAc;CACrB,CAAC;AAEF,MAAM,UAAU,gBAAgB,CAAC,QAA8B,EAAE,KAAc;IAC7E,MAAM,aAAa,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,IAAI,aAAa,CAAC,CAAC,CAAC;IACvE,MAAM,UAAU,GAAG,QAAQ,CAAC,KAAK,KAAK,MAAM,CAAC,CAAC,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,iBAAiB,CAAC,QAAQ,CAAC,CAAC;IAEtG,KAAK,MAAM,SAAS,IAAI,UAAU,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC;QAClD,IAAI,aAAa,CAAC,SAAS,CAAC,IAAI,aAAa,EAAE,CAAC;YAC9C,OAAO,aAAa,CAAC,SAAS,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC;QAClD,CAAC;IACH,CAAC;IAED,OAAO,aAAa,CAAC,QAAQ,CAAC,SAAS,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC,EAAE,aAAa,CAAC,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC;AAC3H,CAAC;AAED,SAAS,iBAAiB,CAAC,QAA8B;IACvD,MAAM,QAAQ,GAAG,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC7D,MAAM,QAAQ,GAAG,QAAQ,CAAC,SAAS,IAAI,QAAQ,CAAC,eAAe,CAAC;IAChE,MAAM,KAAK,GAAG,QAAQ,CAAC,YAAY,IAAI,EAAE,CAAC;IAC1C,IAAI,QAAQ,CAAC,SAAS,EAAE,CAAC;QACvB,OAAO;YACL,CAAC,OAAO,QAAQ,CAAC,KAAK,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"renderer.js","sourceRoot":"","sources":["../src/renderer.ts"],"names":[],"mappings":"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"}
|
package/dist/settings.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export type SettingsScope = "local" | "project" | "user";
|
|
2
|
+
export type InstallMode = "observe" | "coach" | "guard";
|
|
2
3
|
export interface SettingsTarget {
|
|
3
4
|
scope: SettingsScope;
|
|
4
5
|
settingsPath: string;
|
|
@@ -12,6 +13,8 @@ export interface InstallOptions {
|
|
|
12
13
|
replace?: boolean;
|
|
13
14
|
cliFilePath?: string;
|
|
14
15
|
hooks?: boolean;
|
|
16
|
+
mode?: InstallMode;
|
|
17
|
+
learn?: boolean;
|
|
15
18
|
}
|
|
16
19
|
export interface UninstallOptions {
|
|
17
20
|
scope?: SettingsScope;
|
package/dist/settings.js
CHANGED
|
@@ -34,6 +34,8 @@ export async function installStatusLine(options = {}) {
|
|
|
34
34
|
const targetRead = await readSettings(target.settingsPath);
|
|
35
35
|
const existing = targetRead.settings.statusLine;
|
|
36
36
|
const existingHooks = targetRead.settings.hooks;
|
|
37
|
+
const mode = options.mode || "coach";
|
|
38
|
+
const learn = options.learn !== false;
|
|
37
39
|
if (existing && !isBbStatusLine(existing) && !options.replace) {
|
|
38
40
|
return {
|
|
39
41
|
status: "refused",
|
|
@@ -41,14 +43,14 @@ export async function installStatusLine(options = {}) {
|
|
|
41
43
|
message: `Existing Claude statusLine found in ${describeSettingsTarget(target)}; pass --replace to replace it with bb-cc-lite.`
|
|
42
44
|
};
|
|
43
45
|
}
|
|
44
|
-
const launchers = await ensureRuntimeLaunchers(options.cliFilePath || cliPath(), target.homeDir);
|
|
46
|
+
const launchers = await ensureRuntimeLaunchers(options.cliFilePath || cliPath(), target.homeDir, { learn });
|
|
45
47
|
const statusLine = {
|
|
46
48
|
type: "command",
|
|
47
49
|
command: quoteShell(launchers.statusline),
|
|
48
50
|
padding: 0
|
|
49
51
|
};
|
|
50
52
|
if (existing && isBbStatusLine(existing)) {
|
|
51
|
-
const afterHooks = options.hooks ? mergeBbHooks(existingHooks, launchers.hook) : existingHooks;
|
|
53
|
+
const afterHooks = options.hooks ? mergeBbHooks(existingHooks, launchers.hook, mode, learn) : existingHooks;
|
|
52
54
|
const shouldWriteStatusLine = JSON.stringify(existing) !== JSON.stringify(statusLine);
|
|
53
55
|
const shouldWriteHooks = options.hooks && JSON.stringify(existingHooks) !== JSON.stringify(afterHooks);
|
|
54
56
|
if (shouldWriteStatusLine || shouldWriteHooks) {
|
|
@@ -68,7 +70,7 @@ export async function installStatusLine(options = {}) {
|
|
|
68
70
|
target,
|
|
69
71
|
command: statusLine.command,
|
|
70
72
|
backupId: installId,
|
|
71
|
-
message: `bb-cc-lite statusLine is already installed in ${describeSettingsTarget(target)}; ${options.hooks ?
|
|
73
|
+
message: `bb-cc-lite statusLine is already installed in ${describeSettingsTarget(target)}; ${options.hooks ? `repaired ${hooksLabel(mode)} and ` : ""}refreshed runtime launcher.`
|
|
72
74
|
};
|
|
73
75
|
}
|
|
74
76
|
return {
|
|
@@ -83,7 +85,7 @@ export async function installStatusLine(options = {}) {
|
|
|
83
85
|
const afterSettings = {
|
|
84
86
|
...beforeSettings,
|
|
85
87
|
statusLine,
|
|
86
|
-
...(options.hooks ? { hooks: mergeBbHooks(beforeSettings.hooks, launchers.hook) } : {})
|
|
88
|
+
...(options.hooks ? { hooks: mergeBbHooks(beforeSettings.hooks, launchers.hook, mode, learn) } : {})
|
|
87
89
|
};
|
|
88
90
|
const afterRaw = `${JSON.stringify(afterSettings, null, 2)}\n`;
|
|
89
91
|
const installId = randomUUID();
|
|
@@ -95,8 +97,8 @@ export async function installStatusLine(options = {}) {
|
|
|
95
97
|
command: statusLine.command,
|
|
96
98
|
backupId: installId,
|
|
97
99
|
message: existing
|
|
98
|
-
? `Replaced existing Claude statusLine with bb-cc-lite${options.hooks ?
|
|
99
|
-
: `Installed bb-cc-lite statusLine${options.hooks ?
|
|
100
|
+
? `Replaced existing Claude statusLine with bb-cc-lite${options.hooks ? ` and ${hooksLabel(mode)}` : ""} in ${describeSettingsTarget(target)}. Previous settings were backed up.`
|
|
101
|
+
: `Installed bb-cc-lite statusLine${options.hooks ? ` and ${hooksLabel(mode)}` : ""} in ${describeSettingsTarget(target)}.`
|
|
100
102
|
};
|
|
101
103
|
}
|
|
102
104
|
export async function uninstallStatusLine(options = {}) {
|
|
@@ -201,18 +203,19 @@ export function isBbStatusLine(value) {
|
|
|
201
203
|
const normalizedCommand = command.replaceAll("'", "");
|
|
202
204
|
return commandReferencesBbCcLite(command) || normalizedCommand.includes(join(appHome(), "bin", "statusline"));
|
|
203
205
|
}
|
|
204
|
-
async function ensureRuntimeLaunchers(cliFilePath, homeDir) {
|
|
206
|
+
async function ensureRuntimeLaunchers(cliFilePath, homeDir, options = {}) {
|
|
205
207
|
const home = appHome(homeDir);
|
|
206
208
|
const binDir = join(home, "bin");
|
|
207
209
|
const statuslinePath = join(binDir, "statusline");
|
|
208
210
|
const hookPath = join(binDir, "hook");
|
|
209
211
|
const stableCliPath = await copyRuntime(cliFilePath, home);
|
|
212
|
+
const learningExports = options.learn === false ? "export BB_CC_LITE_AUTO_LEARN=0\nexport BB_CC_LITE_LESSON_MEMORY=0\n" : "";
|
|
210
213
|
await mkdir(binDir, { recursive: true, mode: 0o700 });
|
|
211
|
-
await writeFile(statuslinePath, `#!/bin/sh\nexport BB_CC_LITE_HOME=${quoteShell(home)}\
|
|
214
|
+
await writeFile(statuslinePath, `#!/bin/sh\nexport BB_CC_LITE_HOME=${quoteShell(home)}\n${learningExports}exec ${quoteShell(process.execPath)} ${quoteShell(stableCliPath)} statusline "$@"\n`, {
|
|
212
215
|
encoding: "utf8",
|
|
213
216
|
mode: 0o700
|
|
214
217
|
});
|
|
215
|
-
await writeFile(hookPath, `#!/bin/sh\nexport BB_CC_LITE_HOME=${quoteShell(home)}\
|
|
218
|
+
await writeFile(hookPath, `#!/bin/sh\nexport BB_CC_LITE_HOME=${quoteShell(home)}\n${learningExports}exec ${quoteShell(process.execPath)} ${quoteShell(stableCliPath)} hook "$@"\n`, {
|
|
216
219
|
encoding: "utf8",
|
|
217
220
|
mode: 0o700
|
|
218
221
|
});
|
|
@@ -220,19 +223,27 @@ async function ensureRuntimeLaunchers(cliFilePath, homeDir) {
|
|
|
220
223
|
await chmod(hookPath, 0o700);
|
|
221
224
|
return { statusline: statuslinePath, hook: hookPath };
|
|
222
225
|
}
|
|
223
|
-
function mergeBbHooks(existingHooks, hookPath) {
|
|
226
|
+
function mergeBbHooks(existingHooks, hookPath, mode, learn) {
|
|
224
227
|
const result = removeBbHooks(existingHooks, undefined) ?? {};
|
|
225
|
-
for (const
|
|
228
|
+
for (const spec of hookSpecsForMode(mode)) {
|
|
229
|
+
const eventName = spec.eventName;
|
|
226
230
|
const entries = Array.isArray(result[eventName]) ? [...result[eventName]] : [];
|
|
227
231
|
entries.push({
|
|
228
|
-
matcher:
|
|
232
|
+
matcher: spec.matcher,
|
|
229
233
|
hooks: [
|
|
230
234
|
{
|
|
231
235
|
type: "command",
|
|
232
236
|
command: hookPath,
|
|
233
|
-
args: [
|
|
234
|
-
|
|
235
|
-
|
|
237
|
+
args: [
|
|
238
|
+
"--bb-cc-lite-hook",
|
|
239
|
+
eventName,
|
|
240
|
+
"--bb-cc-lite-mode",
|
|
241
|
+
mode,
|
|
242
|
+
"--bb-cc-lite-learn",
|
|
243
|
+
learn ? "1" : "0"
|
|
244
|
+
],
|
|
245
|
+
...(spec.async ? { async: true } : {}),
|
|
246
|
+
timeout: spec.timeout
|
|
236
247
|
}
|
|
237
248
|
]
|
|
238
249
|
});
|
|
@@ -240,6 +251,36 @@ function mergeBbHooks(existingHooks, hookPath) {
|
|
|
240
251
|
}
|
|
241
252
|
return result;
|
|
242
253
|
}
|
|
254
|
+
function hookSpecsForMode(mode) {
|
|
255
|
+
if (mode === "observe") {
|
|
256
|
+
return SAFE_HOOK_EVENTS.map((eventName) => ({
|
|
257
|
+
eventName,
|
|
258
|
+
matcher: "*",
|
|
259
|
+
async: true,
|
|
260
|
+
timeout: 1
|
|
261
|
+
}));
|
|
262
|
+
}
|
|
263
|
+
return [
|
|
264
|
+
{ eventName: "SessionStart", matcher: "*", async: false, timeout: 2 },
|
|
265
|
+
{ eventName: "PreToolUse", matcher: "Bash", async: false, timeout: 2 },
|
|
266
|
+
{ eventName: "PostToolUse", matcher: "*", async: false, timeout: 2 },
|
|
267
|
+
{ eventName: "PostToolUseFailure", matcher: "*", async: false, timeout: 2 },
|
|
268
|
+
{ eventName: "PostToolBatch", matcher: "*", async: false, timeout: 2 },
|
|
269
|
+
{ eventName: "PreCompact", matcher: "*", async: true, timeout: 1 },
|
|
270
|
+
{ eventName: "PostCompact", matcher: "*", async: true, timeout: 1 },
|
|
271
|
+
{ eventName: "Stop", matcher: "*", async: false, timeout: 2 },
|
|
272
|
+
{ eventName: "SessionEnd", matcher: "*", async: false, timeout: 2 }
|
|
273
|
+
];
|
|
274
|
+
}
|
|
275
|
+
function hooksLabel(mode) {
|
|
276
|
+
if (mode === "observe") {
|
|
277
|
+
return "observe-only telemetry hooks";
|
|
278
|
+
}
|
|
279
|
+
if (mode === "guard") {
|
|
280
|
+
return "guard hooks";
|
|
281
|
+
}
|
|
282
|
+
return "coach hooks";
|
|
283
|
+
}
|
|
243
284
|
function removeBbHooks(existingHooks, homeDir) {
|
|
244
285
|
const hooks = cloneRecord(existingHooks);
|
|
245
286
|
for (const [eventName, entries] of Object.entries(hooks)) {
|