rhachet-roles-bhrain 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. package/dist/.test/getContextOpenAI.js +1 -1
  2. package/dist/.test/getContextOpenAI.js.map +1 -1
  3. package/dist/domain.operations/review/compileReviewPrompt.d.ts +22 -0
  4. package/dist/domain.operations/review/compileReviewPrompt.js +95 -0
  5. package/dist/domain.operations/review/compileReviewPrompt.js.map +1 -0
  6. package/dist/domain.operations/review/enumFilesFromDiffs.d.ts +8 -0
  7. package/dist/domain.operations/review/enumFilesFromDiffs.js +74 -0
  8. package/dist/domain.operations/review/enumFilesFromDiffs.js.map +1 -0
  9. package/dist/domain.operations/review/enumFilesFromGlob.d.ts +8 -0
  10. package/dist/domain.operations/review/enumFilesFromGlob.js +31 -0
  11. package/dist/domain.operations/review/enumFilesFromGlob.js.map +1 -0
  12. package/dist/domain.operations/review/estimateTokenCount.d.ts +9 -0
  13. package/dist/domain.operations/review/estimateTokenCount.js +20 -0
  14. package/dist/domain.operations/review/estimateTokenCount.js.map +1 -0
  15. package/dist/domain.operations/review/formatReviewOutput.d.ts +14 -0
  16. package/dist/domain.operations/review/formatReviewOutput.js +42 -0
  17. package/dist/domain.operations/review/formatReviewOutput.js.map +1 -0
  18. package/dist/domain.operations/review/genTokenBreakdownMarkdown.d.ts +19 -0
  19. package/dist/domain.operations/review/genTokenBreakdownMarkdown.js +110 -0
  20. package/dist/domain.operations/review/genTokenBreakdownMarkdown.js.map +1 -0
  21. package/dist/domain.operations/review/genTokenBreakdownReport.d.ts +24 -0
  22. package/dist/domain.operations/review/genTokenBreakdownReport.js +64 -0
  23. package/dist/domain.operations/review/genTokenBreakdownReport.js.map +1 -0
  24. package/dist/domain.operations/review/invokeClaudeCode.d.ts +22 -0
  25. package/dist/domain.operations/review/invokeClaudeCode.js +92 -0
  26. package/dist/domain.operations/review/invokeClaudeCode.js.map +1 -0
  27. package/dist/domain.operations/review/writeInputArtifacts.d.ts +27 -0
  28. package/dist/domain.operations/review/writeInputArtifacts.js +50 -0
  29. package/dist/domain.operations/review/writeInputArtifacts.js.map +1 -0
  30. package/dist/domain.operations/review/writeOutputArtifacts.d.ts +12 -0
  31. package/dist/domain.operations/review/writeOutputArtifacts.js +46 -0
  32. package/dist/domain.operations/review/writeOutputArtifacts.js.map +1 -0
  33. package/dist/roles/getRoleRegistry.js +2 -1
  34. package/dist/roles/getRoleRegistry.js.map +1 -1
  35. package/dist/roles/getRoleRegistry.readme.js +6 -0
  36. package/dist/roles/getRoleRegistry.readme.js.map +1 -1
  37. package/dist/roles/reviewer/briefs/review.tactics.md +60 -0
  38. package/dist/roles/reviewer/getReviewerRole.d.ts +6 -0
  39. package/dist/roles/reviewer/getReviewerRole.js +80 -0
  40. package/dist/roles/reviewer/getReviewerRole.js.map +1 -0
  41. package/dist/roles/reviewer/skills/review/review.d.ts +57 -0
  42. package/dist/roles/reviewer/skills/review/review.js +445 -0
  43. package/dist/roles/reviewer/skills/review/review.js.map +1 -0
  44. package/dist/roles/reviewer/skills/review/review.sh +21 -0
  45. package/dist/roles/reviewer/skills/review/review.ts +575 -0
  46. package/dist/roles/thinker/getThinkerRole.js +1 -1
  47. package/dist/roles/thinker/getThinkerRole.js.map +1 -1
  48. package/dist/roles/thinker/skills/brief.articulate/.demo/article.vision.v2025_08_19..i1.via_chatgpt.md +47 -0
  49. package/dist/roles/thinker/skills/brief.articulate/.demo/article.vision.v2025_08_19.i2.via_rhachet.md +60 -0
  50. package/dist/roles/thinker/skills/brief.articulate/.demo/diverge.v2025_08_17.i1.md +62 -0
  51. package/dist/roles/thinker/skills/brief.articulate/.demo/diverge.v2025_08_17.i1.with_feedback.md +89 -0
  52. package/dist/roles/thinker/skills/brief.articulate/.demo/diverge.v2025_08_17.i2.md +47 -0
  53. package/dist/roles/thinker/skills/brief.articulate/.demo/joke.v2025_08_15.i1.md +44 -0
  54. package/dist/roles/thinker/skills/brief.articulate/.demo/joke.v2025_08_15.i2.md +63 -0
  55. package/dist/roles/thinker/skills/brief.articulate/.demo/joke.v2025_08_15.i3.md +51 -0
  56. package/dist/roles/thinker/skills/brief.articulate/.demo/user-journey.v2025_08_17.i1.md +62 -0
  57. package/dist/roles/thinker/skills/brief.articulate/.demo/user-journey.v2025_08_17.i2.md +49 -0
  58. package/dist/roles/thinker/skills/brief.articulate/.readme.md +0 -0
  59. package/dist/roles/thinker/skills/brief.articulate/stepArticulate.skill.js +1 -1
  60. package/dist/roles/thinker/skills/brief.articulate/stepArticulate.skill.js.map +1 -1
  61. package/dist/roles/thinker/skills/brief.articulate/stepArticulate.skill.ts +168 -0
  62. package/dist/roles/thinker/skills/brief.articulate/stepArticulate.ts +157 -0
  63. package/dist/roles/thinker/skills/brief.catalogize/.demo/joke.types.v2025_08_28.i1.md +93 -0
  64. package/dist/roles/thinker/skills/brief.catalogize/.demo/joke.types.v2025_08_28.i2.md +84 -0
  65. package/dist/roles/thinker/skills/brief.catalogize/.demo/joke.types.v2025_09_28.i1.no_focus_context.md +8 -0
  66. package/dist/roles/thinker/skills/brief.catalogize/.demo/joke.types.v2025_09_28.i2.md +54 -0
  67. package/dist/roles/thinker/skills/brief.catalogize/.demo/persona.usecases.v2025_08_28.i1.md +62 -0
  68. package/dist/roles/thinker/skills/brief.catalogize/.demo/persona.usecases.v2025_08_28.i2.md +64 -0
  69. package/dist/roles/thinker/skills/brief.catalogize/.readme.md +5 -0
  70. package/dist/roles/thinker/skills/brief.catalogize/stepCatalogize.skill.js +1 -1
  71. package/dist/roles/thinker/skills/brief.catalogize/stepCatalogize.skill.js.map +1 -1
  72. package/dist/roles/thinker/skills/brief.catalogize/stepCatalogize.skill.ts +173 -0
  73. package/dist/roles/thinker/skills/brief.catalogize/stepCatalogize.ts +132 -0
  74. package/dist/roles/thinker/skills/brief.demonstrate/.demo/user.journey.roadtrip.input.example.i4.md +3 -0
  75. package/dist/roles/thinker/skills/brief.demonstrate/.demo/user.journey.roadtrip.input.example.i5.md +3 -0
  76. package/dist/roles/thinker/skills/brief.demonstrate/.demo/user.journey.roadtrip.input.example.i6.md +3 -0
  77. package/dist/roles/thinker/skills/brief.demonstrate/.demo/user.journey.roadtrip.input.example.md +3 -0
  78. package/dist/roles/thinker/skills/brief.demonstrate/.demo/user.journey.roadtrip.v2025_08_27.i1.md +52 -0
  79. package/dist/roles/thinker/skills/brief.demonstrate/.demo/user.journey.roadtrip.v2025_08_27.i2.md +51 -0
  80. package/dist/roles/thinker/skills/brief.demonstrate/.demo/user.journey.roadtrip.v2025_08_27.i3.md +47 -0
  81. package/dist/roles/thinker/skills/brief.demonstrate/.demo/user.journey.roadtrip.v2025_08_27.i4.md +62 -0
  82. package/dist/roles/thinker/skills/brief.demonstrate/.demo/user.journey.roadtrip.v2025_08_27.i5.md +47 -0
  83. package/dist/roles/thinker/skills/brief.demonstrate/.demo/user.journey.roadtrip.v2025_08_27.i6.md +53 -0
  84. package/dist/roles/thinker/skills/brief.demonstrate/.readme +3 -0
  85. package/dist/roles/thinker/skills/brief.demonstrate/stepDemonstrate.skill.js +1 -1
  86. package/dist/roles/thinker/skills/brief.demonstrate/stepDemonstrate.skill.js.map +1 -1
  87. package/dist/roles/thinker/skills/brief.demonstrate/stepDemonstrate.skill.ts +190 -0
  88. package/dist/roles/thinker/skills/brief.demonstrate/stepDemonstrate.ts +164 -0
  89. package/dist/roles/thinker/skills/khue.cluster/.demo/user.journeys.input1.cluster.v2025_08_17.i1.md +72 -0
  90. package/dist/roles/thinker/skills/khue.cluster/.demo/user.journeys.input1.cluster.v2025_08_17.i2.md +53 -0
  91. package/dist/roles/thinker/skills/khue.cluster/.demo/user.journeys.input1.cluster.v2025_08_17.i3.which_objectives.md +58 -0
  92. package/dist/roles/thinker/skills/khue.cluster/.demo/user.journeys.input1.cluster.v2025_08_17.i5.which_personas.md +64 -0
  93. package/dist/roles/thinker/skills/khue.cluster/.demo/user.journeys.input2.cluster.v2025_08_17.i1.md +67 -0
  94. package/dist/roles/thinker/skills/khue.cluster/.demo/user.journeys.input2.cluster.v2025_08_17.i2.md +49 -0
  95. package/dist/roles/thinker/skills/khue.cluster/.demo/user.journeys.input2.cluster.v2025_08_17.i3.md +59 -0
  96. package/dist/roles/thinker/skills/khue.cluster/.readme.md +0 -0
  97. package/dist/roles/thinker/skills/khue.cluster/stepCluster.skill.js +1 -1
  98. package/dist/roles/thinker/skills/khue.cluster/stepCluster.skill.js.map +1 -1
  99. package/dist/roles/thinker/skills/khue.cluster/stepCluster.skill.ts +174 -0
  100. package/dist/roles/thinker/skills/khue.cluster/stepCluster.ts +150 -0
  101. package/dist/roles/thinker/skills/khue.decompose/.readme.md +9 -0
  102. package/dist/roles/thinker/skills/khue.diverge/.demo/joke.examples.v2025_08_17.i2.md +23 -0
  103. package/dist/roles/thinker/skills/khue.diverge/.demo/joke.examples.v2025_08_17.i3.md +23 -0
  104. package/dist/roles/thinker/skills/khue.diverge/.demo/joke.varieties.v2025_08_17.i1.md +23 -0
  105. package/dist/roles/thinker/skills/khue.diverge/.demo/userjourney.examples.v2025_08_17.i1.md +9 -0
  106. package/dist/roles/thinker/skills/khue.diverge/.demo/userjourney.examples.v2025_08_17.i2.md +9 -0
  107. package/dist/roles/thinker/skills/khue.diverge/.demo/userjourney.examples.v2025_08_17.i3.md +23 -0
  108. package/dist/roles/thinker/skills/khue.diverge/.demo/userjourney.examples.v2025_08_17.i4.folksy.md +9 -0
  109. package/dist/roles/thinker/skills/khue.diverge/.demo/userjourney.examples.v2025_08_17.i5.folksy.md +23 -0
  110. package/dist/roles/thinker/skills/khue.diverge/.readme.md +0 -0
  111. package/dist/roles/thinker/skills/khue.diverge/stepDiverge.skill.js +1 -1
  112. package/dist/roles/thinker/skills/khue.diverge/stepDiverge.skill.js.map +1 -1
  113. package/dist/roles/thinker/skills/khue.diverge/stepDiverge.skill.ts +149 -0
  114. package/dist/roles/thinker/skills/khue.diverge/stepDiverge.ts +151 -0
  115. package/dist/roles/thinker/skills/khue.encompose/.readme.md +7 -0
  116. package/dist/roles/thinker/skills/khue.instantiate/.readme.md +14 -0
  117. package/dist/roles/thinker/skills/khue.instantiate/stepInstantiate.skill.js +1 -1
  118. package/dist/roles/thinker/skills/khue.instantiate/stepInstantiate.skill.js.map +1 -1
  119. package/dist/roles/thinker/skills/khue.instantiate/stepInstantiate.skill.ts +190 -0
  120. package/dist/roles/thinker/skills/khue.instantiate/stepInstantiate.ts +132 -0
  121. package/dist/roles/thinker/skills/khue.triage/.demo/laughs.v2025_08_18.i1.md +29 -0
  122. package/dist/roles/thinker/skills/khue.triage/.demo/user.journeys.v2025_08_17.i1.md +86 -0
  123. package/dist/roles/thinker/skills/khue.triage/.demo/user.journeys.v2025_08_17.i2.md +68 -0
  124. package/dist/roles/thinker/skills/khue.triage/.readme.md +0 -0
  125. package/dist/roles/thinker/skills/khue.triage/stepTriage.skill.js +1 -1
  126. package/dist/roles/thinker/skills/khue.triage/stepTriage.skill.js.map +1 -1
  127. package/dist/roles/thinker/skills/khue.triage/stepTriage.skill.ts +174 -0
  128. package/dist/roles/thinker/skills/khue.triage/stepTriage.ts +153 -0
  129. package/package.json +7 -6
  130. package/readme.md +55 -0
@@ -0,0 +1,575 @@
1
+ import * as fs from 'fs/promises';
2
+ import { BadRequestError } from 'helpful-errors';
3
+ import * as path from 'path';
4
+
5
+ import { compileReviewPrompt } from '@src/domain.operations/review/compileReviewPrompt';
6
+ import { enumFilesFromDiffs } from '@src/domain.operations/review/enumFilesFromDiffs';
7
+ import { enumFilesFromGlob } from '@src/domain.operations/review/enumFilesFromGlob';
8
+ import { formatReviewOutput } from '@src/domain.operations/review/formatReviewOutput';
9
+ import { genTokenBreakdownMarkdown } from '@src/domain.operations/review/genTokenBreakdownMarkdown';
10
+ import { genTokenBreakdownReport } from '@src/domain.operations/review/genTokenBreakdownReport';
11
+ import { invokeClaudeCode } from '@src/domain.operations/review/invokeClaudeCode';
12
+ import { writeInputArtifacts } from '@src/domain.operations/review/writeInputArtifacts';
13
+ import { writeOutputArtifacts } from '@src/domain.operations/review/writeOutputArtifacts';
14
+
15
+ /**
16
+ * .what = result of stepReview execution
17
+ * .why = enables caller to inspect review outcome and artifacts
18
+ */
19
+ export type StepReviewResult = {
20
+ review: {
21
+ formatted: string;
22
+ };
23
+ log: {
24
+ dir: string;
25
+ };
26
+ output: {
27
+ path: string;
28
+ };
29
+ metrics: {
30
+ files: {
31
+ rulesCount: number;
32
+ targetsCount: number;
33
+ };
34
+ expected: {
35
+ tokens: {
36
+ estimate: number;
37
+ contextWindowPercent: number;
38
+ };
39
+ cost: {
40
+ estimate: number;
41
+ };
42
+ };
43
+ realized: {
44
+ tokens: {
45
+ input: number;
46
+ inputCacheCreation: number;
47
+ inputCacheRead: number;
48
+ output: number;
49
+ };
50
+ cost: {
51
+ input: number;
52
+ cacheWrite: number;
53
+ cacheRead: number;
54
+ output: number;
55
+ total: number;
56
+ };
57
+ };
58
+ };
59
+ };
60
+
61
+ /**
62
+ * .what = generates ISO timestamp for log directory naming
63
+ * .why = enables unique, sortable log directories
64
+ */
65
+ const genLogTimestamp = (): string => {
66
+ return new Date().toISOString().replace(/[:.]/g, '-');
67
+ };
68
+
69
+ /**
70
+ * .what = simple spinner for CLI feedback
71
+ * .why = shows progress during long-running operations
72
+ */
73
+ const withSpinner = async <T>(input: {
74
+ message: string;
75
+ operation: () => Promise<T>;
76
+ }): Promise<T> => {
77
+ const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
78
+ const startTime = Date.now();
79
+ let i = 0;
80
+
81
+ // print title once
82
+ console.log(input.message);
83
+
84
+ // render only the elapsed time branch line
85
+ const render = (frame: string) => {
86
+ const elapsed = Math.floor((Date.now() - startTime) / 1000);
87
+ process.stdout.write(`\r └─ elapsed: ${elapsed}s ${frame} `);
88
+ };
89
+
90
+ render(frames[0]!);
91
+ const interval = setInterval(() => {
92
+ i = (i + 1) % frames.length;
93
+ render(frames[i]!);
94
+ }, 100);
95
+
96
+ try {
97
+ const result = await input.operation();
98
+ clearInterval(interval);
99
+ const elapsed = Math.floor((Date.now() - startTime) / 1000);
100
+ process.stdout.write(`\r └─ elapsed: ${elapsed}s ✓\n\n`);
101
+ return result;
102
+ } catch (error) {
103
+ clearInterval(interval);
104
+ const elapsed = Math.floor((Date.now() - startTime) / 1000);
105
+ process.stdout.write(`\r └─ elapsed: ${elapsed}s ✗\n`);
106
+ throw error;
107
+ }
108
+ };
109
+
110
+ /**
111
+ * .what = executes a code review against specified rules and targets
112
+ * .why = core orchestration flow for reviewer role
113
+ */
114
+ export const stepReview = async (input: {
115
+ rules: string | string[];
116
+ diffs?: string;
117
+ paths?: string | string[];
118
+ output: string;
119
+ mode: 'soft' | 'hard';
120
+ cwd?: string;
121
+ }): Promise<StepReviewResult> => {
122
+ const cwd = input.cwd ?? process.cwd();
123
+
124
+ // validate that at least one of rules, diffs, or paths is specified
125
+ const hasRules =
126
+ input.rules && (Array.isArray(input.rules) ? input.rules.length > 0 : true);
127
+ const hasDiffs = !!input.diffs;
128
+ const hasPaths =
129
+ input.paths && (Array.isArray(input.paths) ? input.paths.length > 0 : true);
130
+ if (!hasRules && !hasDiffs && !hasPaths)
131
+ throw new BadRequestError(
132
+ 'must specify at least one of --rules, --diffs, or --paths',
133
+ );
134
+
135
+ // validate output parent directory exists
136
+ const outputParent = path.dirname(input.output);
137
+ const outputParentAbsolute = path.isAbsolute(outputParent)
138
+ ? outputParent
139
+ : path.join(cwd, outputParent);
140
+ try {
141
+ await fs.access(outputParentAbsolute);
142
+ } catch {
143
+ throw new BadRequestError('output path parent directory does not exist', {
144
+ outputParent: outputParentAbsolute,
145
+ });
146
+ }
147
+
148
+ // enumerate rule files
149
+ const ruleGlobs = Array.isArray(input.rules)
150
+ ? input.rules
151
+ : [input.rules].filter(Boolean);
152
+ const ruleFiles = await enumFilesFromGlob({ glob: ruleGlobs, cwd });
153
+ if (ruleGlobs.length > 0 && ruleFiles.length === 0)
154
+ throw new BadRequestError(
155
+ '--rules glob was ineffective; matched zero files',
156
+ {
157
+ rules: input.rules,
158
+ },
159
+ );
160
+
161
+ // enumerate target files from diffs
162
+ const targetFilesFromDiffs = input.diffs
163
+ ? await enumFilesFromDiffs({
164
+ range: input.diffs as 'uptil-main' | 'uptil-commit' | 'uptil-staged',
165
+ cwd,
166
+ })
167
+ : [];
168
+
169
+ // enumerate target files from paths
170
+ const pathGlobs = input.paths
171
+ ? Array.isArray(input.paths)
172
+ ? input.paths
173
+ : [input.paths]
174
+ : [];
175
+ const positivePathGlobs = pathGlobs.filter((p) => !p.startsWith('!'));
176
+ const negativePathGlobs = pathGlobs
177
+ .filter((p) => p.startsWith('!'))
178
+ .map((p) => p.slice(1));
179
+ const targetFilesFromPaths = await enumFilesFromGlob({
180
+ glob: positivePathGlobs,
181
+ cwd,
182
+ });
183
+
184
+ // union target files from diffs and paths, then apply global exclusions
185
+ const targetFilesUnion = [
186
+ ...new Set([...targetFilesFromDiffs, ...targetFilesFromPaths]),
187
+ ];
188
+ const targetFiles = targetFilesUnion
189
+ .filter((file) => {
190
+ for (const pattern of negativePathGlobs) {
191
+ if (file === pattern || file.endsWith(`/${pattern}`)) return false;
192
+ if (pattern.includes('*')) {
193
+ const regex = new RegExp(
194
+ '^' + pattern.replace(/\*/g, '.*').replace(/\?/g, '.') + '$',
195
+ );
196
+ if (regex.test(file)) return false;
197
+ }
198
+ }
199
+ return true;
200
+ })
201
+ .sort();
202
+
203
+ // validate combined scope is non-empty
204
+ if (targetFiles.length === 0)
205
+ throw new BadRequestError('combined scope resolves to zero files', {
206
+ diffs: input.diffs,
207
+ paths: input.paths,
208
+ diffsMatched: targetFilesFromDiffs.length,
209
+ pathsMatched: targetFilesFromPaths.length,
210
+ });
211
+
212
+ // create log directory early for debugging
213
+ const logDir = path.join(cwd, '.log', 'bhrain', 'review', genLogTimestamp());
214
+ await fs.mkdir(logDir, { recursive: true });
215
+
216
+ // write scope immediately for debugging
217
+ await fs.writeFile(
218
+ path.join(logDir, 'input.scope.json'),
219
+ JSON.stringify({ ruleFiles, targetFiles }, null, 2),
220
+ 'utf-8',
221
+ );
222
+
223
+ // read file contents for prompt compilation
224
+ const readFileContent = async (file: string) => {
225
+ try {
226
+ return await fs.readFile(path.join(cwd, file), 'utf-8');
227
+ } catch (error) {
228
+ if (!(error instanceof Error)) throw error;
229
+ throw new BadRequestError(`failed to read file: ${file}`, {
230
+ file,
231
+ fullPath: path.join(cwd, file),
232
+ error: error.message,
233
+ });
234
+ }
235
+ };
236
+ const ruleContents = await Promise.all(
237
+ ruleFiles.map(async (file) => ({
238
+ path: file,
239
+ content: await readFileContent(file),
240
+ })),
241
+ );
242
+ const targetContents = await Promise.all(
243
+ targetFiles.map(async (file) => ({
244
+ path: file,
245
+ content: await readFileContent(file),
246
+ })),
247
+ );
248
+
249
+ // generate and write token breakdown reports
250
+ const allContents = [...ruleContents, ...targetContents];
251
+ const allBreakdown = genTokenBreakdownReport({ files: allContents });
252
+ const rulesBreakdown = genTokenBreakdownReport({ files: ruleContents });
253
+ const targetsBreakdown = genTokenBreakdownReport({ files: targetContents });
254
+ await fs.writeFile(
255
+ path.join(logDir, 'tokens.expected.json'),
256
+ JSON.stringify(
257
+ {
258
+ all: allBreakdown,
259
+ rules: rulesBreakdown,
260
+ targets: targetsBreakdown,
261
+ },
262
+ null,
263
+ 2,
264
+ ),
265
+ 'utf-8',
266
+ );
267
+ await fs.writeFile(
268
+ path.join(logDir, 'tokens.expected.md'),
269
+ genTokenBreakdownMarkdown({
270
+ all: allBreakdown,
271
+ rules: rulesBreakdown,
272
+ targets: targetsBreakdown,
273
+ }),
274
+ 'utf-8',
275
+ );
276
+
277
+ // compile review prompt
278
+ const promptResult = compileReviewPrompt({
279
+ rules: ruleContents,
280
+ targets: targetContents,
281
+ mode: input.mode,
282
+ });
283
+
284
+ // write metrics.expected immediately after files are read
285
+ await fs.writeFile(
286
+ path.join(logDir, 'metrics.expected.json'),
287
+ JSON.stringify(
288
+ {
289
+ files: {
290
+ rulesCount: ruleFiles.length,
291
+ targetsCount: targetFiles.length,
292
+ },
293
+ tokens: {
294
+ estimate: promptResult.tokenEstimate,
295
+ contextWindowPercent: promptResult.contextWindowPercent,
296
+ },
297
+ cost: {
298
+ estimate: promptResult.costEstimate,
299
+ },
300
+ },
301
+ null,
302
+ 2,
303
+ ),
304
+ 'utf-8',
305
+ );
306
+
307
+ // write input artifacts
308
+ await writeInputArtifacts({
309
+ logDir,
310
+ args: {
311
+ rules: input.rules,
312
+ diffs: input.diffs,
313
+ paths: input.paths,
314
+ output: input.output,
315
+ mode: input.mode,
316
+ },
317
+ scope: {
318
+ ruleFiles,
319
+ targetFiles,
320
+ },
321
+ metrics: {
322
+ tokenEstimate: promptResult.tokenEstimate,
323
+ contextWindowPercent: promptResult.contextWindowPercent,
324
+ costEstimate: promptResult.costEstimate,
325
+ },
326
+ prompt: promptResult.prompt,
327
+ });
328
+
329
+ // emit metrics.expected before invocation
330
+ const logDirRelative = path.relative(cwd, logDir);
331
+ console.log(
332
+ `
333
+ 🔭 metrics.expected
334
+ ├─ files
335
+ │ ├─ rules: ${ruleFiles.length}
336
+ │ └─ targets: ${targetFiles.length}
337
+ ├─ tokens
338
+ │ ├─ estimate: ${promptResult.tokenEstimate}
339
+ │ └─ context: ${promptResult.contextWindowPercent.toFixed(1)}%
340
+ └─ cost
341
+ └─ estimate: $${promptResult.costEstimate.toFixed(4)}
342
+
343
+ 🪵 logs
344
+ ├─ scope: ${logDirRelative}/input.scope.json
345
+ ├─ metrics: ${logDirRelative}/metrics.expected.json
346
+ └─ tokens: ${logDirRelative}/tokens.expected.md
347
+ `.trim(),
348
+ );
349
+
350
+ // invoke claude-code with spinner
351
+ console.log('');
352
+ const brainResult = await withSpinner({
353
+ message: "🐢 let's review!",
354
+ operation: () => invokeClaudeCode({ prompt: promptResult.prompt, cwd }),
355
+ });
356
+
357
+ // calculate realized costs per token type
358
+ const realizedCosts = (() => {
359
+ const input = (brainResult.usage.inputTokens / 1_000_000) * 3;
360
+ const cacheWrite =
361
+ (brainResult.usage.inputTokensCacheCreation / 1_000_000) * 3.75;
362
+ const cacheRead =
363
+ (brainResult.usage.inputTokensCacheRead / 1_000_000) * 0.3;
364
+ const output = (brainResult.usage.outputTokens / 1_000_000) * 15;
365
+ const total =
366
+ Math.round((input + cacheWrite + cacheRead + output) * 10000) / 10000;
367
+ return { input, cacheWrite, cacheRead, output, total };
368
+ })();
369
+
370
+ // write metrics.realized after invocation
371
+ await fs.writeFile(
372
+ path.join(logDir, 'metrics.realized.json'),
373
+ JSON.stringify(
374
+ {
375
+ tokens: {
376
+ input: brainResult.usage.inputTokens,
377
+ inputCacheCreation: brainResult.usage.inputTokensCacheCreation,
378
+ inputCacheRead: brainResult.usage.inputTokensCacheRead,
379
+ output: brainResult.usage.outputTokens,
380
+ },
381
+ cost: {
382
+ input: realizedCosts.input,
383
+ cacheWrite: realizedCosts.cacheWrite,
384
+ cacheRead: realizedCosts.cacheRead,
385
+ output: realizedCosts.output,
386
+ total: realizedCosts.total,
387
+ },
388
+ },
389
+ null,
390
+ 2,
391
+ ),
392
+ 'utf-8',
393
+ );
394
+
395
+ // parse issues from review text
396
+ const reviewIssues = (() => {
397
+ // extract JSON from the review text (may be wrapped in markdown code blocks)
398
+ const jsonMatch = brainResult.review.match(/```(?:json)?\s*([\s\S]*?)```/);
399
+ const jsonText = jsonMatch?.[1]?.trim() ?? brainResult.review.trim();
400
+
401
+ try {
402
+ return JSON.parse(jsonText) as {
403
+ issues: Array<{
404
+ type: 'blocker' | 'nitpick';
405
+ message: string;
406
+ file?: string;
407
+ line?: number;
408
+ }>;
409
+ };
410
+ } catch (error) {
411
+ throw new BadRequestError(
412
+ 'failed to parse review issues from claude response',
413
+ {
414
+ review: brainResult.review,
415
+ jsonText,
416
+ error: error instanceof Error ? error.message : String(error),
417
+ },
418
+ );
419
+ }
420
+ })();
421
+
422
+ // format review output
423
+ const formattedReview = formatReviewOutput({
424
+ response: reviewIssues,
425
+ });
426
+
427
+ // write output artifacts
428
+ await writeOutputArtifacts({
429
+ logDir,
430
+ response: brainResult.response,
431
+ review: formattedReview,
432
+ });
433
+
434
+ // write final review to output path
435
+ const outputAbsolute = path.isAbsolute(input.output)
436
+ ? input.output
437
+ : path.join(cwd, input.output);
438
+ await fs.writeFile(outputAbsolute, formattedReview, 'utf-8');
439
+
440
+ // emit metrics.realized after invocation
441
+ console.log(
442
+ `
443
+ ✨ metrics.realized
444
+ ├─ tokens
445
+ │ ├─ input: ${brainResult.usage.inputTokens}
446
+ │ ├─ cache.write: ${brainResult.usage.inputTokensCacheCreation}
447
+ │ ├─ cache.read: ${brainResult.usage.inputTokensCacheRead}
448
+ │ └─ output: ${brainResult.usage.outputTokens}
449
+ └─ cost
450
+ ├─ input: $${realizedCosts.input.toFixed(4)}
451
+ ├─ cache.write: $${realizedCosts.cacheWrite.toFixed(4)}
452
+ ├─ cache.read: $${realizedCosts.cacheRead.toFixed(4)}
453
+ ├─ output: $${realizedCosts.output.toFixed(4)}
454
+ └─ total: $${realizedCosts.total.toFixed(4)}
455
+
456
+ 🌊 output
457
+ ├─ logs: ${path.relative(cwd, logDir)}
458
+ └─ review: ${path.relative(cwd, outputAbsolute).startsWith('..') ? outputAbsolute : path.relative(cwd, outputAbsolute)}
459
+ `.trim(),
460
+ );
461
+
462
+ return {
463
+ review: {
464
+ formatted: formattedReview,
465
+ },
466
+ log: {
467
+ dir: logDir,
468
+ },
469
+ output: {
470
+ path: outputAbsolute,
471
+ },
472
+ metrics: {
473
+ files: {
474
+ rulesCount: ruleFiles.length,
475
+ targetsCount: targetFiles.length,
476
+ },
477
+ expected: {
478
+ tokens: {
479
+ estimate: promptResult.tokenEstimate,
480
+ contextWindowPercent: promptResult.contextWindowPercent,
481
+ },
482
+ cost: {
483
+ estimate: promptResult.costEstimate,
484
+ },
485
+ },
486
+ realized: {
487
+ tokens: {
488
+ input: brainResult.usage.inputTokens,
489
+ inputCacheCreation: brainResult.usage.inputTokensCacheCreation,
490
+ inputCacheRead: brainResult.usage.inputTokensCacheRead,
491
+ output: brainResult.usage.outputTokens,
492
+ },
493
+ cost: {
494
+ input: realizedCosts.input,
495
+ cacheWrite: realizedCosts.cacheWrite,
496
+ cacheRead: realizedCosts.cacheRead,
497
+ output: realizedCosts.output,
498
+ total: realizedCosts.total,
499
+ },
500
+ },
501
+ },
502
+ };
503
+ };
504
+
505
+ /**
506
+ * .what = CLI entrypoint when run directly
507
+ * .why = enables ./review.sh to invoke this module
508
+ */
509
+ if (require.main === module) {
510
+ // parse command line arguments
511
+ const args = process.argv.slice(2);
512
+ const parsed: Record<string, string> = {};
513
+ for (let i = 0; i < args.length; i += 2) {
514
+ const key = args[i]?.replace(/^--/, '');
515
+ const value = args[i + 1];
516
+ if (key && value) parsed[key] = value;
517
+ }
518
+
519
+ // default output path if not provided
520
+ const output =
521
+ parsed.output ??
522
+ path.join(
523
+ process.cwd(),
524
+ '.review',
525
+ 'bhrain',
526
+ `v${genLogTimestamp()}`,
527
+ '[feedback].[given].by_robot.md',
528
+ );
529
+
530
+ // validate mode is provided
531
+ if (!parsed.mode || (parsed.mode !== 'soft' && parsed.mode !== 'hard')) {
532
+ console.error('⛈️ error: --mode must be "soft" or "hard"');
533
+ process.exit(1);
534
+ }
535
+ const mode = parsed.mode as 'soft' | 'hard';
536
+
537
+ // default rules to .agent/**/briefs/**/rule.*.md
538
+ const rules = parsed.rules
539
+ ? parsed.rules.split(',').map((r) => r.trim())
540
+ : ['.agent/**/briefs/**/rule.*.md'];
541
+
542
+ // default diffs to uptil-main
543
+ const diffs =
544
+ (parsed.diffs as 'uptil-main' | 'uptil-commit' | 'uptil-staged') ??
545
+ 'uptil-main';
546
+
547
+ // parse paths if provided
548
+ const paths = parsed.paths
549
+ ? parsed.paths.split(',').map((p) => p.trim())
550
+ : undefined;
551
+
552
+ // execute review
553
+ void (async () => {
554
+ try {
555
+ // ensure output directory exists
556
+ await fs.mkdir(path.dirname(output), { recursive: true });
557
+
558
+ // run the review
559
+ await stepReview({
560
+ rules,
561
+ diffs,
562
+ paths,
563
+ output,
564
+ mode,
565
+ });
566
+ } catch (error) {
567
+ if (error instanceof BadRequestError) {
568
+ console.error(`\n⛈️ error: ${error.message}`);
569
+ process.exit(1);
570
+ }
571
+ console.error('⛈️ unexpected error:', error);
572
+ process.exit(1);
573
+ }
574
+ })();
575
+ }
@@ -11,7 +11,7 @@ const stepDiverge_skill_1 = require("./skills/khue.diverge/stepDiverge.skill");
11
11
  const stepInstantiate_skill_1 = require("./skills/khue.instantiate/stepInstantiate.skill");
12
12
  const stepTriage_skill_1 = require("./skills/khue.triage/stepTriage.skill");
13
13
  exports.ROLE_THINKER = rhachet_1.Role.build({
14
- slug: 'bhrain',
14
+ slug: 'thinker',
15
15
  name: 'Thinker',
16
16
  purpose: 'think',
17
17
  readme: `
@@ -1 +1 @@
1
- {"version":3,"file":"getThinkerRole.js","sourceRoot":"","sources":["../../../src/roles/thinker/getThinkerRole.ts"],"names":[],"mappings":";;;AAAA,qCAA0C;AAE1C,uDAAoD;AACpD,yFAAkF;AAClF,yFAAkF;AAClF,4FAAqF;AACrF,+EAAwE;AACxE,+EAAwE;AACxE,2FAAoF;AACpF,4EAAqE;AAExD,QAAA,YAAY,GAAS,cAAI,CAAC,KAAK,CAAC;IAC3C,IAAI,EAAE,QAAQ;IACd,IAAI,EAAE,SAAS;IACf,OAAO,EAAE,OAAO;IAChB,MAAM,EAAE;;;;GAIP,CAAC,IAAI,EAAE;IACR,MAAM,EAAE;QACN,mBAAS,CAAC,KAAK,CAAC;YACd,IAAI,EAAE,KAAK;YACX,MAAM,EAAE,iDAAiD;YACzD,KAAK,EAAE,IAAA,iCAAe,EAAC,cAAc,CAAC;SACvC,CAAC;QACF,mBAAS,CAAC,KAAK,CAAC;YACd,IAAI,EAAE,OAAO;YACb,MAAM,EAAE,8CAA8C;YACtD,KAAK,EAAE,IAAA,iCAAe,EAAC,0BAA0B,CAAC;SACnD,CAAC;KACH;IACD,MAAM,EAAE;QACN,IAAI,EAAE,EAAE;QACR,IAAI,EAAE;YACJ,kBAAkB;YAClB,iCAAa;YACb,iCAAa;YACb,+BAAY;YACZ,yCAAiB;YAEjB,kBAAkB;YAClB,uCAAgB;YAChB,yCAAiB;YACjB,uCAAgB;YAEhB,kBAAkB;YAClB,sCAAsC;SACvC;KACF;IACD,MAAM,EAAE;QACN,IAAI,EAAE,CAAC,EAAE,GAAG,EAAE,SAAS,GAAG,SAAS,EAAE,CAAC;KACvC;CACF,CAAC,CAAC"}
1
+ {"version":3,"file":"getThinkerRole.js","sourceRoot":"","sources":["../../../src/roles/thinker/getThinkerRole.ts"],"names":[],"mappings":";;;AAAA,qCAA0C;AAE1C,uDAAoD;AACpD,yFAAkF;AAClF,yFAAkF;AAClF,4FAAqF;AACrF,+EAAwE;AACxE,+EAAwE;AACxE,2FAAoF;AACpF,4EAAqE;AAExD,QAAA,YAAY,GAAS,cAAI,CAAC,KAAK,CAAC;IAC3C,IAAI,EAAE,SAAS;IACf,IAAI,EAAE,SAAS;IACf,OAAO,EAAE,OAAO;IAChB,MAAM,EAAE;;;;GAIP,CAAC,IAAI,EAAE;IACR,MAAM,EAAE;QACN,mBAAS,CAAC,KAAK,CAAC;YACd,IAAI,EAAE,KAAK;YACX,MAAM,EAAE,iDAAiD;YACzD,KAAK,EAAE,IAAA,iCAAe,EAAC,cAAc,CAAC;SACvC,CAAC;QACF,mBAAS,CAAC,KAAK,CAAC;YACd,IAAI,EAAE,OAAO;YACb,MAAM,EAAE,8CAA8C;YACtD,KAAK,EAAE,IAAA,iCAAe,EAAC,0BAA0B,CAAC;SACnD,CAAC;KACH;IACD,MAAM,EAAE;QACN,IAAI,EAAE,EAAE;QACR,IAAI,EAAE;YACJ,kBAAkB;YAClB,iCAAa;YACb,iCAAa;YACb,+BAAY;YACZ,yCAAiB;YAEjB,kBAAkB;YAClB,uCAAgB;YAChB,yCAAiB;YACjB,uCAAgB;YAEhB,kBAAkB;YAClB,sCAAsC;SACvC;KACF;IACD,MAAM,EAAE;QACN,IAAI,EAAE,CAAC,EAAE,GAAG,EAAE,SAAS,GAAG,SAAS,EAAE,CAAC;KACvC;CACF,CAAC,CAAC"}
@@ -0,0 +1,47 @@
1
+ # 🧩 .brief: `vision.article`
2
+
3
+ ## .what
4
+ a **vision** is a variant of an [article] that expresses a **desired future state**.
5
+ unlike descriptive or analytical articles, which capture what *is* or what *was*, a vision projects what *could be* or what *should be*.
6
+ it blends aspiration, imagination, and direction into a structured articulation.
7
+
8
+ ---
9
+
10
+ ## 🎯 purpose
11
+ - to **inspire** by painting a compelling picture of a possible future
12
+ - to **align** people, efforts, or resources toward a shared destination
13
+ - to **guide** decision-making by establishing a reference point for progress
14
+ - to **differentiate** from mere goals or plans by focusing on *direction and possibility*, not only steps
15
+
16
+ ---
17
+
18
+ ## ⚖️ comparison
19
+
20
+ | aspect | standard [article] | [vision].article |
21
+ |-------------------|-------------------------------------------|--------------------------------------------|
22
+ | **orientation** | present/past: what is, what was | future: what could/should be |
23
+ | **tone** | explanatory, descriptive, analytical | aspirational, imaginative, directional |
24
+ | **basis** | facts, evidence, analysis | ideals, possibilities, desired outcomes |
25
+ | **function** | inform, explain, clarify | inspire, align, mobilize |
26
+ | **risk** | incompleteness, bias, irrelevance | vagueness, over-idealization, impracticality|
27
+
28
+ ---
29
+
30
+ ## 📐 examples
31
+
32
+ - **article (education):** a report on literacy rates in rural schools.
33
+ - **vision.article (education):** “a future where every child, no matter their location, can access high-quality digital learning from home.”
34
+
35
+ - **article (healthcare):** documentation of hospital bed availability during flu season.
36
+ - **vision.article (healthcare):** “a world where predictive healthcare ensures no patient ever waits for urgent treatment.”
37
+
38
+ - **article (transportation):** a study of current urban traffic congestion.
39
+ - **vision.article (transportation):** “a city where clean, autonomous transit eliminates traffic jams and reduces commute times to minutes.”
40
+
41
+ ---
42
+
43
+ ## 📊 insight
44
+ a **vision.article** sits at the crossroads of imagination and articulation:
45
+ - it frames the **direction of motion** without binding to the exact steps.
46
+ - it serves as a **north star** for other [articles] (plans, roadmaps, strategies).
47
+ - its strength lies in **clarity + aspiration**, balancing inspiration with plausibility.
@@ -0,0 +1,60 @@
1
+ # 🧩 .brief: `[vision]`
2
+
3
+ ## .what
4
+ a **vision** is an imaginative depiction or scenario illustrating a potential future state of a concept, entity, or project. it serves as a creative projection aimed at inspiring and guiding transformative thinking or strategic planning by showcasing what is possible.
5
+
6
+ ---
7
+
8
+ ## 🎯 purpose
9
+ - to **inspire innovation** and broad thinking about potential futures
10
+ - to **guide strategic decision-making** by providing a target state to work towards
11
+ - to **engage stakeholders** by vividly portraying the benefits and impact of pursuing the vision
12
+
13
+ ---
14
+
15
+ ## 🛠 components
16
+
17
+ ### 1. **imagination**
18
+ - creatively constructed possibilities that envision a future state
19
+ - may involve speculative scenarios or conceptual explorations
20
+
21
+ ### 2. **impact**
22
+ - describes the anticipated results or changes if the vision is realized
23
+ - typically highlights positive transformations or advancements
24
+
25
+ ### 3. **feasibility**
26
+ - considers the practicality and potential challenges in achieving the vision
27
+ - often includes contingency thoughts to address uncertainties
28
+
29
+ ### 4. **engagement**
30
+ - a compelling narrative that captures the interest of diverse audiences
31
+ - calls for alignment and participation from stakeholders
32
+
33
+ ---
34
+
35
+ ## 🔍 features
36
+ - **vivid and illustrative** to make abstract ideas tangible
37
+ - **aspirational tone** to elevate thinking beyond current limitations
38
+ - includes **elements of storytelling** to foster emotional connection
39
+
40
+ ---
41
+
42
+ ## 🏗 creating a vision
43
+ 1. **research trends and scenarios**: understand ongoing changes and potential future shifts.
44
+ 2. **brainstorm creatively**: think imaginatively about what is possible without current constraints.
45
+ 3. **storyboard your vision**: craft a narrative that conveys the exciting, transformative potential.
46
+ 4. **seek feedback and iteratively refine**: engage others in reviewing and enhancing the vision for broader resonance.
47
+
48
+ ---
49
+
50
+ ## 📚 examples
51
+ - vision of a sustainable future city: "the city of tomorrow is a zero-emission urban paradise where nature and technology coexist, providing a thriving, inclusive, and adaptive ecosystem for all."
52
+ - vision for an education system: "an educational landscape where personalized learning and novel pedagogies empower every student to realize their full potential, unbounded by their starting point."
53
+
54
+ ---
55
+
56
+ ## 💡 notes
57
+ - a vision can be abstract and speculative, unlike a mission or strategy, which are typically more grounded and specific.
58
+ - ensure a vision remains **flexible** to evolve with new insights and realities.
59
+
60
+ ---