@iiwish/agentrecord 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,608 @@
1
+ import { safePathSegment } from "../core/config.mjs";
2
+ import { dimensions, roles } from "./catalog.mjs";
3
+ import { unique } from "./utils.mjs";
4
+
5
+ function getArchetype(roleSignals, abilityModel, stats, identityConfidence) {
6
+ const getScore = (list, id) => list.find((item) => (item.role_id || item.dimension_id) === id)?.score || 40;
7
+ const dominantRole = roleSignals[0] || null;
8
+ const dominantAbility = abilityModel[0] || null;
9
+
10
+ const system = getScore(roleSignals, "systems_thinker");
11
+ const product = getScore(roleSignals, "product_builder");
12
+ const reviewer = getScore(roleSignals, "technical_reviewer");
13
+ const operator = getScore(roleSignals, "agent_operator");
14
+ const shipOwner = getScore(roleSignals, "shipping_owner");
15
+
16
+ const verify = getScore(abilityModel, "verification_discipline");
17
+ const shipHygiene = getScore(abilityModel, "shipping_hygiene");
18
+ const context = getScore(abilityModel, "context_packaging");
19
+ const goal = getScore(abilityModel, "goal_framing");
20
+
21
+ const focus = system >= product ? "S" : "P";
22
+ const execution = reviewer >= operator ? "R" : "O";
23
+ const quality = verify >= shipHygiene ? "V" : "D";
24
+ const scope = context >= goal ? "C" : "M";
25
+
26
+ const code = `${focus}${execution}${quality}${scope}`;
27
+
28
+ const rigor = Math.max(45, Math.min(99, Math.round((reviewer + verify) / 2) + 6));
29
+ const control = Math.max(45, Math.min(99, Math.round((operator + context) / 2) + 4));
30
+ const strategic = Math.max(45, Math.min(99, Math.round((system + goal) / 2) + 2));
31
+ const closedLoop = Math.max(45, Math.min(99, Math.round((shipOwner + shipHygiene) / 2) + 5));
32
+
33
+ const candidates = [
34
+ { val: reviewer, label: "代码质检委" },
35
+ { val: operator, label: "多核驯兽师" },
36
+ { val: verify, label: "无情测试监工" },
37
+ { val: shipOwner, label: "一键闭环机器" },
38
+ { val: system, label: "底层架构信徒" },
39
+ { val: product, label: "精益MVP狂魔" },
40
+ { val: context, label: "上下文包工头" },
41
+ { val: goal, label: "黑话粉碎机" }
42
+ ];
43
+ candidates.sort((a, b) => b.val - a.val);
44
+ const tags = candidates.slice(0, 3).map((c) => `#${c.label}`);
45
+
46
+ let traceDays = 1;
47
+ try {
48
+ const start = new Date(stats.trace_window?.start);
49
+ const end = new Date(stats.trace_window?.end);
50
+ const diff = Math.round((end - start) / (1000 * 60 * 60 * 24));
51
+ if (diff > 0) traceDays = diff;
52
+ } catch {
53
+ traceDays = 42;
54
+ }
55
+
56
+ const activeStatus = identityConfidence === "high" ? "99% 高频狂热" : identityConfidence === "medium" ? "85% 默契协从" : "65% 试探磨合";
57
+
58
+ const mapping = {
59
+ "SRVC": {
60
+ name: "AI 架构审查官",
61
+ enName: "THE CHIEF JUSTICE",
62
+ tagline: "“代码可以不跑,但测试必须全绿。”",
63
+ enTagline: "No green test, no release.",
64
+ talent: "像素级代码审计,把 AI 治得服服帖帖",
65
+ quote: "“把证据拿出来,没跑过测试别跟我谈交付。”",
66
+ weakness: "宁可在本地写 3 小时规范提示词,也不愿意自己动手改一行 JS。",
67
+ status: "每天都在跟 AI 进行严密的法庭辩论,直至其乖乖跑通所有校验。"
68
+ },
69
+ "SRVM": {
70
+ name: "大局观安全审计员",
71
+ enName: "THE STRATEGIC AUDITOR",
72
+ tagline: "“AI 会撒谎,但系统的边界不会。”",
73
+ enTagline: "AI lies, but system boundaries don't.",
74
+ talent: "宏观系统设计,在脑海中沙盘演练 AI 的执行过程",
75
+ quote: "“你的上下文很漂亮,但这和我们的交付有什么关系?”",
76
+ weakness: "架构图画得很完美,但经常忘记在本地配置环境变量。",
77
+ status: "拿着法槌,看着一打 Agent 跑来跑去,在失控边缘按下 Stop 键。"
78
+ },
79
+ "SROC": {
80
+ name: "全栈控场大师",
81
+ enName: "THE SYSTEM CONTROL MASTER",
82
+ tagline: "“并发 10 个 Agent,是我作为一个指挥官的尊严。”",
83
+ enTagline: "Orchestrating agents is my dignity.",
84
+ talent: "高效打包复杂上下文,让 AI 一次性精准理解系统意图",
85
+ quote: "“我已经打包好了 repo 的 AST 树和 5 个报错日志,去吧。”",
86
+ weakness: "上下文打包太重,经常让 AI 陷入深度死循环并耗尽 Token。",
87
+ status: "双手插袋,看着终端里 agent 的并发日志以 1000 行/秒的速度飞逝。"
88
+ },
89
+ "SROM": {
90
+ name: "系统级 AI 牧羊人",
91
+ enName: "THE ARCHITECT OPERATOR",
92
+ tagline: "“我只负责指明方向,剩下的路由 AI 自己搞定。”",
93
+ enTagline: "I define the vectors; agents find the path.",
94
+ talent: "顶层逻辑清晰,擅长用最简洁的契约指令指派宏大系统",
95
+ quote: "“我们要重构整个后端。去吧,自己跑通 schema 和迁移。”",
96
+ weakness: "过于相信 AI 的自主性,睡醒一觉发现 AI 把测试库全清空了。",
97
+ status: "每天跑着 5 个 autonomous 脚本,感觉自己像是虚拟科技公司 CEO。"
98
+ },
99
+ "SDVC": {
100
+ name: "流水线完美主义者",
101
+ enName: "THE PIPELINE PERFECTER",
102
+ tagline: "“把安全和验证,刻进持续集成的每一秒。”",
103
+ enTagline: "Embed security into every single second of CI.",
104
+ talent: "全自动交付与严格本地验证的双重重锁把关人",
105
+ quote: "“没有自动化脚本保护的交付,只是一盘散沙。”",
106
+ weakness: "本地脚本一报错就陷入强迫症,非要重构到完美才肯吃饭。",
107
+ status: "乐此不疲地编写第 24 个自动化校验挂钩,把发布卫生拉到最满。"
108
+ },
109
+ "SDVM": {
110
+ name: "交付防御之盾",
111
+ enName: "THE SHIELD OF DELIVERY",
112
+ tagline: "“安全交付,是一门克制的艺术。”",
113
+ enTagline: "Safe shipping is an art of restraint.",
114
+ talent: "懂得权衡发布边界与代码质量,用规则防范 AI 暴走",
115
+ quote: "“AI 生成的代码?先过我的脱敏边界和沙箱审查。”",
116
+ weakness: "过于热衷构建安全基础设施,导致产品本体进度被一拖再拖。",
117
+ status: "悠闲地品着咖啡,欣赏 GitHub Actions 运行出的完美绿色勾勾。"
118
+ },
119
+ "SDOC": {
120
+ name: "高频发布狂魔",
121
+ enName: "THE VELOCITY OPERATOR",
122
+ tagline: "“只要我发布得够快,Bug 就追不上我。”",
123
+ enTagline: "If I ship fast enough, bugs can't catch me.",
124
+ talent: "极速环境调度,AI 刚写完代码就已自动推向 staging 环境",
125
+ quote: "“这行过了?马上 build 部署,我们在生产环境看反馈。”",
126
+ weakness: "过于追求交付速度,有时会把评审环节完全变成形式主义。",
127
+ status: "正在疯狂执行一键发布,并在 Slack 里高频 @ 所有人查看更新。"
128
+ },
129
+ "SDOM": {
130
+ name: "云原生大魔王",
131
+ enName: "THE PLATFORM SHAMAN",
132
+ tagline: "“用代码定义一切,用 AI 吞噬架构。”",
133
+ enTagline: "Define everything in code; let AI eat the architecture.",
134
+ talent: "宏观掌控云原生部署与发布流,擅长复杂的多服务编排",
135
+ quote: "“把这个微服务路由到网关,顺便让 AI 生成三套 K8s配置。”",
136
+ weakness: "对底层细节极度不屑,经常因为一个微小的拼写错误导致容器崩溃。",
137
+ status: "正在指挥三个 AI 编写 Helm Charts,嘴里念叨着「声明式基础设施」。"
138
+ },
139
+ "PRVC": {
140
+ name: "像素级体验判官",
141
+ enName: "THE UX GUARDIAN",
142
+ tagline: "“别跟我谈什么底层架构,用户看得到的才算数。”",
143
+ enTagline: "Don't talk architecture; only what users see counts.",
144
+ talent: "像素、排版、动画无一漏网,拥有超群的视觉强迫症",
145
+ quote: "“这个中文字体怎么落了 2 像素?让 AI 重新写一版 CSS 动画。”",
146
+ weakness: "经常为了一个按钮的悬停动画,和 AI 纠缠搏斗一整天。",
147
+ status: "正在严厉审查 CJK 字符的边缘截断和 terminal alignment。"
148
+ },
149
+ "PRVM": {
150
+ name: "产品体验教皇",
151
+ enName: "THE PRODUCT EVANGELIST",
152
+ tagline: "“AI 能理解代码,但我能理解人类的心灵。”",
153
+ enTagline: "AI understands code, but I understand the human soul.",
154
+ talent: "无与伦比的产品嗅觉,确保 AI 的所有产出都符合极致人性",
155
+ quote: "“这个功能链路太反人类了,重新把 user story 梳理一遍。”",
156
+ weakness: "对复杂的底层逻辑不感冒,导致架构层面有时略显混乱。",
157
+ status: "拿着白板笔,给 AI 解释为什么这个「极简设计」能带来 200% 转化率。"
158
+ },
159
+ "PROC": {
160
+ name: "MVP 缝合巨匠",
161
+ enName: "THE SHIELD OF FEASIBILITY",
162
+ tagline: "“缝合,也是一种伟大的、充满生产力的艺术。”",
163
+ enTagline: "Stitching is a great, productive art form.",
164
+ talent: "以肉眼不可见的速度将 AI 的零碎代码粘合成可跑的 Demo",
165
+ quote: "“我把 5 个会话的代码拼在一起了,别动它,它居然能跑!”",
166
+ weakness: "代码内部极其混乱,后人维护时常常发出绝望的叹息。",
167
+ status: "疯狂在多个 chat 窗口中复制粘贴,暗自祈祷 staging 别炸。"
168
+ },
169
+ "PROM": {
170
+ name: "一键出海创客",
171
+ enName: "THE INDIE SHARK",
172
+ tagline: "“今天早上有灵感,明天晚上就上线收费。”",
173
+ enTagline: "Inspired this morning, launched and charging tomorrow night.",
174
+ talent: "极强的商业敏锐度,用 AI 闪电战快速占领长尾流量",
175
+ quote: "“用最快的速度把落地页和支付接上,马上开始投流测试。”",
176
+ weakness: "没有任何代码整洁度可言,遇到 Bug 全靠 AI 进行暴力热修复。",
177
+ status: "一边在推特上吹嘘自己刚用 AI 赚了第一桶金,一边叫 AI 改 landing page。"
178
+ },
179
+ "PDVC": {
180
+ name: "商业验证尖兵",
181
+ enName: "THE FEASIBILITY AUDITOR",
182
+ tagline: "“用最小的成本做最严的测试,不交一分智商税。”",
183
+ enTagline: "Min cost, max testing. No wasted cycles.",
184
+ talent: "砍需求刀法极其狠辣,用精准测试保障最核心的业务闭环",
185
+ quote: "“这个功能暂时不要,我们先用 AI 测通核心买单逻辑。”",
186
+ weakness: "极度讨厌长期技术重构,遇到大型架构改动就想换新坑。",
187
+ status: "正在盯着冒烟测试结果,盘算着今晚的产品发布计划。"
188
+ },
189
+ "PDVM": {
190
+ name: "敏捷产品掌舵人",
191
+ enName: "THE AGILE HELMSMAN",
192
+ tagline: "“方向比速度重要,但速度就是最好的方向。”",
193
+ enTagline: "Direction beats speed, but speed is the best direction.",
194
+ talent: "极其擅长给 AI 定义清晰的目标,并严格控制版本发布节奏",
195
+ quote: "“这版 MVP 体验刚好卡在及格线上,马上开始灰度测试。”",
196
+ weakness: "项目交付后容易失去兴趣,转头开始构思下一个酷炫点子。",
197
+ status: "对 AI 团队下达「不要偏离 MVP,下个迭代再加功能」的冷静指示。"
198
+ },
199
+ "PDOC": {
200
+ name: "闪电战产品经理",
201
+ enName: "THE LIGHTNING BUILDER",
202
+ tagline: "“天下武功,唯快不破。AI,全军出击!”",
203
+ enTagline: "In the land of AI, speed is king. Charge!",
204
+ talent: "带着一打 AI 进行饱和攻击式开发,24小时连发 10 个新功能",
205
+ quote: "“AI 写的原型非常好,再加 3 个页面,今晚我们直接发布。”",
206
+ weakness: "文档严重稀缺,项目维护全靠脑补,AI 稍微改一下就容易迷失。",
207
+ status: "Staging 环境一小时内被他更新了 15 次,开发服务器在尖叫。"
208
+ },
209
+ "PDOM": {
210
+ name: "野性生长创客",
211
+ enName: "THE WILD GROWER",
212
+ tagline: "“我发布,我倾听,我调整。”",
213
+ enTagline: "I launch, I listen, I pivot.",
214
+ talent: "完美将增长直觉转化为 AI 提示词,疯狂生产高转化页面",
215
+ quote: "“只要用户愿意点击,里面的代码就算是用胶水粘的也无所谓。”",
216
+ weakness: "技术债务越滚越大,最终项目重构成本可能会高达天文数字。",
217
+ status: "正在紧盯着数据漏斗,命令 AI 在一分钟内修改主页的大字报标题。"
218
+ }
219
+ };
220
+
221
+ const mapped = mapping[code] || mapping["SRVC"];
222
+ const cardTheme = getCardTheme(dominantRole?.role_id, dominantAbility?.dimension_id);
223
+ const signature = buildSignature({ dominantRole, dominantAbility, stats, identityConfidence });
224
+
225
+ return {
226
+ code,
227
+ axes: { focus, execution, quality, scope },
228
+ scores: { system, product, reviewer, operator, verify, ship: shipHygiene, context, goal },
229
+ dominant: {
230
+ role_id: dominantRole?.role_id || null,
231
+ role_label: dominantRole?.label || null,
232
+ ability_id: dominantAbility?.dimension_id || null,
233
+ ability_label: dominantAbility?.label || null
234
+ },
235
+ card_theme: cardTheme,
236
+ signature,
237
+ rigor,
238
+ control,
239
+ strategic,
240
+ closedLoop,
241
+ tags,
242
+ traceDays,
243
+ activeStatus,
244
+ ...mapped
245
+ };
246
+ }
247
+
248
+ function getCardTheme(roleId, abilityId) {
249
+ const byRole = {
250
+ technical_reviewer: {
251
+ id: "audit",
252
+ label: "审查型",
253
+ accent: "#0b695e",
254
+ accentSoft: "#e4f4f2",
255
+ field: "#e1ebd2",
256
+ page: "#f0f3ef",
257
+ gold: "#c28d2e",
258
+ stripGradient: "linear-gradient(180deg, #153f39 0%, #0b695e 35%, #d09a2f 70%, #121816 100%)",
259
+ chipGradient: "linear-gradient(135deg, #f4cc50 0%, #b88308 100%)",
260
+ bannerPattern: "repeating-linear-gradient(45deg, rgba(255,255,255,0.05) 0 8px, transparent 8px 16px)"
261
+ },
262
+ agent_operator: {
263
+ id: "operator",
264
+ label: "调度型",
265
+ accent: "#315f8d",
266
+ accentSoft: "#e4eef8",
267
+ field: "#dfe9f5",
268
+ page: "#eff3f7",
269
+ gold: "#b47432",
270
+ stripGradient: "linear-gradient(180deg, #1f3555 0%, #315f8d 35%, #20a9a3 72%, #0e151f 100%)",
271
+ chipGradient: "linear-gradient(135deg, #8bc7ff 0%, #315f8d 100%)",
272
+ bannerPattern: "linear-gradient(90deg, rgba(255,255,255,0.08) 1px, transparent 1px), linear-gradient(rgba(255,255,255,0.05) 1px, transparent 1px)"
273
+ },
274
+ shipping_owner: {
275
+ id: "shipping",
276
+ label: "交付型",
277
+ accent: "#7a3024",
278
+ accentSoft: "#fae8df",
279
+ field: "#f0dfcf",
280
+ page: "#f6f0ea",
281
+ gold: "#c39a31",
282
+ stripGradient: "linear-gradient(180deg, #7a3024 0%, #c84f2f 32%, #c39a31 68%, #21120f 100%)",
283
+ chipGradient: "linear-gradient(135deg, #ffd36b 0%, #b56a20 100%)",
284
+ bannerPattern: "repeating-linear-gradient(-45deg, rgba(255,255,255,0.08) 0 5px, transparent 5px 12px)"
285
+ },
286
+ systems_thinker: {
287
+ id: "systems",
288
+ label: "系统型",
289
+ accent: "#394667",
290
+ accentSoft: "#e7ebf3",
291
+ field: "#dde4ed",
292
+ page: "#f1f2f5",
293
+ gold: "#a98c3a",
294
+ stripGradient: "linear-gradient(180deg, #1d2437 0%, #394667 42%, #7f8a59 74%, #10131c 100%)",
295
+ chipGradient: "linear-gradient(135deg, #ccd7e8 0%, #6d7892 100%)",
296
+ bannerPattern: "radial-gradient(circle at 8px 8px, rgba(255,255,255,0.14) 1.5px, transparent 2px)"
297
+ },
298
+ product_builder: {
299
+ id: "product",
300
+ label: "产品型",
301
+ accent: "#8a3f68",
302
+ accentSoft: "#f5e5ee",
303
+ field: "#f1dce8",
304
+ page: "#f7f1f4",
305
+ gold: "#b99028",
306
+ stripGradient: "linear-gradient(180deg, #8a3f68 0%, #c34f7c 35%, #2e9d8c 70%, #171017 100%)",
307
+ chipGradient: "linear-gradient(135deg, #f6aacb 0%, #8a3f68 100%)",
308
+ bannerPattern: "linear-gradient(135deg, rgba(255,255,255,0.08) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.08) 50%, rgba(255,255,255,0.08) 75%, transparent 75%)"
309
+ },
310
+ research_synthesizer: {
311
+ id: "research",
312
+ label: "研究型",
313
+ accent: "#5a5f2f",
314
+ accentSoft: "#eef0df",
315
+ field: "#e6e8cf",
316
+ page: "#f4f5ec",
317
+ gold: "#b08837",
318
+ stripGradient: "linear-gradient(180deg, #36391f 0%, #5a5f2f 38%, #9a7a35 70%, #15160c 100%)",
319
+ chipGradient: "linear-gradient(135deg, #d7dca2 0%, #787d3b 100%)",
320
+ bannerPattern: "repeating-linear-gradient(90deg, rgba(255,255,255,0.06) 0 2px, transparent 2px 10px)"
321
+ },
322
+ collaboration_handoff: {
323
+ id: "handoff",
324
+ label: "交接型",
325
+ accent: "#31615a",
326
+ accentSoft: "#e3f0ed",
327
+ field: "#dbe9e4",
328
+ page: "#eff4f2",
329
+ gold: "#bf8f2a",
330
+ stripGradient: "linear-gradient(180deg, #203b37 0%, #31615a 36%, #bd8f2e 70%, #111817 100%)",
331
+ chipGradient: "linear-gradient(135deg, #9ed1c4 0%, #31615a 100%)",
332
+ bannerPattern: "repeating-linear-gradient(135deg, rgba(255,255,255,0.08) 0 4px, transparent 4px 14px)"
333
+ }
334
+ };
335
+
336
+ if (byRole[roleId]) return byRole[roleId];
337
+ if (abilityId === "product_judgment" || abilityId === "goal_framing") return byRole.product_builder;
338
+ if (abilityId === "failure_recovery") return byRole.technical_reviewer;
339
+ return byRole.agent_operator;
340
+ }
341
+
342
+ function buildSignature({ dominantRole, dominantAbility, stats, identityConfidence }) {
343
+ const role = dominantRole?.label || "AI 协作操作者";
344
+ const ability = dominantAbility?.label || "本地活跃度";
345
+ const sessions = stats.files || 0;
346
+ const tokens = stats.total_token_usage?.total_tokens || 0;
347
+ const perSession = sessions > 0 ? Math.round(tokens / sessions) : 0;
348
+ const density = perSession >= 20_000_000 ? "深上下文" : sessions >= 300 ? "高频调用" : "稳态记录";
349
+ const confidence = identityConfidence === "medium" || identityConfidence === "high" ? "证据已成型" : "基线画像";
350
+ return `${role} · ${ability} · ${density} · ${confidence}`;
351
+ }
352
+
353
+ export function buildProfile({ config, stats, codexAccountUsage, evidenceCards, report, localeBundle, runMetadata, agentContextEnabled }) {
354
+ const evidenceIds = evidenceCards.map((card) => card.id);
355
+ const roleSignals = buildRoleSignals(evidenceCards, localeBundle);
356
+ const abilityModel = buildAbilityModel(evidenceCards, localeBundle);
357
+ const hasCuratedEvidence = evidenceCards.some((card) => card.id !== "EV-ACTIVITY-METADATA");
358
+ const identityConfidence = hasCuratedEvidence ? "medium" : stats.files > 0 ? "low-medium" : "low";
359
+ const archetype = getArchetype(roleSignals, abilityModel, stats, identityConfidence);
360
+ const topRole = roleSignals[0] || roleSignals.find((item) => item.role_id === "agent_operator");
361
+ const topAbility = abilityModel[0] || abilityModel.find((item) => item.dimension_id === "agent_delegation");
362
+ const topRoleEvidence = topRole?.evidence_ids?.slice(0, 3) || [];
363
+
364
+ return {
365
+ schema_version: "agentrecord.profile.v0",
366
+ owner: {
367
+ id: safePathSegment(config.resolved.owner),
368
+ display_name: config.resolved.ownerDisplayName || config.resolved.owner
369
+ },
370
+ generated_at: report.generated_at,
371
+ report,
372
+ archetype,
373
+ work_identity: {
374
+ primary_label: hasCuratedEvidence ? "Evidence-bound AI work operator" : "Local AI-agent activity baseline",
375
+ localized_label: report.locale === "zh-CN" ? (hasCuratedEvidence ? "证据约束的 AI 工作操作者" : "本地 AI Agent 活跃度基线") : null,
376
+ summary: hasCuratedEvidence
377
+ ? `AgentRecord found ${evidenceCards.length} public-safe evidence cards for how this owner frames, delegates, reviews, verifies, and hands off AI-agent work.`
378
+ : `AgentRecord found local Codex activity and generated a conservative baseline profile without curated memory evidence.`,
379
+ strongest_claim: hasCuratedEvidence
380
+ ? `Strongest current signal: ${topRole?.label || "agent operation"} with ${topRole?.confidence || identityConfidence} confidence, supported by ${topRoleEvidence.join(", ") || evidenceIds.slice(0, 2).join(", ")}.`
381
+ : "The defensible claim is repeated local AI-agent usage, not calibrated work quality.",
382
+ confidence: identityConfidence,
383
+ evidence_ids: evidenceIds.slice(0, 6)
384
+ },
385
+ work_role_signals: roleSignals,
386
+ ability_model: abilityModel,
387
+ agent_ledger: {
388
+ clients: [
389
+ {
390
+ client_id: "codex",
391
+ status: stats.files > 0 ? "measured" : "not_found",
392
+ sessions: stats.files,
393
+ token_sessions: stats.token_sessions,
394
+ token_usage: stats.total_token_usage,
395
+ usage_source: "local_codex_logs",
396
+ account_usage: codexAccountUsage || {
397
+ status: "unavailable",
398
+ source: "codex_app_server",
399
+ summary: null,
400
+ daily_usage_buckets: null
401
+ },
402
+ display_usage: buildCodexDisplayUsage(stats, codexAccountUsage),
403
+ trace_window: {
404
+ start: stats.trace_window.start,
405
+ end: stats.trace_window.end
406
+ },
407
+ top_projects: stats.top_projects,
408
+ evidence_count: evidenceCards.length
409
+ },
410
+ { client_id: "claude_code", status: "not_configured" },
411
+ { client_id: "opencode", status: "not_configured" },
412
+ { client_id: "openclaw", status: "not_configured" },
413
+ { client_id: "hermes", status: "not_configured" }
414
+ ]
415
+ },
416
+ evidence_notes: evidenceCards.map((card) => ({
417
+ evidence_id: card.id,
418
+ level: card.level,
419
+ title: card.title,
420
+ summary: card.summary,
421
+ agent_clients: card.agent_clients,
422
+ dimensions: card.dimensions,
423
+ role_signals: card.role_signals,
424
+ confidence: card.confidence,
425
+ refs: card.refs,
426
+ privacy: card.privacy
427
+ })),
428
+ calibration_notes: buildCalibrationNotes({ hasCuratedEvidence, stats, codexAccountUsage }),
429
+ privacy_boundary: {
430
+ local_only_generation: true,
431
+ raw_logs_included: false,
432
+ public_session_ids_included: false,
433
+ public_project_paths_included: config.resolved.privacy.publicProjectPaths,
434
+ public_artifacts: [
435
+ "profile.json",
436
+ "evidence.jsonl",
437
+ "index.html",
438
+ "profile.md",
439
+ "redaction-report.md",
440
+ "run-report.md",
441
+ ...(agentContextEnabled ? ["agent-context.md", "agent-context.json"] : [])
442
+ ],
443
+ excluded_from_public_artifacts: [
444
+ "raw prompts",
445
+ "raw assistant responses",
446
+ "raw session IDs",
447
+ "Codex session file paths",
448
+ "terminal bodies",
449
+ "source bodies",
450
+ "secret-like values"
451
+ ]
452
+ },
453
+ run_metadata: runMetadata
454
+ };
455
+ }
456
+
457
+ function buildCodexDisplayUsage(stats, codexAccountUsage) {
458
+ const accountSummary = codexAccountUsage?.status === "measured" ? codexAccountUsage.summary : null;
459
+ if (accountSummary?.lifetime_tokens) {
460
+ return {
461
+ source: "codex_account_usage",
462
+ source_label: codexAccountUsage.source_label || "Codex CLI account usage",
463
+ token_usage: {
464
+ total_tokens: accountSummary.lifetime_tokens,
465
+ peak_daily_tokens: accountSummary.peak_daily_tokens,
466
+ longest_running_turn_sec: accountSummary.longest_running_turn_sec,
467
+ current_streak_days: accountSummary.current_streak_days,
468
+ longest_streak_days: accountSummary.longest_streak_days
469
+ },
470
+ local_sessions: stats.files,
471
+ local_token_usage: stats.total_token_usage
472
+ };
473
+ }
474
+
475
+ return {
476
+ source: "local_codex_logs",
477
+ source_label: "Local Codex logs",
478
+ token_usage: {
479
+ total_tokens: stats.total_token_usage?.total_tokens || 0,
480
+ peak_daily_tokens: null,
481
+ longest_running_turn_sec: null,
482
+ current_streak_days: null,
483
+ longest_streak_days: null
484
+ },
485
+ local_sessions: stats.files,
486
+ local_token_usage: stats.total_token_usage
487
+ };
488
+ }
489
+
490
+ function buildRoleSignals(evidenceCards, localeBundle) {
491
+ return roles.map((role) => {
492
+ const cards = evidenceCards.filter((card) => card.role_signals.includes(role.id));
493
+ const evidenceLevelMix = countEvidenceLevels(cards);
494
+ const score = scoreForCards(cards);
495
+ return {
496
+ role_id: role.id,
497
+ label: localeBundle.roles?.[role.id] || role.canonical,
498
+ score,
499
+ band: bandForScore(score),
500
+ confidence: confidenceForCards(cards),
501
+ why: cards.length
502
+ ? `Evidence mix ${formatEvidenceMix(evidenceLevelMix)} across ${cards.length} public-safe evidence card${cards.length === 1 ? "" : "s"}.`
503
+ : "No strong public-safe evidence card supports this role signal yet.",
504
+ evidence_level_mix: evidenceLevelMix,
505
+ evidence_ids: cards.map((card) => card.id),
506
+ missing_evidence: cards.length ? [] : ["More non-volume work evidence is needed before making a stronger claim."]
507
+ };
508
+ }).sort((a, b) => b.score - a.score);
509
+ }
510
+
511
+ function buildAbilityModel(evidenceCards, localeBundle) {
512
+ return dimensions.map((dimension) => {
513
+ const cards = evidenceCards.filter((card) => card.dimensions.includes(dimension.id));
514
+ const evidenceLevelMix = countEvidenceLevels(cards);
515
+ const score = scoreForCards(cards);
516
+ return {
517
+ dimension_id: dimension.id,
518
+ label: localeBundle.abilities?.[dimension.id] || dimension.canonical,
519
+ score,
520
+ band: bandForScore(score),
521
+ confidence: confidenceForCards(cards),
522
+ evidence_level_mix: evidenceLevelMix,
523
+ evidence_ids: cards.map((card) => card.id),
524
+ missing_evidence: cards.length ? [] : ["No direct public-safe evidence card yet."]
525
+ };
526
+ }).sort((a, b) => b.score - a.score);
527
+ }
528
+
529
+ function countEvidenceLevels(cards) {
530
+ const mix = { E1: 0, E2: 0, E3: 0, E4: 0 };
531
+ for (const card of cards) {
532
+ for (const level of card.level || []) {
533
+ if (Object.hasOwn(mix, level)) mix[level] += 1;
534
+ }
535
+ }
536
+ return mix;
537
+ }
538
+
539
+ export function formatEvidenceMix(mix = {}) {
540
+ return ["E1", "E2", "E3", "E4"]
541
+ .filter((level) => (mix[level] || 0) > 0)
542
+ .map((level) => `${level}:${mix[level]}`)
543
+ .join(" / ") || "none";
544
+ }
545
+
546
+ function scoreForCards(cards) {
547
+ if (!cards.length) return 40;
548
+ const onlyBaseline = cards.every((card) => card.id === "EV-ACTIVITY-METADATA");
549
+ const levelWeight = cards.reduce((sum, card) => sum
550
+ + (card.level.includes("E1") ? 11 : 0)
551
+ + (card.level.includes("E2") ? 7 : 0)
552
+ + (card.level.includes("E3") ? 3 : 0)
553
+ + (card.level.includes("E4") ? 1 : 0), 0);
554
+ const categorySpread = new Set(cards.map((card) => card.category).filter(Boolean)).size;
555
+ const baseline = onlyBaseline ? 52 : 45;
556
+ const score = baseline + Math.sqrt(levelWeight) * 3.6 + categorySpread * 3.5 + cards.length * 1.5;
557
+ return Math.max(45, Math.min(96, Math.round(score)));
558
+ }
559
+
560
+ function bandForScore(score) {
561
+ if (score >= 78) return "strong";
562
+ if (score >= 68) return "solid";
563
+ if (score >= 56) return "developing";
564
+ return "emerging";
565
+ }
566
+
567
+ function confidenceForCards(cards) {
568
+ const levels = unique(cards.flatMap((card) => card.level || []));
569
+ if (levels.includes("E1") && levels.includes("E2")) return "high";
570
+ if (levels.includes("E2")) return "medium";
571
+ if (levels.includes("E3")) return "medium";
572
+ return cards.length ? "low-medium" : "low";
573
+ }
574
+
575
+ function buildCalibrationNotes({ hasCuratedEvidence, stats, codexAccountUsage }) {
576
+ const notes = [
577
+ {
578
+ type: "privacy_boundary",
579
+ severity: "high",
580
+ summary: "Public artifacts are generated from aggregate metadata and curated summaries only; raw conversation payloads remain excluded."
581
+ },
582
+ {
583
+ type: "activity_not_ability",
584
+ severity: "medium",
585
+ summary: "Token usage and session counts are activity-density context, not direct proof of ability."
586
+ },
587
+ {
588
+ type: "adapter_coverage",
589
+ severity: "medium",
590
+ summary: `Codex is the only measured adapter in this run; ${stats.files} local rollout files were scanned.`
591
+ },
592
+ {
593
+ type: "missing_evidence",
594
+ severity: hasCuratedEvidence ? "low" : "medium",
595
+ summary: hasCuratedEvidence
596
+ ? "The profile still needs external outcomes before making stronger public claims."
597
+ : "No curated memory evidence was available, so the profile stays at activity-baseline strength."
598
+ }
599
+ ];
600
+ notes.push({
601
+ type: "account_usage_boundary",
602
+ severity: codexAccountUsage?.status === "measured" ? "low" : "medium",
603
+ summary: codexAccountUsage?.status === "measured"
604
+ ? "Codex account-level token usage was read through the local Codex CLI app-server; local session counts still come from auditable local traces."
605
+ : "Codex account-level usage was unavailable, so public token totals fall back to auditable local traces."
606
+ });
607
+ return notes;
608
+ }