agentboss 0.1.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 (53) hide show
  1. package/README.md +34 -0
  2. package/bin/aboss.js +288 -0
  3. package/client/dist/assets/index-C1wFD_Vo.css +1 -0
  4. package/client/dist/assets/index-DBj1Ujlx.js +137 -0
  5. package/client/dist/index.html +34 -0
  6. package/package.json +64 -0
  7. package/server/analysis/daily-aggregator.js +258 -0
  8. package/server/analysis/difficulty.js +129 -0
  9. package/server/analysis/dimensions/ai-knowledge.js +172 -0
  10. package/server/analysis/dimensions/ai-tools.js +161 -0
  11. package/server/analysis/dimensions/judgement.js +107 -0
  12. package/server/analysis/dimensions/llm-merge.js +57 -0
  13. package/server/analysis/dimensions/output-quality.js +167 -0
  14. package/server/analysis/dimensions/problem-definition.js +104 -0
  15. package/server/analysis/dimensions/system-thinking.js +225 -0
  16. package/server/analysis/evidence-builder.js +104 -0
  17. package/server/analysis/job.js +273 -0
  18. package/server/analysis/report-builder.js +581 -0
  19. package/server/analysis/scoring-v2.js +72 -0
  20. package/server/analysis/text-signals.js +179 -0
  21. package/server/analysis/thresholds-v2.js +358 -0
  22. package/server/api/advice.js +124 -0
  23. package/server/api/analysis.js +141 -0
  24. package/server/api/execution.js +330 -0
  25. package/server/api/metrics.js +277 -0
  26. package/server/api/overview.js +308 -0
  27. package/server/api/project.js +255 -0
  28. package/server/api/reports.js +125 -0
  29. package/server/api/sessions.js +118 -0
  30. package/server/api/settings.js +119 -0
  31. package/server/db/connection.js +175 -0
  32. package/server/db/queries.js +1051 -0
  33. package/server/db/schema.js +487 -0
  34. package/server/etl/active-time.js +150 -0
  35. package/server/etl/backfill-subagents.js +178 -0
  36. package/server/etl/claude-code.js +826 -0
  37. package/server/etl/detect.js +341 -0
  38. package/server/etl/judge-filter.js +117 -0
  39. package/server/etl/opencode.js +606 -0
  40. package/server/execution/job.js +662 -0
  41. package/server/execution/prompt.js +227 -0
  42. package/server/execution/runner.js +218 -0
  43. package/server/index.js +94 -0
  44. package/server/llm/advice-prompt.js +339 -0
  45. package/server/llm/advice.js +384 -0
  46. package/server/llm/analysis-prompt.js +162 -0
  47. package/server/llm/cli-runner.js +249 -0
  48. package/server/llm/judge-prompts.js +179 -0
  49. package/server/llm/judge.js +118 -0
  50. package/server/llm/project-advice-prompt.js +332 -0
  51. package/server/llm/project-advice.js +491 -0
  52. package/server/llm/session-analyzer.js +122 -0
  53. package/server/utils/project.js +80 -0
@@ -0,0 +1,339 @@
1
+ /**
2
+ * Per-session AI-advice prompt builder.
3
+ *
4
+ * Wraps a session's context (basic meta, tool-call summary, message
5
+ * transcript) into a single instruction that asks the local CLI
6
+ * (opencode / claude) to return 5-category, evidence-backed improvement
7
+ * suggestions as strict JSON.
8
+ *
9
+ * Spec: docs/superpowers/specs/2026-06-13-session-advice-design.md
10
+ *
11
+ * # Inputs the prompt deliberately OMITS
12
+ *
13
+ * We do NOT feed the model any of our computed H1/H2/E1/O1 scores or
14
+ * sub-scores. Earlier iterations did, and the model immediately
15
+ * collapsed into echoing those numbers back as "evidence" ("H1=63 偏低,
16
+ * 所以问题定义不清晰"), which is just a fancy way of laundering our own
17
+ * rule-based output through an LLM. Stripping the scores forces the
18
+ * model to look at the conversation itself. The prompt also explicitly
19
+ * forbids referencing scores, levels, or dimension keys in the output.
20
+ *
21
+ * # Scope of advice (intentional)
22
+ *
23
+ * The model is told to comment on the developer's COLLABORATION PATTERNS:
24
+ * how prompts were phrased, where context was missing, where flow could
25
+ * have been tighter, whether a skill/subagent would have helped, where
26
+ * cost/cache choices hurt.
27
+ *
28
+ * It is told NOT to critique the domain content of the conversation —
29
+ * e.g. "your React hook is wrong", "you should have used an INNER JOIN".
30
+ * That kind of feedback belongs in code review, not in a usage analytics
31
+ * tool, and the model lacks the runtime/repo context to do it safely.
32
+ *
33
+ * # Independence from judge
34
+ *
35
+ * Advice has its own sentinel (ADVICE_SENTINEL). The judge prompts use
36
+ * JUDGE_SENTINEL. Both are recognised by server/etl/judge-filter.js so
37
+ * the resulting CLI sessions get filtered out of the user's own data.
38
+ * This file does NOT import from judge-prompts, so changes there cannot
39
+ * silently affect advice generation.
40
+ *
41
+ * # Versioning
42
+ *
43
+ * Bump ADVICE_PROMPT_VERSION whenever the OUTPUT contract (categories,
44
+ * AdviceItem shape, hard rules) changes so old cached results are
45
+ * discarded and re-judged. Cosmetic edits to wording, layout or ctx
46
+ * ordering do NOT need a bump.
47
+ *
48
+ * @author Felix
49
+ */
50
+
51
+ 'use strict';
52
+
53
+ // ---------------------------------------------------------------------------
54
+ // Constants
55
+ // ---------------------------------------------------------------------------
56
+
57
+ /**
58
+ * First line of every advice prompt. Mirrors the role of JUDGE_SENTINEL
59
+ * in judge-prompts.js but lives here so advice is fully self-contained.
60
+ * Recognised by server/etl/judge-filter.js together with JUDGE_SENTINEL.
61
+ */
62
+ const ADVICE_SENTINEL = '[ABOSS-ADVICE]';
63
+
64
+ /** Bump when the JSON output contract changes (see header). */
65
+ const ADVICE_PROMPT_VERSION = 4;
66
+
67
+ /** Hard categories — written into the prompt as an enum the model MUST fill. */
68
+ const CATEGORIES = ['cost', 'accuracy', 'context', 'skills', 'workflow'];
69
+
70
+ /** Soft size budget for the assembled prompt (bytes). Beyond this we
71
+ * start dropping mid-conversation messages (see truncateContext). */
72
+ const DEFAULT_MAX_BYTES = 80_000;
73
+
74
+ /** When truncating: keep the first N and last N user/assistant turns
75
+ * verbatim, replace the middle with a single system placeholder. */
76
+ const HEAD_KEEP = 30;
77
+ const TAIL_KEEP = 30;
78
+
79
+ /** Per-message hard cap when even head+tail still overflow. */
80
+ const MSG_HARD_CAP_CHARS = 600;
81
+
82
+ // ---------------------------------------------------------------------------
83
+ // Truncation
84
+ // ---------------------------------------------------------------------------
85
+
86
+ /**
87
+ * Reduce ctx.messages so the resulting prompt stays within `maxBytes`.
88
+ *
89
+ * Strategy (applied in order):
90
+ * 1. If total < maxBytes → no-op.
91
+ * 2. Keep HEAD_KEEP + TAIL_KEEP messages, drop the middle, insert one
92
+ * `[…省略 N 条…]` system placeholder. Mark `truncated: true`.
93
+ * 3. Still over? Cap every remaining message text to MSG_HARD_CAP_CHARS.
94
+ * 4. Still over? Drop messages from the head (keep tail) until we fit;
95
+ * mark `truncated: 'hard'`.
96
+ *
97
+ * @param {object} ctx see buildAdvicePrompt() — must have .messages[]
98
+ * @param {number} [maxBytes=DEFAULT_MAX_BYTES]
99
+ * @returns {object} possibly-truncated ctx (shallow copy)
100
+ */
101
+ function truncateContext(ctx, maxBytes = DEFAULT_MAX_BYTES) {
102
+ const out = { ...ctx, truncated: false, omittedMessages: 0 };
103
+ const messages = Array.isArray(ctx.messages) ? ctx.messages.slice() : [];
104
+
105
+ const FIXED_OVERHEAD = 4_000;
106
+ const estimateBytes = (msgs) =>
107
+ FIXED_OVERHEAD + msgs.reduce((n, m) => n + (m.text ? m.text.length : 0), 0);
108
+
109
+ if (estimateBytes(messages) <= maxBytes) {
110
+ out.messages = messages;
111
+ return out;
112
+ }
113
+
114
+ if (messages.length > HEAD_KEEP + TAIL_KEEP) {
115
+ const omitted = messages.length - HEAD_KEEP - TAIL_KEEP;
116
+ const head = messages.slice(0, HEAD_KEEP);
117
+ const tail = messages.slice(messages.length - TAIL_KEEP);
118
+ const placeholder = {
119
+ role: 'system',
120
+ text: `[…中段省略 ${omitted} 条消息…]`,
121
+ };
122
+ out.messages = head.concat([placeholder], tail);
123
+ out.truncated = true;
124
+ out.omittedMessages = omitted;
125
+ } else {
126
+ out.messages = messages;
127
+ }
128
+
129
+ if (estimateBytes(out.messages) <= maxBytes) return out;
130
+
131
+ out.messages = out.messages.map((m) => {
132
+ if (!m.text || m.text.length <= MSG_HARD_CAP_CHARS) return m;
133
+ return { ...m, text: m.text.slice(0, MSG_HARD_CAP_CHARS) + '…[trimmed]' };
134
+ });
135
+
136
+ if (estimateBytes(out.messages) <= maxBytes) return out;
137
+
138
+ while (out.messages.length > 2 && estimateBytes(out.messages) > maxBytes) {
139
+ out.messages.shift();
140
+ }
141
+ out.truncated = 'hard';
142
+ return out;
143
+ }
144
+
145
+ // ---------------------------------------------------------------------------
146
+ // Formatting helpers
147
+ // ---------------------------------------------------------------------------
148
+
149
+ function fmtNum(n) {
150
+ if (n == null || Number.isNaN(n)) return '–';
151
+ if (typeof n !== 'number') return String(n);
152
+ if (Number.isInteger(n)) return n.toLocaleString('en-US');
153
+ return n.toFixed(3);
154
+ }
155
+
156
+ function fmtToolTable(tools) {
157
+ if (!Array.isArray(tools) || tools.length === 0) return '(无工具调用)';
158
+ const rows = tools.slice(0, 20).map((t) =>
159
+ ` ${(t.name || '?').padEnd(20)} ` +
160
+ `count=${String(t.count ?? 0).padStart(4)} ` +
161
+ `err=${String(t.errorCount ?? 0).padStart(3)} ` +
162
+ `avg_ms=${String(Math.round(t.avgDurationMs ?? 0)).padStart(6)} ` +
163
+ `args="${(t.argsPreview || '').replace(/\s+/g, ' ').slice(0, 120)}"`
164
+ );
165
+ return rows.join('\n');
166
+ }
167
+
168
+ function fmtMessages(messages) {
169
+ if (!Array.isArray(messages) || messages.length === 0) {
170
+ return '(无消息)';
171
+ }
172
+ return messages
173
+ .filter((m) => m.text != null && m.text !== '')
174
+ .map((m) => {
175
+ const role = (m.role || '?').toUpperCase().padEnd(9);
176
+ return `[${role}] ${m.text}`;
177
+ })
178
+ .join('\n---\n');
179
+ }
180
+
181
+ // ---------------------------------------------------------------------------
182
+ // Prompt
183
+ // ---------------------------------------------------------------------------
184
+
185
+ /**
186
+ * Assemble the full advice prompt.
187
+ *
188
+ * `ctx` shape — see spec §4.4.
189
+ *
190
+ * @param {object} ctx
191
+ * @returns {string}
192
+ */
193
+ function buildAdvicePrompt(ctx) {
194
+ const s = ctx.session || {};
195
+ const t = s.tokens || {};
196
+
197
+ const truncatedNote =
198
+ ctx.truncated === 'hard'
199
+ ? '(注意:会话很长,已强力截断,部分内容缺失。)'
200
+ : ctx.truncated
201
+ ? `(注意:中段已省略 ${ctx.omittedMessages} 条消息。)`
202
+ : '';
203
+
204
+ return `${ADVICE_SENTINEL}(内部标记,忽略本行)
205
+ 你是一位 AI 协作教练。我给你一段开发者与 AI 编程助手的会话原文,以及
206
+ 非常少量的基础统计(模型、时长、token、工具次数)。你的任务:仔细
207
+ 阅读对话本身,评估开发者「如何使用 AI」,给出可执行的改进建议。
208
+
209
+ # 关键原则:只看对话,不要套用任何指标
210
+
211
+ 我们的系统另外有一套 H1/H2/E1/O1 等评分体系,但本次评估**不会**把
212
+ 那些分数喂给你,也禁止你引用、推测或编造任何此类分数。
213
+
214
+ 禁止出现的表达举例:
215
+ - 「H1=63 偏低,所以问题定义不清晰」
216
+ - 「子分 clarity=0.45,说明开场模糊」
217
+ - 「评分显示效率不足」
218
+ - 「L2 水平,有改进空间」
219
+
220
+ 即使下文有的统计字段(消息数、token、错误次数)可以引用,也只能作为
221
+ 「对话事实」的描述,不能换算成抽象分数或等级。
222
+
223
+ 每一条建议的 evidence 必须直接指向对话内容或基础统计中的具体事实,例如:
224
+ - 「第 3 条用户消息只说『改一下』,未指出文件路径」
225
+ - 「同一个 read 工具被连续调用 12 次,看起来在反复检索」
226
+ - 「开场没有给出任何代码片段或文件名」
227
+
228
+ # 评估对象与禁区
229
+
230
+ 你要评估的是「协作方式」,不是「业务内容」:
231
+
232
+ 可以谈:
233
+ - 用户的提问方式(是否清晰、是否分步、是否说明意图)
234
+ - 上下文准备(是否提供文件路径、约束、示例、依赖)
235
+ - 工具与模型的使用(是否选对工具、是否过度调用、缓存命中率)
236
+ - 流程节奏(回退次数、纠偏速度、是否过早收敛)
237
+ - 是否适合引入一个 opencode skill 或 subagent 来自动化重复模式
238
+ - 成本与 token 经济性
239
+
240
+ 不要谈:
241
+ - 对话中讨论的具体技术 / 代码是否正确(如「你写的 SQL JOIN 错了」、
242
+ 「这个 React Hook 用法不对」、「应该用更高效的算法」)
243
+ - 业务领域内的推荐(「应该改用 PostgreSQL 而不是 MySQL」)
244
+ - 任何需要运行代码 / 看完整仓库才能下的判断
245
+ 上述话题属于代码评审,不属于协作分析,即使你能看出问题也不要写。
246
+
247
+ 如果对话里只有业务讨论、看不出可改进的协作模式,5 个类别都给空数组,
248
+ summary 写「本会话以业务讨论为主,协作模式无明显问题」。
249
+
250
+ # 输出契约
251
+
252
+ 只输出严格 JSON,不要 markdown 代码块,不要多余文字。结构如下:
253
+
254
+ {
255
+ "summary": "≤60 字的一句话总评(只谈协作)",
256
+ "categories": {
257
+ "cost": [AdviceItem, ...],
258
+ "accuracy": [AdviceItem, ...],
259
+ "context": [AdviceItem, ...],
260
+ "skills": [AdviceItem, ...],
261
+ "workflow": [AdviceItem, ...]
262
+ },
263
+ "rationale": "≤80 字综合理由(只谈协作)"
264
+ }
265
+
266
+ AdviceItem:
267
+ {
268
+ "severity": "high" | "medium" | "low",
269
+ "title": "≤20 字",
270
+ "why": "1 句话,说明协作上的问题",
271
+ "action": "1 句话,具体可操作的改变(下次怎么做)",
272
+ "evidence": "引自第 N 条消息 / 工具 X / 基础统计 — 必须是对话事实,不得引用任何评分",
273
+ "actionable": true | false,
274
+ "executor": "opencode" | "claude" | "manual",
275
+ "cwd_hint": "project_root"
276
+ }
277
+
278
+ # 硬规则
279
+
280
+ 1. 5 个 categories 键必须存在,无内容给空数组。
281
+ 2. 全部 AdviceItem 总数 ≤ 6 条,只挑最值得改的;按 severity 由高到低排。
282
+ 3. 每条 evidence 必须能在下文对话或基础统计中找到原话/原始数字;
283
+ 出现「分数」「等级」「Lx」「子分」「H1」「H2」「E1」「O1」字眼一律视为违规。
284
+ 4. action 必须是「下次怎么做」级别的协作动作,不是「这段代码应该改成 X」。
285
+ 5. 类别定义:
286
+ - cost 省钱:模型档位、prompt 长度、工具调用次数、缓存利用。
287
+ - accuracy 提准确率(协作层):暴露隐藏假设、要求 AI 自检、加入验证步骤。
288
+ 不是「业务结论是否正确」。
289
+ - context 上下文准备:开场是否给出文件 / 依赖 / 约束 / 示例 / 期望输出。
290
+ - skills 推荐新建或使用 opencode skill / subagent。
291
+ 每条 action 给出:skill 名 + 触发条件 + 一句话用途。
292
+ - workflow 流程与节奏:拆解、迭代步幅、回退策略、人 ↔ AI 分工。
293
+
294
+ 6. actionable=true 的条件必须同时满足:
295
+ - 是「写代码 / 改文件 / 加 skill」类具体动作;
296
+ - 在原项目根目录运行 opencode/claude 就能完成,无需补充人类专属知识。
297
+ 不属于这一类(如「下次开场用模板」「以后多用缓存」「对 AI 的指令更
298
+ 具体」)的 → actionable=false, executor='manual'。
299
+
300
+ 7. executor:
301
+ - 显式动手做的任务(创建文件、改代码、写 skill) → 'opencode' 或 'claude';
302
+ - 单纯让人类调整行为的建议 → 'manual'。
303
+ 不会判断时填 'opencode'。
304
+
305
+ 8. cwd_hint: 目前只能填 "project_root"。
306
+
307
+ 9. actionable 与 executor 必须一致:executor='manual' 时 actionable 必须 false;
308
+ executor 是 'opencode'/'claude' 时 actionable 通常 true。
309
+
310
+ # 会话基础(只作事实参考,不要换算成分数)
311
+
312
+ 模型: ${s.model || '未知'}
313
+ 难度: ${s.difficulty ?? '?'} / 4
314
+ 时长: ${fmtNum(s.durationMinutes)} 分钟
315
+ 消息: ${fmtNum(s.messageCount)} 条 (用户 ${fmtNum(s.userCount)} / 助手 ${fmtNum(s.assistantCount)})
316
+ 工具调用: ${fmtNum(s.toolCallCount)} 次 错误 ${fmtNum(s.errorCount)}
317
+ Token: in ${fmtNum(t.input)} / out ${fmtNum(t.output)} / reasoning ${fmtNum(t.reasoning)} / cacheR ${fmtNum(t.cacheRead)} / cacheW ${fmtNum(t.cacheWrite)}
318
+ 成本: $${typeof s.cost === 'number' ? s.cost.toFixed(4) : '–'}
319
+ 已回退: ${s.reverted ? '是' : '否'}
320
+
321
+ # 工具使用 Top 20
322
+
323
+ ${fmtToolTable(ctx.toolBreakdown)}
324
+
325
+ # 消息全文 ${truncatedNote}
326
+
327
+ ${fmtMessages(ctx.messages)}
328
+
329
+ —— 输出 JSON ——`;
330
+ }
331
+
332
+ module.exports = {
333
+ ADVICE_SENTINEL,
334
+ ADVICE_PROMPT_VERSION,
335
+ CATEGORIES,
336
+ DEFAULT_MAX_BYTES,
337
+ buildAdvicePrompt,
338
+ truncateContext,
339
+ };