oh-aicoding-tool 0.1.2 → 0.1.4

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 (55) hide show
  1. package/README.md +79 -80
  2. package/bin/cli.js +257 -384
  3. package/package.json +28 -56
  4. package/CODEX_LANGFUSE_PLAN.md +0 -62
  5. package/bin/langfuse-cli.js +0 -718
  6. package/codex_langfuse_notify.py +0 -591
  7. package/langfuse_hook.py +0 -603
  8. package/opencode-ohai-report/.claude/commands/report-ai-issue.md +0 -60
  9. package/opencode-ohai-report/.opencode/commands/report-ai-issue.md +0 -30
  10. package/opencode-ohai-report/.opencode/plugins/oh-ai-report.ts +0 -569
  11. package/opencode-ohai-report/README.md +0 -45
  12. package/opencode-ohai-report/bin/cli.js +0 -421
  13. package/opencode-ohai-report/docs/opencode-ai-issue-collection-architecture.md +0 -313
  14. package/opencode-ohai-report/docs/opencode-ai-issue-collection-best-practices.md +0 -476
  15. package/opencode-ohai-report/docs/opencode-ai-issue-collection-phase1-summary.md +0 -405
  16. package/opencode-ohai-report/examples/issue_output.json +0 -4
  17. package/opencode-ohai-report/package.json +0 -40
  18. package/opencode-ohai-report/scripts/claude_report_hook.py +0 -257
  19. package/opencode-ohai-report/scripts/create_issue.py +0 -34
  20. package/opencode-ohai-report/scripts/install-claude-plugin.ps1 +0 -254
  21. package/opencode-ohai-report/scripts/install-opencode-plugin.ps1 +0 -264
  22. package/opencode-ohai-report/scripts/install-opencode-plugin.sh +0 -218
  23. package/opencode-ohai-report/scripts/merge-claude-settings.py +0 -99
  24. package/opencode-ohai-report/tools/ohai-report/README.md +0 -151
  25. package/opencode-ohai-report/tools/ohai-report/examples/issue-input.json +0 -26
  26. package/opencode-ohai-report/tools/ohai-report/ohai_report/__init__.py +0 -5
  27. package/opencode-ohai-report/tools/ohai-report/ohai_report/__main__.py +0 -9
  28. package/opencode-ohai-report/tools/ohai-report/ohai_report/cli.py +0 -319
  29. package/opencode-ohai-report/tools/ohai-report/ohai_report/git_context.py +0 -32
  30. package/opencode-ohai-report/tools/ohai-report/ohai_report/gitcode_defaults.py +0 -14
  31. package/opencode-ohai-report/tools/ohai-report/ohai_report/issue_markdown.py +0 -313
  32. package/opencode-ohai-report/tools/ohai-report/ohai_report/metadata.py +0 -360
  33. package/opencode-ohai-report/tools/ohai-report/ohai_report/observability/__init__.py +0 -1
  34. package/opencode-ohai-report/tools/ohai-report/ohai_report/observability/langfuse.py +0 -38
  35. package/opencode-ohai-report/tools/ohai-report/ohai_report/payload.py +0 -64
  36. package/opencode-ohai-report/tools/ohai-report/ohai_report/schema.py +0 -80
  37. package/opencode-ohai-report/tools/ohai-report/ohai_report/sinks/__init__.py +0 -1
  38. package/opencode-ohai-report/tools/ohai-report/ohai_report/sinks/base.py +0 -15
  39. package/opencode-ohai-report/tools/ohai-report/ohai_report/sinks/gitcode.py +0 -405
  40. package/opencode-ohai-report/tools/ohai-report/ohai_report/sinks/local.py +0 -21
  41. package/opencode-ohai-report/tools/ohai-report/ohai_report/sinks/webhook.py +0 -354
  42. package/opencode-ohai-report/tools/ohai-report/ohai_report/webhook_defaults.py +0 -9
  43. package/opencode-ohai-report/tools/ohai-report/ohai_report/workspace.py +0 -61
  44. package/opencode-ohai-report/tools/ohai-report/ohai_report.py +0 -10
  45. package/opencode-ohai-report/tools/ohai-report/schemas/report_issue.schema.json +0 -166
  46. package/scripts/codex-langfuse-check.mjs +0 -101
  47. package/scripts/codex-langfuse-setup.mjs +0 -181
  48. package/scripts/langfuse-check.mjs +0 -90
  49. package/scripts/langfuse-setup.mjs +0 -278
  50. package/scripts/opencode-langfuse-check.mjs +0 -94
  51. package/scripts/opencode-langfuse-run.mjs +0 -96
  52. package/scripts/opencode-langfuse-setup.mjs +0 -478
  53. package/scripts/resolve-opencode-cli.mjs +0 -58
  54. package/setup-langfuse.bat +0 -163
  55. package/setup-langfuse.sh +0 -130
@@ -1,405 +0,0 @@
1
- # OpenCode 问题采集一阶段工作总结
2
-
3
- ## 1. 阶段目标
4
-
5
- 一阶段目标是先跑通 OpenCode 场景下的问题采集最小闭环:
6
-
7
- - 在 OpenCode 中提供 `/report-ai-issue` 用户入口。
8
- - 使用 `subtask: true` 让 subagent 进行轻量模板化,降低主上下文污染。
9
- - 只上传问题模板字段和元信息索引,不上传完整日志、diff、transcript、源码或工具输出。
10
- - 通过 `session_id`、`trace_id`、`langfuse_url` 关联 Langfuse 中的完整会话和日志。
11
- - 将核心能力沉淀为可维护、可升级的 `ohai-report` CLI 模块。
12
-
13
- ## 2. 已完成内容
14
-
15
- ### 2.1 OpenCode 命令入口
16
-
17
- 已新增:
18
-
19
- ```text
20
- .opencode/commands/report-ai-issue.md
21
- ```
22
-
23
- 能力:
24
-
25
- - 提供 `/report-ai-issue <用户问题>` 入口。
26
- - 配置 `subtask: true`,让模板化过程在 subagent 中执行。
27
- - 明确约束 subagent 只基于用户输入生成模板字段。
28
- - 禁止读取完整日志、diff、源码、transcript。
29
- - 调用 `ohai-report` CLI 完成落盘。
30
- - 主会话最终只返回 `已上报:<issue_id>`。
31
-
32
- ### 2.2 OpenCode Plugin 薄适配器
33
-
34
- 已新增:
35
-
36
- ```text
37
- .opencode/plugins/oh-ai-report.ts
38
- ```
39
-
40
- 能力:
41
-
42
- - 监听 OpenCode session/command 事件。
43
- - 尝试刷新本地 metadata。
44
- - 提供可选 `report_ai_issue` custom tool。
45
- - 不采集、不上传完整日志。
46
-
47
- ### 2.3 `ohai-report` CLI 模块化
48
-
49
- 已将原型 CLI 拆分为模块化结构:
50
-
51
- ```text
52
- tools/ohai-report/
53
- ohai_report.py
54
- README.md
55
- examples/
56
- issue-input.json
57
- schemas/
58
- report_issue.schema.json
59
- ohai_report/
60
- __init__.py
61
- __main__.py
62
- cli.py
63
- schema.py
64
- metadata.py
65
- payload.py
66
- workspace.py
67
- git_context.py
68
- sinks/
69
- __init__.py
70
- base.py
71
- local.py
72
- gitcode.py
73
- ```
74
-
75
- 模块职责:
76
-
77
- | 模块 | 职责 |
78
- | --- | --- |
79
- | `ohai_report.py` | 兼容入口,保持现有调用路径不变 |
80
- | `cli.py` | 解析命令,编排校验、metadata、payload、sink |
81
- | `schema.py` | 读取 schema 并执行校验 |
82
- | `schemas/report_issue.schema.json` | 维护问题模板字段、枚举和长度约束 |
83
- | `metadata.py` | 解析 session、trace、user、git context |
84
- | `payload.py` | 组装标准 ReportPayload |
85
- | `sinks/local.py` | 本地 JSON 落盘 |
86
- | `sinks/gitcode.py` | GitCode sink 预留扩展点 |
87
-
88
- ### 2.4 问题模板 schema 外置
89
-
90
- 已新增:
91
-
92
- ```text
93
- tools/ohai-report/schemas/report_issue.schema.json
94
- ```
95
-
96
- 当前字段:
97
-
98
- ```text
99
- title
100
- category
101
- summary
102
- expected_behavior
103
- actual_behavior
104
- severity
105
- user_description
106
- ```
107
-
108
- 当前枚举:
109
-
110
- ```text
111
- category:
112
- 模型输出错误
113
- 工具调用失败
114
- Skill 缺陷
115
- 上下文缺失
116
- 环境问题
117
- 其他
118
-
119
- severity:
120
- P0
121
- P1
122
- P2
123
- P3
124
- ```
125
-
126
- 维护收益:
127
-
128
- - 新增字段优先改 schema。
129
- - subagent prompt 与 schema 对齐即可。
130
- - CLI 支持 `--field key=value` 和 `--issue-file`,便于扩展字段。
131
- - 不需要频繁修改 sink 或 payload 逻辑。
132
-
133
- ### 2.5 本地 Issue 落盘
134
-
135
- 当前 issue 输出目录:
136
-
137
- ```text
138
- .ohai-report/issues/
139
- .ohai-report/latest.json
140
- .ohai-report/metadata.json
141
- ```
142
-
143
- `.ohai-report/` 已加入 `.gitignore`,避免运行产物误提交。
144
-
145
- 当前 payload 示例结构:
146
-
147
- ```json
148
- {
149
- "issue_id": "AI-DEV-20260508144314435120",
150
- "created_at": "2026-05-08T14:43:14+08:00",
151
- "source": "opencode",
152
- "agent": "opencode",
153
- "session_id": "opencode-session-arch",
154
- "trace_id": "trace-arch",
155
- "langfuse_url": "https://langfuse.example.com/project/demo/traces/trace-arch",
156
- "user": {
157
- "id": "Geralt",
158
- "name": "Geralt",
159
- "team": ""
160
- },
161
- "issue": {
162
- "title": "Schema固定参数验证",
163
- "category": "其他",
164
- "summary": "验证固定参数兼容",
165
- "expected_behavior": "生成 payload",
166
- "actual_behavior": "生成 JSON",
167
- "severity": "P3",
168
- "user_description": "测试固定参数路径"
169
- },
170
- "privacy": {
171
- "uploads_logs": false,
172
- "uploads_diff": false,
173
- "uploads_transcript": false
174
- },
175
- "status": "created-local"
176
- }
177
- ```
178
-
179
- ## 3. 使用方式
180
-
181
- ### 3.1 在 OpenCode 中使用
182
-
183
- 在项目根目录启动 OpenCode:
184
-
185
- ```powershell
186
- cd C:\Users\Geralt\Desktop\problem_collect
187
- opencode
188
- ```
189
-
190
- 在 OpenCode 中输入:
191
-
192
- ```text
193
- /report-ai-issue hdc 远程服务器无法连接本地开发板
194
- ```
195
-
196
- 预期主会话只返回:
197
-
198
- ```text
199
- 已上报:AI-DEV-xxxx
200
- ```
201
-
202
- ### 3.2 命令行创建 Issue
203
-
204
- ```powershell
205
- python tools/ohai-report/ohai_report.py create `
206
- --source opencode `
207
- --title "HDC远程连接失败" `
208
- --category "环境问题" `
209
- --summary "远程 OpenCode 无法访问本地 HDC 设备" `
210
- --expected-behavior "能够给出本地设备访问配置提示" `
211
- --actual-behavior "无法发现或连接本地开发板" `
212
- --severity P2 `
213
- --user-description "hdc 远程服务器无法连接本地开发板" `
214
- --metadata auto `
215
- --quiet `
216
- --json
217
- ```
218
-
219
- ### 3.3 更新 metadata
220
-
221
- ```powershell
222
- python tools/ohai-report/ohai_report.py metadata update `
223
- --source opencode `
224
- --session-id "opencode-session-xxx" `
225
- --trace-id "langfuse-trace-xxx" `
226
- --langfuse-url "https://langfuse.example.com/project/xxx/traces/xxx" `
227
- --json
228
- ```
229
-
230
- ### 3.4 使用 issue 文件
231
-
232
- ```powershell
233
- python tools/ohai-report/ohai_report.py create `
234
- --source opencode `
235
- --issue-file tools/ohai-report/examples/issue-input.json `
236
- --metadata auto `
237
- --quiet `
238
- --json
239
- ```
240
-
241
- ## 4. 验证结果
242
-
243
- 已完成以下验证:
244
-
245
- ```text
246
- python -m py_compile ...
247
- python tools/ohai-report/ohai_report.py metadata update ...
248
- python tools/ohai-report/ohai_report.py create ... 固定参数
249
- python tools/ohai-report/ohai_report.py create ... --field scenario=HDC
250
- python tools/ohai-report/ohai_report.py create ... --issue-file tools/ohai-report/examples/issue-input.json
251
- ```
252
-
253
- 验证结论:
254
-
255
- - CLI 拆包后兼容原调用路径。
256
- - `metadata update` 可正常写入 `.ohai-report/metadata.json`。
257
- - `create` 可正常生成 `.ohai-report/issues/<issue_id>.json`。
258
- - `--field` 可支持未来扩展字段。
259
- - `--issue-file` 可避免 Windows shell 下 JSON 引号问题。
260
- - issue_id 已升级为微秒级,避免同一秒内重复创建覆盖文件。
261
- - payload 不包含完整日志、diff、transcript、源码或工具输出。
262
-
263
- ## 5. 当前架构特点
264
-
265
- ### 5.1 低上下文污染
266
-
267
- 通过 OpenCode command 的 `subtask: true` 降低主上下文污染。
268
-
269
- 预期主上下文只保留:
270
-
271
- ```text
272
- 用户:/report-ai-issue xxx
273
- 助手:已上报:AI-DEV-xxx
274
- ```
275
-
276
- ### 5.2 日志链路解耦
277
-
278
- Issue 不保存日志正文,只保存:
279
-
280
- ```text
281
- session_id
282
- trace_id
283
- langfuse_url
284
- ```
285
-
286
- 完整日志、工具调用、trace 和 session 通过 Langfuse 查询。
287
-
288
- ### 5.3 Agent 与后端解耦
289
-
290
- OpenCode 只作为 adapter:
291
-
292
- ```text
293
- OpenCode command/plugin
294
- -> ohai-report CLI
295
- -> sink
296
- ```
297
-
298
- 未来支持 Claude Code、Codex 或其他 Agent 时,只需新增 adapter,不需要重做上报核心。
299
-
300
- ### 5.4 Sink 可替换
301
-
302
- 当前使用本地 JSON sink:
303
-
304
- ```text
305
- tools/ohai-report/ohai_report/sinks/local.py
306
- ```
307
-
308
- 后续可增加:
309
-
310
- ```text
311
- tools/ohai-report/ohai_report/sinks/gitcode.py
312
- tools/ohai-report/ohai_report/sinks/jira.py
313
- tools/ohai-report/ohai_report/sinks/dts.py
314
- ```
315
-
316
- ## 6. 一阶段产物清单
317
-
318
- | 文件 | 说明 |
319
- | --- | --- |
320
- | `.opencode/commands/report-ai-issue.md` | OpenCode `/report-ai-issue` 入口 |
321
- | `.opencode/plugins/oh-ai-report.ts` | OpenCode plugin 薄适配器 |
322
- | `tools/ohai-report/ohai_report.py` | CLI 兼容入口 |
323
- | `tools/ohai-report/ohai_report/cli.py` | CLI 编排 |
324
- | `tools/ohai-report/ohai_report/schema.py` | schema 读取与校验 |
325
- | `tools/ohai-report/schemas/report_issue.schema.json` | 问题模板字段定义 |
326
- | `tools/ohai-report/ohai_report/metadata.py` | metadata 解析 |
327
- | `tools/ohai-report/ohai_report/payload.py` | payload 组装 |
328
- | `tools/ohai-report/ohai_report/sinks/local.py` | 本地 JSON sink |
329
- | `tools/ohai-report/ohai_report/sinks/gitcode.py` | GitCode sink 预留 |
330
- | `tools/ohai-report/examples/issue-input.json` | issue-file 示例 |
331
- | `tools/ohai-report/README.md` | CLI 使用说明 |
332
- | `docs/opencode-ai-issue-collection-best-practices.md` | OpenCode 方案文档 |
333
- | `docs/opencode-ai-issue-collection-architecture.md` | 正式架构设计文档 |
334
- | `.gitignore` | 忽略运行产物 |
335
-
336
- ## 7. 未完成事项
337
-
338
- 一阶段尚未实现:
339
-
340
- - GitCode/Jira/DTS 真实 Issue API。
341
- - Langfuse API 自动查询和 trace 校验。
342
- - 组织级用户/团队映射。
343
- - 单元测试套件。
344
- - OpenCode plugin 在真实 OpenCode 环境中的端到端验证。
345
- - schema 与 command prompt 自动同步生成。
346
-
347
- ## 8. 下一阶段建议
348
-
349
- ### 8.1 接入真实 Issue 后端
350
-
351
- 优先实现:
352
-
353
- ```text
354
- tools/ohai-report/ohai_report/sinks/gitcode.py
355
- ```
356
-
357
- 并增加 CLI 参数:
358
-
359
- ```text
360
- --sink local
361
- --sink gitcode
362
- ```
363
-
364
- ### 8.2 增加单元测试
365
-
366
- 建议覆盖:
367
-
368
- - schema 校验
369
- - metadata merge
370
- - payload 生成
371
- - local sink
372
- - issue_id 唯一性
373
- - 不上传日志的 privacy 约束
374
-
375
- ### 8.3 增强 Langfuse 集成
376
-
377
- 建议新增:
378
-
379
- ```text
380
- tools/ohai-report/ohai_report/observability/langfuse.py
381
- ```
382
-
383
- 用于:
384
-
385
- - 根据 session_id 生成 langfuse_url。
386
- - 校验 trace 是否存在。
387
- - 补齐 trace 元信息。
388
-
389
- ### 8.4 schema 驱动 prompt
390
-
391
- 后续可以由 `report_issue.schema.json` 自动生成 subagent prompt 中的字段说明,避免字段定义和 prompt 不一致。
392
-
393
- ## 9. 阶段结论
394
-
395
- 一阶段已经完成 OpenCode 问题采集的最小可用闭环:
396
-
397
- ```text
398
- /report-ai-issue
399
- -> subagent LLM 模板化
400
- -> ohai-report CLI 校验和组装 payload
401
- -> local sink 落盘
402
- -> Langfuse 索引关联
403
- ```
404
-
405
- 当前实现已经具备模块化边界,后续可以按 sink、schema、metadata、adapter 独立升级,不需要重做整体链路。
@@ -1,4 +0,0 @@
1
- {
2
- "title": "minmax 模型在代码生成过程中误删已有函数",
3
- "content": "## AI 使用问题反馈\n\n### 标题\nminmax 模型在代码生成过程中误删已有函数\n\n### 问题摘要\n用户在使用 minmax 模型进行代码相关任务时,观察到模型会错误地删除已有的函数,导致代码丢失。\n\n### 原始描述\nminmax模型会误删函数\n\n### 使用场景\n- 工具:`minmax`\n- 场景:`coding`\n- 任务类型:`unknown`\n- 工作流阶段:`unknown`\n- 受影响角色:`developer`\n\n### 问题分类\n- 主分类:`model-capability`\n- 次分类:`none`\n- 分类置信度:`medium`\n- 判断依据:用户描述表明模型在生成或修改代码时,未能正确识别和保留已有函数,属于代码理解和输出质量控制问题。\n\n### 标签\n- `tool:minmax`\n- `category:model-capability`\n- `scenario:coding`\n- `task:unknown`\n- `issue:wrong-file-edit`\n- `issue:bad-output-quality`\n- `capability:coding`\n- `capability:context-management`\n- `severity:high`\n- `frequency:unknown`\n\n### 影响\n该问题可能导致已有功能代码被意外删除,造成代码丢失、功能回退,增加代码审查和恢复成本,并降低开发者对 AI 代码生成能力的信任。\n\n### 可能原因\n- 模型可能未充分理解代码上下文,将已有函数误判为冗余代码。\n- 模型可能在尝试修复或重构时,未遵循最小化修改原则。\n- 提示词或上下文可能未明确强调保留现有函数。\n\n### 改进方向\n- 需要增强模型在代码编辑时的上下文理解能力,确保能够区分已有功能和待修改部分。\n- 在 AI 辅助代码生成流程中增加代码变更保护机制,避免未经确认的删除操作。\n- 优化提示词模板,明确约束模型保留现有函数。\n\n### 建议补充信息\n- 具体使用的 minmax 模型版本和调用方式。\n- 被误删的函数类型和业务重要性。\n- 发生误删时的具体任务场景(如重构、补全、Bug 修复等)。\n- 该问题出现的频率(偶发还是频繁)。\n- 是否有相关的代码 diff 或复现步骤。\n\n### 建议分派方向\n模型能力、产品体验、提示词/流程设计"
4
- }
@@ -1,40 +0,0 @@
1
- {
2
- "name": "opencode-ohai-report",
3
- "version": "0.1.0",
4
- "private": false,
5
- "type": "module",
6
- "description": "Install and run the oh-ai-report issue feedback plugin for OpenCode and Claude Code.",
7
- "bin": {
8
- "opencode-ohai-report": "bin/cli.js",
9
- "ohai-report": "bin/cli.js"
10
- },
11
- "files": [
12
- "bin",
13
- ".opencode",
14
- ".claude",
15
- "scripts/*.py",
16
- "scripts/*.ps1",
17
- "scripts/*.sh",
18
- "tools/ohai-report/ohai_report.py",
19
- "tools/ohai-report/ohai_report/**/*.py",
20
- "tools/ohai-report/schemas",
21
- "tools/ohai-report/examples",
22
- "tools/ohai-report/README.md",
23
- "README.md",
24
- "docs",
25
- "examples"
26
- ],
27
- "scripts": {
28
- "start": "node bin/cli.js",
29
- "pack:check": "npm pack --dry-run",
30
- "test": "cd tools/ohai-report && python -m unittest discover -s tests -p \"test_*.py\" -v"
31
- },
32
- "keywords": [
33
- "opencode",
34
- "claude-code",
35
- "issue-report",
36
- "ohai-report"
37
- ],
38
- "license": "UNLICENSED",
39
- "dependencies": {}
40
- }
@@ -1,257 +0,0 @@
1
- #!/usr/bin/env python3
2
- """Claude Code hook for /report-ai-issue.
3
-
4
- The hook reads the Claude hook event as bytes so Windows console code pages
5
- cannot mojibake UTF-8 prompts before JSON parsing.
6
- """
7
-
8
- from __future__ import annotations
9
-
10
- import json
11
- import os
12
- import pathlib
13
- import subprocess
14
- import sys
15
- from typing import Any
16
-
17
-
18
- COMMAND_NAME = "report-ai-issue"
19
- AUTO_REPORT_MODES = {"auto", "submit", "legacy"}
20
-
21
-
22
- def emit(value: dict[str, Any]) -> int:
23
- print(json.dumps(value, ensure_ascii=True, separators=(",", ":")))
24
- return 0
25
-
26
-
27
- def passthrough() -> int:
28
- return emit({})
29
-
30
-
31
- def hook_mode() -> str:
32
- return os.environ.get("OHAI_CLAUDE_REPORT_HOOK_MODE", "prompt").strip().lower()
33
-
34
-
35
- def safe_text(value: str) -> str:
36
- return value.encode("utf-8", errors="replace").decode("utf-8")
37
-
38
-
39
- def shorten(value: str, limit: int) -> str:
40
- text = " ".join(safe_text(value).strip().split())
41
- if len(text) <= limit:
42
- return text
43
- return text[: max(0, limit - 1)].rstrip() + "..."
44
-
45
-
46
- def default_issue_fields(description: str) -> dict[str, str]:
47
- text = safe_text(description).strip() or "No description provided."
48
- title = shorten(text.splitlines()[0], 80) or "AI issue report"
49
- summary = shorten(text, 800)
50
- return {
51
- "title": title,
52
- "category": "\u5176\u4ed6",
53
- "summary": summary,
54
- "expected_behavior": "The assistant should handle the request correctly.",
55
- "actual_behavior": shorten(text, 400),
56
- "severity": "P2",
57
- "user_description": shorten(text, 2000),
58
- "agent": "claude",
59
- "level": "P2",
60
- }
61
-
62
-
63
- def resolve_cli(cwd: pathlib.Path) -> pathlib.Path | None:
64
- configured = os.environ.get("OHAI_REPORT_CLI", "").strip()
65
- if configured:
66
- path = pathlib.Path(configured)
67
- if not path.is_absolute():
68
- path = cwd / path
69
- return path if path.exists() else None
70
-
71
- fallback = cwd / "tools" / "ohai-report" / "ohai_report.py"
72
- return fallback if fallback.exists() else None
73
-
74
-
75
- def cli_repo_root(cli: pathlib.Path) -> pathlib.Path | None:
76
- marker = pathlib.Path("tools") / "ohai-report" / "ohai_report.py"
77
- try:
78
- if pathlib.Path(*cli.parts[-3:]) == marker:
79
- return cli.parents[2]
80
- except IndexError:
81
- return None
82
- return None
83
-
84
-
85
- def resolve_report_cwd(cwd: pathlib.Path, cli: pathlib.Path) -> pathlib.Path:
86
- configured = os.environ.get("OHAI_REPORT_CWD", "").strip()
87
- if configured:
88
- return pathlib.Path(configured).expanduser().resolve()
89
-
90
- try:
91
- if cwd == pathlib.Path.home().resolve():
92
- root = cli_repo_root(cli)
93
- if root is not None:
94
- return root
95
- except OSError:
96
- pass
97
- return cwd
98
-
99
-
100
- def resolve_sink() -> str:
101
- sink = os.environ.get("OHAI_REPORT_SINK", "webhook").strip().lower()
102
- if sink not in {"local", "webhook", "gitcode"}:
103
- return "webhook"
104
- return sink
105
-
106
-
107
- def extract_json_line(stdout: str) -> dict[str, Any]:
108
- for line in reversed(stdout.splitlines()):
109
- line = line.strip()
110
- if not line:
111
- continue
112
- try:
113
- value = json.loads(line)
114
- except json.JSONDecodeError:
115
- continue
116
- if isinstance(value, dict):
117
- return value
118
- return {}
119
-
120
-
121
- def read_stdin_text() -> str:
122
- stream = getattr(sys.stdin, "buffer", sys.stdin)
123
- data = stream.read()
124
- if isinstance(data, str):
125
- return data
126
- if not data.strip():
127
- return ""
128
-
129
- encodings = ["utf-8-sig"]
130
- if sys.stdin.encoding:
131
- encodings.append(sys.stdin.encoding)
132
- encodings.append("gb18030")
133
-
134
- for encoding in encodings:
135
- try:
136
- return data.decode(encoding)
137
- except UnicodeDecodeError:
138
- continue
139
- return data.decode("utf-8", errors="replace")
140
-
141
-
142
- def emit_prompt_context(cwd: pathlib.Path, cli: pathlib.Path | None, sink: str, session_id: str) -> int:
143
- cli_text = str(cli) if cli is not None else "not found; resolve OHAI_REPORT_CLI or repo-local tools/ohai-report/ohai_report.py"
144
- context = "\n".join(
145
- [
146
- "ohai-report hook note:",
147
- "- Do not let the hook submit this report automatically.",
148
- "- First read and follow the expanded /report-ai-issue command instructions.",
149
- "- Use the current LLM to classify the user's description and fill the required issue fields.",
150
- "- Then run the ohai-report CLI yourself and parse its final JSON output before replying.",
151
- f"- Resolved CLI hint: {cli_text}",
152
- f"- Suggested working directory: {cwd}",
153
- f"- Suggested sink: {sink}",
154
- f"- Current Claude session_id: {session_id or '(not provided by hook payload)'}",
155
- "- When running the ohai-report CLI, pass this session through the process environment as OHAI_SESSION_ID.",
156
- ]
157
- )
158
- return emit(
159
- {
160
- "hookSpecificOutput": {
161
- "hookEventName": "UserPromptExpansion",
162
- "additionalContext": context,
163
- }
164
- }
165
- )
166
-
167
-
168
- def configure_stdio() -> None:
169
- try:
170
- sys.stdout.reconfigure(encoding="utf-8", errors="replace")
171
- sys.stderr.reconfigure(encoding="utf-8", errors="replace")
172
- except AttributeError:
173
- pass
174
-
175
-
176
- def main() -> int:
177
- configure_stdio()
178
- raw = read_stdin_text()
179
- if not raw.strip():
180
- return passthrough()
181
-
182
- try:
183
- payload = json.loads(raw)
184
- except json.JSONDecodeError:
185
- return passthrough()
186
-
187
- if not isinstance(payload, dict):
188
- return passthrough()
189
-
190
- if payload.get("command_name") != COMMAND_NAME:
191
- return passthrough()
192
-
193
- description = str(payload.get("command_args") or "")
194
- session_id = str(payload.get("session_id") or "")
195
- cwd = pathlib.Path(str(payload.get("cwd") or os.getcwd())).resolve()
196
- cli = resolve_cli(cwd)
197
- sink = resolve_sink()
198
-
199
- if hook_mode() not in AUTO_REPORT_MODES:
200
- report_cwd = resolve_report_cwd(cwd, cli) if cli is not None else cwd
201
- return emit_prompt_context(report_cwd, cli, sink, session_id)
202
-
203
- if cli is None:
204
- return emit(
205
- {
206
- "decision": "block",
207
- "reason": "ohai-report CLI not found. Please set OHAI_REPORT_CLI.",
208
- }
209
- )
210
-
211
- report_cwd = resolve_report_cwd(cwd, cli)
212
- issue = default_issue_fields(description)
213
- cmd = [
214
- sys.executable,
215
- str(cli),
216
- "create",
217
- "--source",
218
- "claude",
219
- "--issue-json",
220
- json.dumps(issue, ensure_ascii=True),
221
- "--metadata",
222
- "auto",
223
- "--sink",
224
- sink,
225
- "--cwd",
226
- str(report_cwd),
227
- "--quiet",
228
- "--json",
229
- ]
230
- env = os.environ.copy()
231
- env["PYTHONUTF8"] = "1"
232
- env["PYTHONIOENCODING"] = "utf-8"
233
- if session_id:
234
- env["OHAI_SESSION_ID"] = session_id
235
-
236
- proc = subprocess.run(cmd, capture_output=True, text=True, encoding="utf-8", env=env)
237
- if proc.returncode != 0:
238
- reason = (proc.stderr or proc.stdout or "unknown error").strip()
239
- return emit({"decision": "block", "reason": f"ohai-report failed: {reason}"})
240
-
241
- result = extract_json_line(proc.stdout)
242
- issue_id = result.get("issue_id") or "unknown"
243
- url = result.get("gitcode_url") or result.get("webhook_url") or ""
244
- suffix = f" {url}" if url else ""
245
- return emit(
246
- {
247
- "decision": "block",
248
- "reason": (
249
- f"Issue reported: {issue_id}{suffix}. "
250
- "The ohai-report CLI has handled this request."
251
- ),
252
- }
253
- )
254
-
255
-
256
- if __name__ == "__main__":
257
- raise SystemExit(main())