aigroup-workflow 1.2.7 → 1.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 (39) hide show
  1. package/.claude/agents/init-architect.md +13 -26
  2. package/.claude/commands/init-project.md +6 -21
  3. package/CLAUDE.md +10 -2
  4. package/cli/utils/scaffold.mjs +1 -1
  5. package/docs/ARCHITECTURE.md +33 -2
  6. package/docs/PROJECT_CONTEXT.md +25 -0
  7. package/docs/QUALITY_SCORE.md +2 -1
  8. package/docs/README.md +5 -0
  9. package/docs/red-flags.md +1 -1
  10. package/docs/templates/memory/activeContext.template.md +30 -0
  11. package/docs/templates/memory/projectContext.template.md +36 -0
  12. package/docs/templates/memory/systemPatterns.template.md +26 -0
  13. package/docs/workflow-pipeline.md +9 -0
  14. package/package.json +1 -2
  15. package/scripts/harness/lint-delegation.sh +7 -3
  16. package/scripts/harness/lint-docs.sh +5 -1
  17. package/scripts/harness/lint-process.sh +5 -1
  18. package/scripts/harness/lint-structure.sh +6 -2
  19. package/scripts/harness/lint-workflow-artifacts.sh +11 -6
  20. package/scripts/harness/log-event.sh +120 -0
  21. package/scripts/harness/logs-query.sh +189 -0
  22. package/scripts/harness/tests/test-log-event.sh +86 -0
  23. package/scripts/harness/tests/test-logs-query.sh +77 -0
  24. package/scripts/harness/workflow-state.sh +42 -2
  25. package/.dev-agents/shared/.workflow-state +0 -5
  26. package/.dev-agents/shared/designs/.gitkeep +0 -0
  27. package/.dev-agents/shared/reviews/.gitkeep +0 -0
  28. package/.dev-agents/shared/tasks/.gitkeep +0 -0
  29. /package/{.dev-agents/shared → docs}/templates/README.md +0 -0
  30. /package/{.dev-agents/shared → docs}/templates/ai-project-final.md +0 -0
  31. /package/{.dev-agents/shared → docs}/templates/ai-project.md +0 -0
  32. /package/{.dev-agents/shared → docs}/templates/api.md +0 -0
  33. /package/{.dev-agents/shared → docs}/templates/bug.md +0 -0
  34. /package/{.dev-agents/shared → docs}/templates/code-review.md +0 -0
  35. /package/{.dev-agents/shared → docs}/templates/generic.md +0 -0
  36. /package/{.dev-agents/shared → docs}/templates/implementation-plan.md +0 -0
  37. /package/{.dev-agents/shared → docs}/templates/meeting.md +0 -0
  38. /package/{.dev-agents/shared → docs}/templates/prd.md +0 -0
  39. /package/{.dev-agents/shared → docs}/templates/ui.md +0 -0
@@ -20,30 +20,15 @@ color: orange
20
20
 
21
21
  ## 一.5、aiGroup 框架保护(铁律)
22
22
 
23
- 当调用方传入 `preserve_framework=true` 时,写入**根** `CLAUDE.md` 必须遵守以下规则:
23
+ 当调用方传入 `preserve_framework=true` 时:
24
24
 
25
- 1. **先读后写**:写入前读取现有根 `CLAUDE.md`,定位框架特征标记(任一命中即判定为框架内容):
26
- - `## 角色:麦克斯 (Max)`
27
- - `## 全局铁律`
28
- - `## 行为门控`
29
- - `## 团队派遣`
30
- - `<!-- aiGroup 框架边界` 注释行
31
- 2. **保留区间**:从文件开头到 `<!-- aiGroup 框架边界 ... -->` 注释行(含)的全部内容**原样保留**,禁止覆盖、删除、重排、润色。若文件不含该注释但命中其他框架特征标记,则保留从开头到最后一个 `##` 框架章节末尾的全部内容。
32
- 3. **追加区间**:仅在框架区块末尾之后追加(或更新)以下结构:
25
+ 1. **根 `CLAUDE.md` 全文受保护** — 不得覆盖、删除、追加、润色任何内容。根 CLAUDE.md 是 aiGroup 框架的导航入口,保持 ≤100 行的精简形态。
26
+ 2. **项目上下文独立存放** 本代理生成的"项目愿景、架构总览、Mermaid 结构图、模块索引、编码规范、变更记录"等内容**全部写入 `docs/PROJECT_CONTEXT.md`**,不再追加到根 CLAUDE.md。
27
+ 3. **根 CLAUDE.md 已内置指引** — "知识库地图"表格中已有指向 `docs/PROJECT_CONTEXT.md` 的条目,无需再动。
28
+ 4. **模块级不受限** — 模块级 `<module>/CLAUDE.md` 不适用本保护规则,正常按第三节结构生成/更新。
29
+ 5. **全新项目(`preserve_framework=false`)** — 按常规方式生成完整根 `CLAUDE.md`(此时不存在框架保护需求)。
33
30
 
34
- ```markdown
35
- ---
36
-
37
- # 项目上下文(由 /init-project 生成)
38
-
39
- [项目愿景、架构总览、Mermaid 结构图、模块索引、编码规范、变更记录等]
40
- ```
41
-
42
- 4. **增量更新**:若 `# 项目上下文(由 /init-project 生成)` 区块已存在,仅更新该区块内容,禁止触碰上方框架区块。
43
- 5. **全新项目**:当 `preserve_framework=false` 或未检测到框架特征标记时,按常规方式生成完整根 `CLAUDE.md`,无需保护。
44
- 6. **模块级不受限**:模块级 `<module>/CLAUDE.md` 不适用本保护规则,正常按第三节结构生成/更新。
45
-
46
- > 违反本节任何一条 = 破坏 aiGroup 框架 = 必须回滚。
31
+ > 违反第 1 条 = 破坏 aiGroup 框架 = 必须回滚。
47
32
 
48
33
  ## 二、分阶段策略(自动选择强度)
49
34
 
@@ -71,19 +56,19 @@ color: orange
71
56
 
72
57
  ## 三、产物与增量更新
73
58
 
74
- 1. **写入根级 `CLAUDE.md`**
59
+ 1. **写入 `docs/PROJECT_CONTEXT.md`(preserve_framework=true 时)**
75
60
  - 如果已存在,则在顶部插入/更新 `变更记录 (Changelog)`。
76
- - 根级结构(精简而全局):
61
+ - 结构(精简而全局):
77
62
  - 项目愿景
78
63
  - 架构总览
79
- - **✨ 新增:模块结构图(Mermaid)**
64
+ - **✨ 模块结构图(Mermaid)**
80
65
  - 在"模块索引"表格**上方**,根据识别出的模块路径,生成一个 Mermaid `graph TD` 树形图。
81
66
  - 每个节点应可点击,并链接到对应模块的 `CLAUDE.md` 文件。
82
67
  - 示例语法:
83
68
 
84
69
  ```mermaid
85
70
  graph TD
86
- A["(根) 我的项目"] --> B["packages"];
71
+ A["(根) [项目名]"] --> B["packages"];
87
72
  B --> C["auth"];
88
73
  B --> D["ui-library"];
89
74
  A --> E["services"];
@@ -101,6 +86,8 @@ color: orange
101
86
  - AI 使用指引
102
87
  - 变更记录 (Changelog)
103
88
 
89
+ > **preserve_framework=false 场景**:若调用方明确不保护框架(新项目),则按原方式写入根 `CLAUDE.md`。
90
+
104
91
  2. **写入模块级 `CLAUDE.md`**
105
92
  - 放在每个模块目录下,结构建议:
106
93
  - **✨ 新增:相对路径面包屑**
@@ -18,38 +18,22 @@ argument-hint: <项目摘要或名称>
18
18
 
19
19
  ## ⚠️ aiGroup 框架保护(铁律)
20
20
 
21
- 根 `CLAUDE.md` 中存在 aiGroup 框架内容时,**必须保留**,不可覆盖或删除。
21
+ 根 `CLAUDE.md` aiGroup 框架的导航入口(≤ 100 行),**绝对禁止**追加项目上下文内容。
22
22
 
23
23
  ### 检测方法
24
24
 
25
- 在写入根 `CLAUDE.md` 前,先读取现有内容,检查是否包含以下特征标记(任一命中即为框架内容):
25
+ 读取现有根 `CLAUDE.md`,检查以下任一特征标记:
26
26
 
27
27
  - `## 角色:麦克斯 (Max)`
28
28
  - `## 全局铁律`
29
29
  - `## 行为门控`
30
30
  - `## 团队派遣`
31
- - `<!-- aiGroup 框架边界` 注释行
32
31
 
33
32
  ### 保护策略
34
33
 
35
- **如果检测到框架内容**:
34
+ **检测到框架内容**:设置 `preserve_framework=true` 调用 `init-architect`。该代理会把项目上下文写入 `docs/PROJECT_CONTEXT.md`,**不碰**根 `CLAUDE.md`。
36
35
 
37
- 1. **完整保留**从文件开头到 `<!-- aiGroup 框架边界 ... -->` 注释行(含)的全部内容。若文件不含该注释但命中其他框架标记,则保留到最后一个框架 `##` 章节末尾
38
- 2. 在框架内容**末尾之后**追加分隔线和项目上下文:
39
-
40
- ```markdown
41
- ---
42
-
43
- # 项目上下文(由 /init-project 生成)
44
-
45
- [项目愿景、技术栈、Mermaid 架构图、模块索引、编码规范等]
46
- ```
47
-
48
- 3. 如果已有 `# 项目上下文` 区块,只更新该区块内容,不触碰框架区块
49
-
50
- **如果未检测到框架内容**(全新项目、未安装 aiGroup):
51
-
52
- - 正常生成完整的 `CLAUDE.md`,无需保护
36
+ **未检测到框架内容**(全新项目):设置 `preserve_framework=false`,按常规方式在根 `CLAUDE.md` 生成完整项目上下文。
53
37
 
54
38
  ### 模块级 CLAUDE.md
55
39
 
@@ -86,7 +70,8 @@ argument-hint: <项目摘要或名称>
86
70
  ## 输出要求
87
71
 
88
72
  - 在主对话中打印"初始化结果摘要",包含:
89
- - 根级 `CLAUDE.md` 是否创建/更新、主要栏目概览。
73
+ - 根级 CLAUDE.md 保护状态(preserve_framework 值;是否已检测并跳过写入)。
74
+ - `docs/PROJECT_CONTEXT.md` 是否创建/更新、主要栏目概览。
90
75
  - ✨ **是否检测到 aiGroup 框架并已保护**(明确说明)。
91
76
  - 识别的模块数量及其路径列表。
92
77
  - 每个模块 `CLAUDE.md` 的生成/更新情况。
package/CLAUDE.md CHANGED
@@ -2,6 +2,15 @@
2
2
 
3
3
  你是麦克斯 (Max),项目经理兼用户个人助理。不直接写代码、做设计或做测试,价值在于需求分析、任务拆解、驱动工作流、进度跟踪、风险预警、熵管理,以及通过 Agent 工具派遣子代理整合成果。
4
4
 
5
+ ## 会话启动协议
6
+
7
+ Max 每次会话启动时必须:
8
+ 1. 读取 `.dev-agents/shared/memory/activeContext.md`(若存在)了解上次工作状态
9
+ 2. 读取 `.dev-agents/shared/.workflow-state`(若存在)确认活跃工作流
10
+ 3. 怀疑反复错误时运行 `bash scripts/harness/logs-query.sh --hotspots`
11
+
12
+ 首次使用:若 `.dev-agents/shared/memory/` 下无文件,从 `docs/templates/memory/` 复制模板(去掉 `.template` 后缀)。
13
+
5
14
  ## 全局铁律
6
15
 
7
16
  ```
@@ -41,6 +50,7 @@
41
50
  | 质量评分与健康度追踪 | `docs/QUALITY_SCORE.md` |
42
51
  | 技术债追踪 | `docs/tech-debt-tracker.md` |
43
52
  | Harness 转向循环 | `docs/steering-loop.md` |
53
+ | 项目实例上下文(/init-project 生成) | `docs/PROJECT_CONTEXT.md` |
44
54
 
45
55
  ## 工作流技能
46
56
 
@@ -82,5 +92,3 @@
82
92
  ## Harness 自检
83
93
 
84
94
  开发完成后运行 `scripts/harness/run-all.sh`,按 [FAIL] 提示的 [FIX] 修复直至全部通过。
85
-
86
- <!-- aiGroup 框架边界(init-architect 保留区至此,以下由 /init-project 生成) -->
@@ -28,7 +28,7 @@ export const BASE_DIRS = [
28
28
  '.dev-agents/shared/tasks',
29
29
  '.dev-agents/shared/designs',
30
30
  '.dev-agents/shared/reviews',
31
- '.dev-agents/shared/templates',
31
+ 'docs/templates',
32
32
  'skills/max/workflow/brainstorming',
33
33
  'skills/max/workflow/requirement-validation',
34
34
  'skills/max/workflow/solution-design',
@@ -12,6 +12,7 @@ ai-agent-workflowGroup/
12
12
  ├── docs/ # 知识库:详细规范与标准
13
13
  │ ├── README.md # 知识库索引
14
14
  │ ├── ARCHITECTURE.md # 本文件
15
+ │ ├── templates/ # 静态文档模板(prd/implementation-plan/code-review 等)
15
16
  │ ├── workflow-pipeline.md # 工作流管道详细规则
16
17
  │ ├── dispatch-rules.md # 派遣规则与上下文传递
17
18
  │ ├── coding-standards.md # 编码与 Git 规范
@@ -19,11 +20,12 @@ ai-agent-workflowGroup/
19
20
  │ ├── QUALITY_SCORE.md # 质量评分追踪
20
21
  │ ├── tech-debt-tracker.md # 技术债追踪
21
22
  │ └── steering-loop.md # 转向循环机制
22
- ├── .dev-agents/shared/ # Agent 间协作产物工作区
23
+ ├── .dev-agents/shared/ # Agent 间协作产物工作区(运行时动态)
23
24
  │ ├── tasks/ # 实现计划
24
25
  │ ├── designs/ # 设计方案与设计稿
25
26
  │ ├── reviews/ # 审查报告
26
- └── templates/ # 文档模板
27
+ ├── memory/ # 长期记忆(projectContext/activeContext/systemPatterns)
28
+ │ └── logs/ # 运行时事件日志(JSONL 按日滚动)
27
29
  ├── skills/ # 技能库
28
30
  │ ├── max/ # PM 技能
29
31
  │ │ ├── workflow/ # 工作流技能(8 阶段管道 + 横切技能)
@@ -84,3 +86,32 @@ Agent 的自由度在约束边界内最大化。
84
86
  **重要区别**:
85
87
  - **Claude Code CLI**:Hooks 自动执行,如同 git hook,Agent 无法跳过
86
88
  - **Cursor / 其他 IDE**:不支持 hooks.json,传感器需要 Agent 根据 CLAUDE.md 指令主动运行
89
+
90
+ ## 六层架构实现映射
91
+
92
+ 本项目完整实现 Harness Engineering 定义的六层架构:
93
+
94
+ | 层 | 职责 | 本项目实现 |
95
+ |----|------|-----------|
96
+ | ① 上下文边界层 | 角色定义、信息裁剪、分层管理 | `CLAUDE.md`(< 100 行入口)+ `.claude/agents/*.md`(角色) + `docs/`(按需加载) + `skills/`(渐进式披露) |
97
+ | ② 工具系统层 | 连接模型与现实 | `Agent(subagent_type)` 派遣 + `.dev-agents/shared/` 文件邮箱 + CLI 工具(Gemini/Qwen/Codex) |
98
+ | ③ 执行编排层 | 任务分解为可执行步骤 | `scripts/harness/workflow-state.sh` 8 阶段状态机 + `docs/workflow-pipeline.md` + `skills/max/workflow/*` |
99
+ | ④ 记忆与状态层 | 解决 Agent 失忆 | `.dev-agents/shared/.workflow-state`(会话状态) + `.dev-agents/shared/memory/`(长期记忆 3 件套) |
100
+ | ⑤ 评估与观测层 | 建立质量反馈 | `scripts/harness/lint-*.sh`(5 个静态传感器) + Kyle 两阶段审查 + `.dev-agents/shared/logs/`(JSONL 事件日志) + `scripts/harness/logs-query.sh`(查询工具) |
101
+ | ⑥ 约束校验与恢复层 | 保障鲁棒性 | `.claude/hooks.json`(运行时约束) + `docs/red-flags.md`(10 条危险信号) + `docs/steering-loop.md`(转向循环将重复错误编码为规则) |
102
+
103
+ ### 记忆系统详解(L4)
104
+
105
+ - `memory/projectContext.md` — 项目级记忆:产品愿景、架构决策、技术栈
106
+ - `memory/activeContext.md` — 会话级记忆:当前焦点、上次做到哪、下一步(不入 git)
107
+ - `memory/systemPatterns.md` — 模式级记忆:代码模式、重构手法、团队约定
108
+
109
+ Max 每次会话启动必读 `activeContext.md`(见 CLAUDE.md 会话启动协议)。
110
+
111
+ ### 观测系统详解(L5)
112
+
113
+ - JSONL 按日滚动:`.dev-agents/shared/logs/events-YYYY-MM-DD.jsonl`
114
+ - 10 种 event_type:`workflow_start/reset/exempt/complete`、`stage_enter/exit`、`dispatch`、`loop_iter`、`lint_fail`、`red_flag`
115
+ - 写入工具:`scripts/harness/log-event.sh`(fail-silent,不阻塞主流程)
116
+ - 查询工具:`scripts/harness/logs-query.sh`(`--stats` / `--hotspots` / `--export`)
117
+ - Schema 详见 `.dev-agents/shared/logs/README.md`
@@ -0,0 +1,25 @@
1
+ # 项目上下文(占位)
2
+
3
+ > 此文件由 `/init-project` 首次运行时生成完整内容。
4
+
5
+ ## 将自动生成的内容
6
+
7
+ - 项目愿景与核心价值
8
+ - 架构总览
9
+ - Mermaid 结构图
10
+ - 模块索引(表格)
11
+ - 运行与开发
12
+ - 测试策略
13
+ - 编码规范
14
+ - AI 使用指引
15
+ - 变更记录 (Changelog)
16
+
17
+ ## 使用
18
+
19
+ 消费方克隆本框架后运行:
20
+
21
+ ```bash
22
+ /init-project <你的项目名或摘要>
23
+ ```
24
+
25
+ 即可自动填充上述内容。aiGroup 框架本身不包含任何项目实例数据。
@@ -14,7 +14,7 @@
14
14
 
15
15
  ## 当前质量评分
16
16
 
17
- > 最后更新:2026-04-11
17
+ > 最后更新:2026-04-23
18
18
 
19
19
  | 维度 | 当前评分 | 说明 | 改进方向 |
20
20
  |------|---------|------|---------|
@@ -34,6 +34,7 @@
34
34
  | 日期 | 综合评分 | 关键变更 |
35
35
  |------|---------|---------|
36
36
  | 2026-04-11 | C+ (66) | 初始化 Harness Engineering 改造:添加 docs/ 知识库、harness 传感器、熵管理 |
37
+ | 2026-04-23 | C+ (66) | L4 记忆层 + L5 观测层补强(commit 09f36f4...7441373) |
37
38
 
38
39
  ## 更新规则
39
40
 
package/docs/README.md CHANGED
@@ -16,6 +16,11 @@ Agent 应按需检索本目录下的文档,而非依赖 CLAUDE.md 中的简短
16
16
  | [QUALITY_SCORE.md](QUALITY_SCORE.md) | 各模块质量评分与健康度追踪 | 每次审查后 |
17
17
  | [tech-debt-tracker.md](tech-debt-tracker.md) | 已知技术债与偿还计划 | 持续更新 |
18
18
  | [steering-loop.md](steering-loop.md) | Harness 转向循环:问题→编码为规则→自动执行 | 发现重复问题时 |
19
+ | [PROJECT_CONTEXT.md](PROJECT_CONTEXT.md) | 项目实例上下文(/init-project 生成) | 项目初始化时 |
20
+
21
+ ## 子目录
22
+
23
+ - `templates/` — 静态文档模板(PRD / 实现计划 / 代码审查等)
19
24
 
20
25
  ## 使用原则
21
26
 
package/docs/red-flags.md CHANGED
@@ -13,7 +13,7 @@ Max 在工作流中必须持续监测以下危险信号。检测到任何一个
13
13
  | 5 | 审查发现问题但直接跳过 | 要求 Jarvis 修复后重新审查 | 🔴 高 |
14
14
  | 6 | 有人想跳过工作流环节 | 拒绝,除非符合简单任务豁免 | 🔴 高 |
15
15
  | 7 | 使用"应该没问题""看起来对了" | 要求提供验证命令和输出证据 | 🟡 中 |
16
- | 8 | 同一类错误反复出现 | 触发 [转向循环](steering-loop.md),将修复编码为规则 | 🟡 中 |
16
+ | 8 | 同一类错误反复出现 | 运行 `bash scripts/harness/logs-query.sh --hotspots --days 30` 查看高频失败项,再触发 [转向循环](steering-loop.md) | 🟡 中 |
17
17
  | 9 | 文档与代码不一致 | 触发 [熵管理](../skills/max/workflow/entropy-management/SKILL.md),修复漂移 | 🟡 中 |
18
18
  | 10 | Harness 传感器长期无告警 | 审查传感器有效性,可能检测不足 | 🟡 中 |
19
19
 
@@ -0,0 +1,30 @@
1
+ ---
2
+ last_updated: YYYY-MM-DD
3
+ updated_by: <stage-name>
4
+ workflow_id: <workflow-name 或 none>
5
+ version: 1
6
+ ---
7
+
8
+ # 当前工作上下文
9
+
10
+ > Max 每次 advance 后更新此文件。下次会话启动时优先读取。
11
+
12
+ ## 当前焦点
13
+
14
+ <当前工作流 ID + 阶段 + 正在做什么>
15
+
16
+ ## 上次做到哪
17
+
18
+ <具体产出位置和关键决策>
19
+
20
+ ## 下一步动作
21
+
22
+ <下次启动第一件事>
23
+
24
+ ## 开放的阻塞问题
25
+
26
+ <需要用户决定的问题,如无则"无">
27
+
28
+ ## 近期学到的
29
+
30
+ <非系统性的临时笔记,稳定后转入 systemPatterns>
@@ -0,0 +1,36 @@
1
+ ---
2
+ last_updated: YYYY-MM-DD
3
+ updated_by: <stage-name>
4
+ version: 1
5
+ ---
6
+
7
+ # 项目上下文
8
+
9
+ > Max 在 design 阶段完成后更新此文件,记录架构决策。
10
+
11
+ ## 产品愿景
12
+
13
+ <一句话描述本项目的使命与核心价值>
14
+
15
+ ## 核心架构决策
16
+
17
+ | 决策 | 时间 | 上下文 | 放弃的替代方案 |
18
+ |------|------|--------|---------------|
19
+ | <决策名> | YYYY-MM-DD | <为什么做这个决策> | <放弃了什么> |
20
+
21
+ ## 技术栈
22
+
23
+ - **运行时**:<语言/框架>
24
+ - **语言**:<主要编程语言>
25
+ - **依赖**:<关键第三方库>
26
+ - **版本控制**:<git 策略>
27
+
28
+ ## 关键约束
29
+
30
+ - <硬约束 1:性能/合规/兼容性>
31
+ - <硬约束 2>
32
+
33
+ ## 团队惯例
34
+
35
+ - <非文档化的团队约定 1>
36
+ - <团队约定 2>
@@ -0,0 +1,26 @@
1
+ ---
2
+ last_updated: YYYY-MM-DD
3
+ updated_by: <stage-name>
4
+ version: 1
5
+ ---
6
+
7
+ # 系统模式
8
+
9
+ > Kyle 审查通过后,Max 将反复出现的好/坏模式沉淀至此。
10
+
11
+ ## 代码模式
12
+
13
+ | 模式名 | 类型 | 场景 | 示例位置 | 首次发现 |
14
+ |--------|------|------|---------|---------|
15
+ | <模式名> | 推荐/反模式 | <触发场景> | `<文件路径>` | YYYY-MM-DD |
16
+
17
+ ## 常用重构手法
18
+
19
+ | 场景 | 重构方法 |
20
+ |------|---------|
21
+ | <场景描述> | <方法名 + 要点> |
22
+
23
+ ## 已沉淀的团队约定
24
+
25
+ - **<约定名>**:<约定内容>
26
+ - *为什么*:<背后的原因或引发该约定的事件>
@@ -9,6 +9,15 @@
9
9
 
10
10
  状态机命令:`bash scripts/harness/workflow-state.sh {init|advance|gate|status|reset|exempt}`
11
11
 
12
+ ## advance 命令副作用
13
+
14
+ 自 2026-04-23 起,`workflow-state.sh {init|advance|reset|exempt}` 会:
15
+ 1. 写入事件到 `.dev-agents/shared/logs/events-YYYY-MM-DD.jsonl`(见 `docs/ARCHITECTURE.md` 六层架构映射)
16
+ 2. 在特定阶段切换时输出 `[REMIND]` 文案提示更新记忆文件:
17
+ - → design:提示更新 `projectContext.md` 记录架构决策
18
+ - → documentation:提示更新 `systemPatterns.md` 沉淀代码模式
19
+ - 所有 advance:提示更新 `activeContext.md` 反映当前状态
20
+
12
21
  ## 各环节详细规则
13
22
 
14
23
  ### 1. 需求收集 (brainstorming)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aigroup-workflow",
3
- "version": "1.2.7",
3
+ "version": "1.3.0",
4
4
  "description": "AI 团队协作框架 — 通过角色派遣、工作流管道和 Harness 传感器驱动 AI 协作开发",
5
5
  "type": "module",
6
6
  "bin": {
@@ -13,7 +13,6 @@
13
13
  "docs/",
14
14
  "skills/",
15
15
  "scripts/",
16
- ".dev-agents/",
17
16
  ".claude/commands/",
18
17
  ".claude/agents/",
19
18
  ".claude/hooks.json",
@@ -19,7 +19,11 @@ ERRORS=0
19
19
  WARNINGS=0
20
20
 
21
21
  pass() { echo -e " [PASS] $1"; }
22
- fail() { echo -e " [FAIL] $1"; ERRORS=$((ERRORS + 1)); }
22
+ fail() {
23
+ echo -e " [FAIL] $1"
24
+ ERRORS=$((ERRORS + 1))
25
+ bash "$(dirname "${BASH_SOURCE[0]}")/log-event.sh" lint_fail --actor harness --payload "lint=delegation" 2>/dev/null || true
26
+ }
23
27
  warn() { echo -e " [WARN] $1"; WARNINGS=$((WARNINGS + 1)); }
24
28
  fix() { echo -e " [FIX] $1"; }
25
29
 
@@ -31,7 +35,7 @@ echo ""
31
35
  # ─────────────────────────────────────────
32
36
  # 扫描目标:CLAUDE.md + docs/ + skills/
33
37
  # 排除目标:.claude/agents/(规范定义自身)
34
- # .dev-agents/shared/templates/(模板允许示例)
38
+ # docs/templates/(模板允许示例)
35
39
  # .git/、node_modules/、.idea/
36
40
  # ─────────────────────────────────────────
37
41
  SCAN_INCLUDE=("CLAUDE.md" "docs" "skills")
@@ -42,7 +46,7 @@ scan_files() {
42
46
  | grep -v "^\.git/" \
43
47
  | grep -v "node_modules/" \
44
48
  | grep -v "\.idea/" \
45
- | grep -v "/shared/templates/" \
49
+ | grep -v "docs/templates/" \
46
50
  || true
47
51
  }
48
52
 
@@ -12,7 +12,11 @@ ERRORS=0
12
12
  WARNINGS=0
13
13
 
14
14
  pass() { echo -e " [PASS] $1"; }
15
- fail() { echo -e " [FAIL] $1"; ERRORS=$((ERRORS + 1)); }
15
+ fail() {
16
+ echo -e " [FAIL] $1"
17
+ ERRORS=$((ERRORS + 1))
18
+ bash "$(dirname "${BASH_SOURCE[0]}")/log-event.sh" lint_fail --actor harness --payload "lint=docs" 2>/dev/null || true
19
+ }
16
20
  warn() { echo -e " [WARN] $1"; WARNINGS=$((WARNINGS + 1)); }
17
21
  fix() { echo -e " [FIX] $1"; }
18
22
 
@@ -16,7 +16,11 @@ ERRORS=0
16
16
  WARNINGS=0
17
17
 
18
18
  pass() { echo -e " [PASS] $1"; }
19
- fail() { echo -e " [FAIL] $1"; ERRORS=$((ERRORS + 1)); }
19
+ fail() {
20
+ echo -e " [FAIL] $1"
21
+ ERRORS=$((ERRORS + 1))
22
+ bash "$(dirname "${BASH_SOURCE[0]}")/log-event.sh" lint_fail --actor harness --payload "lint=process" 2>/dev/null || true
23
+ }
20
24
  warn() { echo -e " [WARN] $1"; WARNINGS=$((WARNINGS + 1)); }
21
25
  fix() { echo -e " [FIX] $1"; }
22
26
 
@@ -12,7 +12,11 @@ ERRORS=0
12
12
  WARNINGS=0
13
13
 
14
14
  pass() { echo -e " [PASS] $1"; }
15
- fail() { echo -e " [FAIL] $1"; ERRORS=$((ERRORS + 1)); }
15
+ fail() {
16
+ echo -e " [FAIL] $1"
17
+ ERRORS=$((ERRORS + 1))
18
+ bash "$(dirname "${BASH_SOURCE[0]}")/log-event.sh" lint_fail --actor harness --payload "lint=structure" 2>/dev/null || true
19
+ }
16
20
  warn() { echo -e " [WARN] $1"; WARNINGS=$((WARNINGS + 1)); }
17
21
  fix() { echo -e " [FIX] $1"; }
18
22
 
@@ -51,7 +55,7 @@ done
51
55
  echo ""
52
56
  echo "▸ 协作产物目录检查"
53
57
 
54
- for dir in ".dev-agents/shared/tasks" ".dev-agents/shared/designs" ".dev-agents/shared/reviews" ".dev-agents/shared/templates"; do
58
+ for dir in ".dev-agents/shared/tasks" ".dev-agents/shared/designs" ".dev-agents/shared/reviews" ".dev-agents/shared/memory" ".dev-agents/shared/logs" "docs/templates"; do
55
59
  if [ -d "$dir" ]; then
56
60
  pass "$dir/ 存在"
57
61
  else
@@ -12,7 +12,11 @@ ERRORS=0
12
12
  WARNINGS=0
13
13
 
14
14
  pass() { echo -e " [PASS] $1"; }
15
- fail() { echo -e " [FAIL] $1"; ERRORS=$((ERRORS + 1)); }
15
+ fail() {
16
+ echo -e " [FAIL] $1"
17
+ ERRORS=$((ERRORS + 1))
18
+ bash "$(dirname "${BASH_SOURCE[0]}")/log-event.sh" lint_fail --actor harness --payload "lint=workflow-artifacts" 2>/dev/null || true
19
+ }
16
20
  warn() { echo -e " [WARN] $1"; WARNINGS=$((WARNINGS + 1)); }
17
21
  fix() { echo -e " [FIX] $1"; }
18
22
 
@@ -38,7 +42,7 @@ if [ -d "$SHARED_DIR/tasks" ]; then
38
42
  pass "$filename 有结构化标题"
39
43
  else
40
44
  warn "$filename 缺少结构化标题"
41
- fix "参考 .dev-agents/shared/templates/implementation-plan.md 模板重构 $plan"
45
+ fix "参考 docs/templates/implementation-plan.md 模板重构 $plan"
42
46
  fi
43
47
 
44
48
  if grep -q -iE "(验收|acceptance|完成标准|done)" "$plan" 2>/dev/null; then
@@ -102,10 +106,10 @@ if [ -d "$SHARED_DIR/reviews" ]; then
102
106
  pass "$filename 包含两阶段审查"
103
107
  elif [ "$HAS_STAGE1" -gt 0 ]; then
104
108
  warn "$filename 只有 Stage 1,缺少 Stage 2"
105
- fix "补充 Stage 2 代码质量审查,参考 .dev-agents/shared/templates/code-review.md"
109
+ fix "补充 Stage 2 代码质量审查,参考 docs/templates/code-review.md"
106
110
  else
107
111
  warn "$filename 审查结构不完整"
108
- fix "参考 .dev-agents/shared/templates/code-review.md 重构审查报告"
112
+ fix "参考 docs/templates/code-review.md 重构审查报告"
109
113
  fi
110
114
  done
111
115
  else
@@ -120,13 +124,14 @@ fi
120
124
  echo ""
121
125
  echo "▸ 模板完整性检查"
122
126
 
127
+ TEMPLATES_DIR="docs/templates"
123
128
  REQUIRED_TEMPLATES=("prd.md" "implementation-plan.md" "code-review.md")
124
129
  for tmpl in "${REQUIRED_TEMPLATES[@]}"; do
125
- if [ -f "$SHARED_DIR/templates/$tmpl" ]; then
130
+ if [ -f "$TEMPLATES_DIR/$tmpl" ]; then
126
131
  pass "模板 $tmpl 存在"
127
132
  else
128
133
  fail "模板 $tmpl 缺失"
129
- fix "创建 $SHARED_DIR/templates/$tmpl"
134
+ fix "创建 $TEMPLATES_DIR/$tmpl"
130
135
  fi
131
136
  done
132
137
 
@@ -0,0 +1,120 @@
1
+ #!/bin/bash
2
+ # ================================================================
3
+ # Harness 事件写入工具
4
+ #
5
+ # 职责:将 harness 事件以 JSONL 格式追加到日志文件
6
+ # 调用者:workflow-state.sh / lint-*.sh / Max(主动调用)
7
+ # 设计原则:fail-silent — 写入失败不得中断调用方主流程
8
+ #
9
+ # 用法:
10
+ # log-event.sh <event_type> [--stage S] [--actor A] [--duration-ms D] [--payload k=v,k=v,...]
11
+ #
12
+ # 示例:
13
+ # log-event.sh stage_enter --actor harness
14
+ # log-event.sh dispatch --actor max --payload "target=jarvis,task_id=T5"
15
+ # log-event.sh lint_fail --payload "rule=template-missing,file=prd.md"
16
+ #
17
+ # 输出:.dev-agents/shared/logs/events-YYYY-MM-DD.jsonl(追加一行)
18
+ # ================================================================
19
+
20
+ EVENT_TYPE="${1:-}"
21
+
22
+ if [ -z "$EVENT_TYPE" ]; then
23
+ echo "[log-event] ERROR: 必须提供 event_type" >&2
24
+ exit 1
25
+ fi
26
+
27
+ shift
28
+
29
+ # ── 解析参数 ──
30
+ STAGE_ARG=""
31
+ ACTOR_ARG=""
32
+ DURATION_ARG=""
33
+ PAYLOAD_ARG=""
34
+
35
+ while [ $# -gt 0 ]; do
36
+ case "$1" in
37
+ --stage) STAGE_ARG="$2"; shift 2 ;;
38
+ --actor) ACTOR_ARG="$2"; shift 2 ;;
39
+ --duration-ms) DURATION_ARG="$2"; shift 2 ;;
40
+ --payload) PAYLOAD_ARG="$2"; shift 2 ;;
41
+ *) shift ;;
42
+ esac
43
+ done
44
+
45
+ # ── 读取 state 文件作为默认值 ──
46
+ STATE_FILE=".dev-agents/shared/.workflow-state"
47
+ DEFAULT_WORKFLOW_ID="idle"
48
+ DEFAULT_STAGE="idle"
49
+
50
+ if [ -f "$STATE_FILE" ]; then
51
+ WORKFLOW_ID_FROM_STATE=$(grep '^task=' "$STATE_FILE" 2>/dev/null | cut -d'=' -f2-)
52
+ STAGE_FROM_STATE=$(grep '^stage=' "$STATE_FILE" 2>/dev/null | cut -d'=' -f2-)
53
+ [ -n "$WORKFLOW_ID_FROM_STATE" ] && DEFAULT_WORKFLOW_ID="$WORKFLOW_ID_FROM_STATE"
54
+ [ -n "$STAGE_FROM_STATE" ] && DEFAULT_STAGE="$STAGE_FROM_STATE"
55
+ fi
56
+
57
+ STAGE="${STAGE_ARG:-$DEFAULT_STAGE}"
58
+ ACTOR="${ACTOR_ARG:-harness}"
59
+ WORKFLOW_ID="$DEFAULT_WORKFLOW_ID"
60
+
61
+ # ── 时间戳(ISO-8601 带时区)──
62
+ TS=$(date +%Y-%m-%dT%H:%M:%S%z)
63
+
64
+ # ── JSON 字符串转义函数 ──
65
+ json_escape() {
66
+ # 转义顺序:\ 先转,再转 "
67
+ local s="$1"
68
+ s="${s//\\/\\\\}"
69
+ s="${s//\"/\\\"}"
70
+ # 换行/制表符转义
71
+ s="${s//$'\n'/\\n}"
72
+ s="${s//$'\t'/\\t}"
73
+ echo "$s"
74
+ }
75
+
76
+ # ── 拼接 payload ──
77
+ PAYLOAD_JSON="{}"
78
+ if [ -n "$PAYLOAD_ARG" ]; then
79
+ PAYLOAD_JSON="{"
80
+ FIRST=1
81
+ # payload 用 "," 分隔 k=v 对;v 内含 "," 需用户调用方避免(本轮不支持复杂 payload)
82
+ IFS=',' read -ra PAIRS <<< "$PAYLOAD_ARG"
83
+ for pair in "${PAIRS[@]}"; do
84
+ K="${pair%%=*}"
85
+ V="${pair#*=}"
86
+ K_ESC=$(json_escape "$K")
87
+ V_ESC=$(json_escape "$V")
88
+ if [ "$FIRST" -eq 1 ]; then
89
+ PAYLOAD_JSON="${PAYLOAD_JSON}\"${K_ESC}\":\"${V_ESC}\""
90
+ FIRST=0
91
+ else
92
+ PAYLOAD_JSON="${PAYLOAD_JSON},\"${K_ESC}\":\"${V_ESC}\""
93
+ fi
94
+ done
95
+ PAYLOAD_JSON="${PAYLOAD_JSON}}"
96
+ fi
97
+
98
+ # ── duration_ms 字段(可选)──
99
+ DURATION_FIELD=""
100
+ if [ -n "$DURATION_ARG" ]; then
101
+ # 仅接受数字
102
+ if echo "$DURATION_ARG" | grep -qE '^[0-9]+$'; then
103
+ DURATION_FIELD=",\"duration_ms\":${DURATION_ARG}"
104
+ fi
105
+ fi
106
+
107
+ # ── 拼接最终 JSON(字段顺序固定)──
108
+ JSON="{\"ts\":\"${TS}\",\"workflow_id\":\"$(json_escape "$WORKFLOW_ID")\",\"stage\":\"$(json_escape "$STAGE")\",\"event_type\":\"$(json_escape "$EVENT_TYPE")\",\"actor\":\"$(json_escape "$ACTOR")\"${DURATION_FIELD},\"payload\":${PAYLOAD_JSON}}"
109
+
110
+ # ── 写入(fail-silent)──
111
+ LOG_DIR=".dev-agents/shared/logs"
112
+ LOG_FILE="${LOG_DIR}/events-$(date +%Y-%m-%d).jsonl"
113
+
114
+ {
115
+ mkdir -p "$LOG_DIR" 2>/dev/null
116
+ echo "$JSON" >> "$LOG_FILE" 2>/dev/null
117
+ } || true
118
+
119
+ # 永远返回 0,即使写入失败
120
+ exit 0
@@ -0,0 +1,189 @@
1
+ #!/bin/bash
2
+ # ================================================================
3
+ # Harness 日志查询工具
4
+ #
5
+ # 用法:
6
+ # logs-query.sh --stats [workflow_id]
7
+ # logs-query.sh --hotspots [--days N]
8
+ # logs-query.sh --export [--days N] [--out PATH]
9
+ #
10
+ # 实现:纯 bash + grep + sed,无 jq 依赖
11
+ # ================================================================
12
+
13
+ LOG_DIR=".dev-agents/shared/logs"
14
+
15
+ # ── 辅助:返回近 N 天内的日志文件路径(一行一个)──
16
+ # 基于文件名 events-YYYY-MM-DD.jsonl 解析日期并与 (today - N 天) 比较。
17
+ # date -d 不可用时(非 GNU date 环境)兜底为返回全部文件。
18
+ get_log_files_within_days() {
19
+ local days="$1"
20
+ local cutoff_ts
21
+ cutoff_ts=$(date -d "$days days ago" +%s 2>/dev/null || echo "0")
22
+ if [ "$cutoff_ts" -eq 0 ]; then
23
+ ls -1 "$LOG_DIR"/events-*.jsonl 2>/dev/null
24
+ return
25
+ fi
26
+ for f in "$LOG_DIR"/events-*.jsonl; do
27
+ [ -f "$f" ] || continue
28
+ local fname
29
+ fname=$(basename "$f")
30
+ local fdate
31
+ fdate=$(echo "$fname" | sed -n 's/^events-\([0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}\)\.jsonl$/\1/p')
32
+ [ -z "$fdate" ] && continue
33
+ local fts
34
+ fts=$(date -d "$fdate" +%s 2>/dev/null || echo "0")
35
+ if [ "$fts" -ge "$cutoff_ts" ]; then
36
+ echo "$f"
37
+ fi
38
+ done
39
+ }
40
+
41
+ cmd_stats() {
42
+ local wf_id="$1"
43
+ local state_file=".dev-agents/shared/.workflow-state"
44
+ if [ -z "$wf_id" ] && [ -f "$state_file" ]; then
45
+ wf_id=$(grep '^task=' "$state_file" 2>/dev/null | cut -d'=' -f2-)
46
+ fi
47
+ wf_id="${wf_id:-unknown}"
48
+
49
+ if [ ! -d "$LOG_DIR" ]; then
50
+ echo "[INFO] 无匹配事件(logs 目录不存在)"
51
+ return 0
52
+ fi
53
+
54
+ local files
55
+ files=$(ls -1 "$LOG_DIR"/events-*.jsonl 2>/dev/null)
56
+ if [ -z "$files" ]; then
57
+ echo "[INFO] 无匹配事件"
58
+ return 0
59
+ fi
60
+
61
+ echo "======================================"
62
+ echo " 工作流统计: $wf_id"
63
+ echo "======================================"
64
+ echo ""
65
+ echo "▸ 阶段耗时(ms)"
66
+
67
+ # 提取 stage_exit 事件,按 stage 聚合 duration_ms
68
+ grep -h "\"workflow_id\":\"${wf_id}\"" $files 2>/dev/null \
69
+ | grep "\"event_type\":\"stage_exit\"" \
70
+ | while IFS= read -r line; do
71
+ stage=$(echo "$line" | sed -n 's/.*"stage":"\([^"]*\)".*/\1/p')
72
+ dur=$(echo "$line" | sed -n 's/.*"duration_ms":\([0-9]*\).*/\1/p')
73
+ [ -n "$stage" ] && [ -n "$dur" ] && echo "$stage $dur"
74
+ done \
75
+ | sort \
76
+ | awk '
77
+ {
78
+ sum[$1] += $2
79
+ cnt[$1] += 1
80
+ }
81
+ END {
82
+ for (s in sum) printf " %-16s %10d ms (× %d)\n", s, sum[s], cnt[s]
83
+ }
84
+ '
85
+
86
+ echo ""
87
+ echo "▸ 循环次数"
88
+ local loop_count
89
+ loop_count=$(grep -h "\"workflow_id\":\"${wf_id}\"" $files 2>/dev/null \
90
+ | grep -c "\"event_type\":\"loop_iter\"")
91
+ echo " Kyle 审查-修复循环: $loop_count 次"
92
+ }
93
+
94
+ cmd_hotspots() {
95
+ local days="${1:-30}"
96
+ if [ ! -d "$LOG_DIR" ]; then
97
+ echo "[INFO] 无匹配事件"
98
+ return 0
99
+ fi
100
+
101
+ local files
102
+ files=$(get_log_files_within_days "$days")
103
+ if [ -z "$files" ]; then
104
+ echo "[INFO] 无匹配事件"
105
+ return 0
106
+ fi
107
+
108
+ echo "======================================"
109
+ echo " 失败热点(近 $days 天 top 10)"
110
+ echo "======================================"
111
+ echo ""
112
+ echo "▸ lint_fail 规则 top 10"
113
+
114
+ # 字段契约:lint_fail 事件 payload 使用 lint=<name>(任务 8.1 修正后);
115
+ # red_flag 事件 payload 使用 flag_id=<id>。两种事件按各自字段聚合。
116
+ grep -h -E '"event_type":"(lint_fail|red_flag)"' $files 2>/dev/null \
117
+ | while IFS= read -r line; do
118
+ lint=$(echo "$line" | sed -n 's/.*"lint":"\([^"]*\)".*/\1/p')
119
+ flag=$(echo "$line" | sed -n 's/.*"flag_id":"\([^"]*\)".*/\1/p')
120
+ type=$(echo "$line" | sed -n 's/.*"event_type":"\([^"]*\)".*/\1/p')
121
+ key="${lint}${flag}"
122
+ [ -n "$key" ] && echo "${type}:${key}"
123
+ done \
124
+ | sort | uniq -c | sort -rn | head -10 \
125
+ | awk '{printf " %-40s %d 次\n", $2, $1}'
126
+ }
127
+
128
+ cmd_export() {
129
+ local days=30
130
+ local out="events-export.csv"
131
+ while [ $# -gt 0 ]; do
132
+ case "$1" in
133
+ --days) days="$2"; shift 2 ;;
134
+ --out) out="$2"; shift 2 ;;
135
+ *) shift ;;
136
+ esac
137
+ done
138
+
139
+ if [ ! -d "$LOG_DIR" ]; then
140
+ echo "ts,workflow_id,stage,event_type,actor,duration_ms" > "$out"
141
+ echo "[INFO] 无事件数据,已生成空 CSV: $out"
142
+ return 0
143
+ fi
144
+
145
+ echo "ts,workflow_id,stage,event_type,actor,duration_ms" > "$out"
146
+
147
+ # --days N 真实过滤:只导出近 N 天内的日志文件
148
+ while IFS= read -r f; do
149
+ [ -f "$f" ] || continue
150
+ while IFS= read -r line; do
151
+ ts=$(echo "$line" | sed -n 's/.*"ts":"\([^"]*\)".*/\1/p')
152
+ wf=$(echo "$line" | sed -n 's/.*"workflow_id":"\([^"]*\)".*/\1/p')
153
+ stg=$(echo "$line" | sed -n 's/.*"stage":"\([^"]*\)".*/\1/p')
154
+ et=$(echo "$line" | sed -n 's/.*"event_type":"\([^"]*\)".*/\1/p')
155
+ ac=$(echo "$line" | sed -n 's/.*"actor":"\([^"]*\)".*/\1/p')
156
+ dur=$(echo "$line" | sed -n 's/.*"duration_ms":\([0-9]*\).*/\1/p')
157
+ echo "$ts,$wf,$stg,$et,$ac,$dur" >> "$out"
158
+ done < "$f"
159
+ done <<< "$(get_log_files_within_days "$days")"
160
+
161
+ echo "[OK] 已导出到 $out($(wc -l < "$out") 行)"
162
+ }
163
+
164
+ # ── 入口 ──
165
+ case "${1:-}" in
166
+ --stats)
167
+ shift
168
+ cmd_stats "$@"
169
+ ;;
170
+ --hotspots)
171
+ shift
172
+ DAYS=30
173
+ while [ $# -gt 0 ]; do
174
+ case "$1" in
175
+ --days) DAYS="$2"; shift 2 ;;
176
+ *) shift ;;
177
+ esac
178
+ done
179
+ cmd_hotspots "$DAYS"
180
+ ;;
181
+ --export)
182
+ shift
183
+ cmd_export "$@"
184
+ ;;
185
+ *)
186
+ echo "用法: logs-query.sh {--stats [workflow_id]|--hotspots [--days N]|--export [--days N] [--out PATH]}" >&2
187
+ exit 1
188
+ ;;
189
+ esac
@@ -0,0 +1,86 @@
1
+ #!/bin/bash
2
+ # 单元测试:log-event.sh
3
+ # 运行:bash scripts/harness/tests/test-log-event.sh
4
+
5
+ set -e
6
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
7
+ ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)"
8
+ LOG_EVENT="$ROOT/scripts/harness/log-event.sh"
9
+
10
+ # 用临时目录做隔离测试
11
+ TMP=$(mktemp -d)
12
+ trap "rm -rf $TMP" EXIT
13
+
14
+ cd "$TMP"
15
+ mkdir -p .dev-agents/shared
16
+ cat > .dev-agents/shared/.workflow-state <<EOF
17
+ stage=testing
18
+ task=test-task
19
+ started=2026-04-23 00:00:00
20
+ exempt=false
21
+ updated=2026-04-23 00:00:00
22
+ EOF
23
+
24
+ PASS=0
25
+ FAIL=0
26
+
27
+ assert() {
28
+ local msg="$1"
29
+ local cond="$2"
30
+ if eval "$cond"; then
31
+ echo " [PASS] $msg"
32
+ PASS=$((PASS + 1))
33
+ else
34
+ echo " [FAIL] $msg"
35
+ echo " 条件: $cond"
36
+ FAIL=$((FAIL + 1))
37
+ fi
38
+ }
39
+
40
+ echo "▸ 测试 1:基础事件写入"
41
+ bash "$LOG_EVENT" stage_enter --actor harness
42
+ LOG_FILE=".dev-agents/shared/logs/events-$(date +%Y-%m-%d).jsonl"
43
+ assert "日志文件已创建" "[ -f '$LOG_FILE' ]"
44
+ assert "事件类型正确" "grep -q '\"event_type\":\"stage_enter\"' '$LOG_FILE'"
45
+ assert "workflow_id 从 state 读取" "grep -q '\"workflow_id\":\"test-task\"' '$LOG_FILE'"
46
+ assert "stage 从 state 读取" "grep -q '\"stage\":\"testing\"' '$LOG_FILE'"
47
+ assert "actor 正确" "grep -q '\"actor\":\"harness\"' '$LOG_FILE'"
48
+
49
+ echo ""
50
+ echo "▸ 测试 2:payload 解析"
51
+ bash "$LOG_EVENT" dispatch --actor max --payload "target=jarvis,task_id=T5"
52
+ assert "payload 包含 target" "grep -q '\"target\":\"jarvis\"' '$LOG_FILE'"
53
+ assert "payload 包含 task_id" "grep -q '\"task_id\":\"T5\"' '$LOG_FILE'"
54
+
55
+ echo ""
56
+ echo "▸ 测试 3:duration_ms 字段"
57
+ bash "$LOG_EVENT" stage_exit --duration-ms 12345
58
+ assert "duration_ms 存在且为数字" "grep -q '\"duration_ms\":12345' '$LOG_FILE'"
59
+
60
+ echo ""
61
+ echo "▸ 测试 4:state 文件缺失时 fallback 为 idle"
62
+ rm .dev-agents/shared/.workflow-state
63
+ bash "$LOG_EVENT" workflow_reset
64
+ assert "无 state 时 workflow_id 为 idle" "grep -q '\"workflow_id\":\"idle\"' '$LOG_FILE'"
65
+ assert "无 state 时 stage 为 idle" "tail -1 '$LOG_FILE' | grep -q '\"stage\":\"idle\"'"
66
+
67
+ echo ""
68
+ echo "▸ 测试 5:特殊字符转义"
69
+ cat > .dev-agents/shared/.workflow-state <<EOF
70
+ stage=design
71
+ task=test-task
72
+ started=2026-04-23 00:00:00
73
+ exempt=false
74
+ EOF
75
+ bash "$LOG_EVENT" red_flag --actor max --payload 'msg=has "quotes" and \ backslash'
76
+ assert "双引号被转义" "tail -1 '$LOG_FILE' | grep -q '\\\\\"quotes\\\\\"'"
77
+
78
+ echo ""
79
+ echo "▸ 测试 6:ts 字段 ISO-8601 格式"
80
+ assert "ts 字段格式合法" "tail -1 '$LOG_FILE' | grep -qE '\"ts\":\"[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}[+-][0-9]{4}\"'"
81
+
82
+ echo ""
83
+ echo "======================================"
84
+ echo " 结果: $PASS 通过 / $FAIL 失败"
85
+ echo "======================================"
86
+ [ "$FAIL" -eq 0 ]
@@ -0,0 +1,77 @@
1
+ #!/bin/bash
2
+ # 单元测试:logs-query.sh
3
+ set -e
4
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5
+ ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)"
6
+ QUERY="$ROOT/scripts/harness/logs-query.sh"
7
+
8
+ TMP=$(mktemp -d)
9
+ trap "rm -rf $TMP" EXIT
10
+ cd "$TMP"
11
+
12
+ # 构造 mock 日志
13
+ mkdir -p .dev-agents/shared/logs
14
+ TODAY=$(date +%Y-%m-%d)
15
+ LOG="$TMP/.dev-agents/shared/logs/events-$TODAY.jsonl"
16
+
17
+ cat > "$LOG" <<EOF
18
+ {"ts":"2026-04-23T10:00:00+0800","workflow_id":"wf1","stage":"brainstorming","event_type":"stage_enter","actor":"harness","payload":{}}
19
+ {"ts":"2026-04-23T10:15:00+0800","workflow_id":"wf1","stage":"brainstorming","event_type":"stage_exit","actor":"harness","duration_ms":900000,"payload":{"next_stage":"design"}}
20
+ {"ts":"2026-04-23T10:15:01+0800","workflow_id":"wf1","stage":"design","event_type":"stage_enter","actor":"harness","payload":{}}
21
+ {"ts":"2026-04-23T10:45:00+0800","workflow_id":"wf1","stage":"design","event_type":"stage_exit","actor":"harness","duration_ms":1800000,"payload":{"next_stage":"planning"}}
22
+ {"ts":"2026-04-23T11:00:00+0800","workflow_id":"wf1","stage":"testing","event_type":"lint_fail","actor":"harness","payload":{"lint":"structure"}}
23
+ {"ts":"2026-04-23T11:05:00+0800","workflow_id":"wf1","stage":"testing","event_type":"lint_fail","actor":"harness","payload":{"lint":"structure"}}
24
+ {"ts":"2026-04-23T11:10:00+0800","workflow_id":"wf1","stage":"testing","event_type":"lint_fail","actor":"harness","payload":{"lint":"docs"}}
25
+ {"ts":"2026-04-23T11:15:00+0800","workflow_id":"wf1","stage":"testing","event_type":"red_flag","actor":"max","payload":{"flag_id":"3"}}
26
+ EOF
27
+
28
+ PASS=0; FAIL=0
29
+ assert() {
30
+ if eval "$2"; then echo " [PASS] $1"; PASS=$((PASS+1))
31
+ else echo " [FAIL] $1 — cond: $2"; FAIL=$((FAIL+1)); fi
32
+ }
33
+
34
+ echo "▸ 测试 1:--stats 输出包含阶段耗时"
35
+ OUT=$(bash "$QUERY" --stats wf1)
36
+ assert "含 brainstorming 900000 ms" "echo '$OUT' | grep -q 'brainstorming.*900000'"
37
+ assert "含 design 1800000 ms" "echo '$OUT' | grep -q 'design.*1800000'"
38
+
39
+ echo ""
40
+ echo "▸ 测试 2:--hotspots 输出按次数降序"
41
+ OUT=$(bash "$QUERY" --hotspots --days 30)
42
+ assert "structure 出现 2 次排第一" "echo '$OUT' | grep -E 'structure.*2|2.*structure'"
43
+ assert "docs 出现 1 次" "echo '$OUT' | grep -E 'docs.*1|1.*docs'"
44
+ assert "red_flag 按 flag_id 聚合" "echo '$OUT' | grep -E 'red_flag.*3|3.*red_flag'"
45
+
46
+ echo ""
47
+ echo "▸ 测试 2b:--days 过滤真实生效"
48
+ # 创建一个旧日期的日志文件,验证 --days N 是否真实按日期过滤
49
+ OLD_LOG="$TMP/.dev-agents/shared/logs/events-2025-01-01.jsonl"
50
+ echo '{"ts":"2025-01-01T00:00:00+0800","workflow_id":"wfold","stage":"testing","event_type":"lint_fail","actor":"harness","payload":{"lint":"ancient"}}' > "$OLD_LOG"
51
+
52
+ # --days 3650 应包含 ancient(近 10 年窗口覆盖 2025-01-01)
53
+ OUT=$(bash "$QUERY" --hotspots --days 3650)
54
+ assert "3650 天窗口包含 ancient" "echo '$OUT' | grep -q ancient"
55
+
56
+ # --days 1 应排除 ancient(2025-01-01 远早于今天前 1 天)
57
+ OUT=$(bash "$QUERY" --hotspots --days 1)
58
+ assert "--days 1 排除 ancient" "! echo '$OUT' | grep -q ancient"
59
+
60
+ echo ""
61
+ echo "▸ 测试 3:--export 生成 CSV"
62
+ bash "$QUERY" --export --out "$TMP/out.csv"
63
+ assert "CSV 文件生成" "[ -f '$TMP/out.csv' ]"
64
+ assert "CSV 含表头" "head -1 '$TMP/out.csv' | grep -q 'ts,workflow_id,stage,event_type'"
65
+ assert "CSV 行数 >= 8" "[ \$(wc -l < '$TMP/out.csv') -ge 8 ]"
66
+
67
+ echo ""
68
+ echo "▸ 测试 4:空日志不报错"
69
+ # 清空所有 mock 日志文件(包括测试 2b 创建的历史日志),确保目录为空
70
+ rm -f "$TMP/.dev-agents/shared/logs"/events-*.jsonl
71
+ OUT=$(bash "$QUERY" --stats 2>&1)
72
+ assert "空日志返回码 0" "bash '$QUERY' --stats >/dev/null 2>&1"
73
+ assert "含 '无匹配事件' 提示" "echo '$OUT' | grep -qE '无匹配|无事件|no events'"
74
+
75
+ echo ""
76
+ echo "结果: $PASS 通过 / $FAIL 失败"
77
+ [ "$FAIL" -eq 0 ]
@@ -83,6 +83,32 @@ updated=$(date '+%Y-%m-%d %H:%M:%S')
83
83
  EOF
84
84
  }
85
85
 
86
+ # 计算自上次 updated 以来的毫秒数(用于 stage_exit duration)
87
+ compute_stage_duration_ms() {
88
+ local last_updated
89
+ last_updated=$(get_field "updated")
90
+ if [ -z "$last_updated" ]; then
91
+ echo "0"
92
+ return
93
+ fi
94
+ local last_ts
95
+ last_ts=$(date -d "$last_updated" +%s 2>/dev/null || echo "0")
96
+ local now_ts
97
+ now_ts=$(date +%s)
98
+ if [ "$last_ts" -eq 0 ]; then
99
+ echo "0"
100
+ else
101
+ echo $(( (now_ts - last_ts) * 1000 ))
102
+ fi
103
+ }
104
+
105
+ # 调用 log-event.sh(失败不报错)
106
+ log_event() {
107
+ local script_dir
108
+ script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
109
+ bash "$script_dir/log-event.sh" "$@" 2>/dev/null || true
110
+ }
111
+
86
112
  stage_index() {
87
113
  local target="$1"
88
114
  for i in "${!STAGES[@]}"; do
@@ -146,9 +172,12 @@ cmd_init() {
146
172
  fi
147
173
 
148
174
  write_state "brainstorming" "$task_name" "$(date '+%Y-%m-%d %H:%M:%S')"
175
+ log_event workflow_start --stage brainstorming --payload "task_name=$task_name"
176
+ log_event stage_enter --stage brainstorming
149
177
  echo "[OK] 工作流已启动: $task_name"
150
178
  echo "[STATE] 当前阶段: brainstorming(需求收集)"
151
179
  echo "[NEXT] 收集和分析功能需求,记录到 .dev-agents/shared/designs/ 后运行 advance"
180
+ echo "[REMIND] 若本工作流涉及架构决策,完成后请更新 .dev-agents/shared/memory/activeContext.md"
152
181
  }
153
182
 
154
183
  cmd_advance() {
@@ -235,12 +264,18 @@ cmd_advance() {
235
264
  local next
236
265
  next=$(next_stage "$current_stage")
237
266
 
267
+ local stage_duration_ms
268
+ stage_duration_ms=$(compute_stage_duration_ms)
269
+ log_event stage_exit --stage "$current_stage" --duration-ms "$stage_duration_ms" --payload "next_stage=$next"
270
+
238
271
  if [ "$next" = "idle" ]; then
239
272
  write_state "idle" "" "" "false"
273
+ log_event workflow_complete --stage idle --payload "prev_stage=$current_stage"
240
274
  echo "[OK] 工作流已完成: $task"
241
275
  echo "[STATE] 状态已重置为 idle"
242
276
  else
243
277
  write_state "$next" "$task" "$(get_field 'started')"
278
+ log_event stage_enter --stage "$next" --payload "prev_stage=$current_stage"
244
279
  echo "[OK] 阶段推进: $current_stage($(stage_label "$current_stage")) → $next($(stage_label "$next"))"
245
280
  echo "[STATE] 当前阶段: $next($(stage_label "$next"))"
246
281
 
@@ -250,16 +285,19 @@ cmd_advance() {
250
285
  design)
251
286
  echo "[NEXT] 设计技术方案和架构,产出方案决策文档后 advance" ;;
252
287
  planning)
253
- echo "[NEXT] 将需求拆解为可执行任务,保存到 .dev-agents/shared/tasks/" ;;
288
+ echo "[NEXT] 将需求拆解为可执行任务,保存到 .dev-agents/shared/tasks/"
289
+ echo "[REMIND] 方案设计已完成,请更新 .dev-agents/shared/memory/projectContext.md 记录架构决策" ;;
254
290
  development)
255
291
  echo "[NEXT] 派遣 Jarvis 子代理按任务执行开发" ;;
256
292
  testing)
257
293
  echo "[NEXT] 派遣 Kyle 编写和执行测试用例,进行两阶段审查" ;;
258
294
  documentation)
259
- echo "[NEXT] 更新相关文档(API 文档、README、ARCHITECTURE 等)" ;;
295
+ echo "[NEXT] 更新相关文档(API 文档、README、ARCHITECTURE 等)"
296
+ echo "[REMIND] Kyle 已通过审查,若发现值得沉淀的代码模式,请更新 .dev-agents/shared/memory/systemPatterns.md" ;;
260
297
  finishing)
261
298
  echo "[NEXT] 执行分支收尾流程(集成/PR/归档)" ;;
262
299
  esac
300
+ echo "[REMIND] 阶段已推进,请同步更新 .dev-agents/shared/memory/activeContext.md 反映当前状态"
263
301
  fi
264
302
  }
265
303
 
@@ -315,6 +353,7 @@ cmd_reset() {
315
353
  return 0
316
354
  fi
317
355
 
356
+ log_event workflow_reset --stage "$current_stage" --payload "prev_task=${task:-unnamed}"
318
357
  write_state "idle" "" "" "false"
319
358
  echo "[OK] 工作流已重置(原任务: ${task:-未命名},原阶段: $current_stage)"
320
359
  }
@@ -328,6 +367,7 @@ cmd_exempt() {
328
367
  fi
329
368
 
330
369
  write_state "idle" "exempt: $reason" "$(date '+%Y-%m-%d %H:%M:%S')" "true"
370
+ log_event workflow_exempt --stage idle --payload "reason=$reason"
331
371
  echo "[OK] 已标记为简单任务豁免: $reason"
332
372
  echo "[INFO] 豁免任务完成后请运行 workflow-state.sh reset 恢复正常模式"
333
373
  }
@@ -1,5 +0,0 @@
1
- stage=idle
2
- task=exempt: 合并 Karpathy 四条编码守则到 jarvis.md(单文件配置调整,方案已对齐)
3
- started=2026-04-22 09:08:06
4
- exempt=true
5
- updated=2026-04-22 09:08:06
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes