principles-disciple 1.80.0 → 1.82.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.
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/src/commands/strategy.ts +0 -18
- package/src/i18n/commands.ts +0 -12
- package/src/index.ts +47 -22
- package/src/service/correction-observer-service.ts +200 -0
- package/src/service/evolution-worker.ts +2 -123
- package/templates/langs/en/core/BOOTSTRAP.md +4 -18
- package/templates/langs/en/skills/bootstrap-tools/SKILL.md +1 -1
- package/templates/langs/en/skills/init-strategy/SKILL.md +1 -1
- package/templates/langs/en/skills/pd-mentor/SKILL.md +8 -23
- package/templates/langs/zh/core/BOOTSTRAP.md +2 -15
- package/templates/langs/zh/skills/bootstrap-tools/SKILL.md +1 -1
- package/templates/langs/zh/skills/init-strategy/SKILL.md +1 -1
- package/templates/langs/zh/skills/pd-mentor/SKILL.md +7 -22
- package/tests/commands/strategy.test.ts +3 -18
- package/tests/service/correction-observer-service.test.ts +331 -0
- package/tests/service/evolution-worker.correction-observer.test.ts +41 -164
- package/templates/langs/en/skills/ai-sprint-orchestration/EXAMPLES.md +0 -63
- package/templates/langs/en/skills/ai-sprint-orchestration/REFERENCE.md +0 -136
- package/templates/langs/en/skills/ai-sprint-orchestration/SKILL.md +0 -67
- package/templates/langs/en/skills/ai-sprint-orchestration/references/agent-registry.json +0 -143
- package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/bugfix-complex-template.json +0 -107
- package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/feature-complex-template.json +0 -107
- package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal-verify.json +0 -95
- package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal.json +0 -98
- package/templates/langs/en/skills/ai-sprint-orchestration/references/workflow-v1-acceptance-checklist.md +0 -58
- package/templates/langs/en/skills/ai-sprint-orchestration/references/workflow-v1.4-work-unit-handoff.md +0 -190
- package/templates/langs/en/skills/ai-sprint-orchestration/scripts/lib/archive.mjs +0 -310
- package/templates/langs/en/skills/ai-sprint-orchestration/scripts/lib/contract-enforcement.mjs +0 -683
- package/templates/langs/en/skills/ai-sprint-orchestration/scripts/lib/decision.mjs +0 -604
- package/templates/langs/en/skills/ai-sprint-orchestration/scripts/lib/state-store.mjs +0 -32
- package/templates/langs/en/skills/ai-sprint-orchestration/scripts/lib/task-specs.mjs +0 -707
- package/templates/langs/en/skills/ai-sprint-orchestration/scripts/run.mjs +0 -3455
- package/templates/langs/en/skills/evolve-system/SKILL.md +0 -46
- package/templates/langs/en/skills/manage-okr/SKILL.md +0 -96
- package/templates/langs/en/skills/pd-daily/SKILL.md +0 -199
- package/templates/langs/en/skills/pd-grooming/SKILL.md +0 -46
- package/templates/langs/zh/skills/ai-sprint-orchestration/EXAMPLES.md +0 -63
- package/templates/langs/zh/skills/ai-sprint-orchestration/REFERENCE.md +0 -136
- package/templates/langs/zh/skills/ai-sprint-orchestration/SKILL.md +0 -67
- package/templates/langs/zh/skills/ai-sprint-orchestration/references/agent-registry.json +0 -143
- package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/bugfix-complex-template.json +0 -107
- package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/feature-complex-template.json +0 -107
- package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/nocturnal-trinity-quality-enhancement.json +0 -111
- package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal-verify.json +0 -95
- package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal.json +0 -98
- package/templates/langs/zh/skills/ai-sprint-orchestration/references/workflow-v1-acceptance-checklist.md +0 -58
- package/templates/langs/zh/skills/ai-sprint-orchestration/references/workflow-v1.4-work-unit-handoff.md +0 -190
- package/templates/langs/zh/skills/ai-sprint-orchestration/runtime/.gitignore +0 -2
- package/templates/langs/zh/skills/ai-sprint-orchestration/scripts/lib/archive.mjs +0 -310
- package/templates/langs/zh/skills/ai-sprint-orchestration/scripts/lib/contract-enforcement.mjs +0 -683
- package/templates/langs/zh/skills/ai-sprint-orchestration/scripts/lib/decision.mjs +0 -604
- package/templates/langs/zh/skills/ai-sprint-orchestration/scripts/lib/state-store.mjs +0 -32
- package/templates/langs/zh/skills/ai-sprint-orchestration/scripts/lib/task-specs.mjs +0 -707
- package/templates/langs/zh/skills/ai-sprint-orchestration/scripts/run.mjs +0 -3455
- package/templates/langs/zh/skills/ai-sprint-orchestration/test/archive.test.mjs +0 -230
- package/templates/langs/zh/skills/ai-sprint-orchestration/test/contract-enforcement.test.mjs +0 -672
- package/templates/langs/zh/skills/ai-sprint-orchestration/test/decision.test.mjs +0 -1321
- package/templates/langs/zh/skills/ai-sprint-orchestration/test/run.test.mjs +0 -1435
- package/templates/langs/zh/skills/evolve-system/SKILL.md +0 -46
- package/templates/langs/zh/skills/manage-okr/SKILL.md +0 -109
- package/templates/langs/zh/skills/pd-daily/SKILL.md +0 -283
- package/templates/langs/zh/skills/pd-grooming/SKILL.md +0 -46
|
@@ -25,16 +25,13 @@ I'm your intelligent mentor, helping you understand and use all features of Prin
|
|
|
25
25
|
|
|
26
26
|
| Command | Purpose | Use Case |
|
|
27
27
|
|---------|---------|----------|
|
|
28
|
-
| `/pd-init` | Initialize strategy
|
|
29
|
-
| `/pd-okr` | Objectives and Key Results management | Weekly/monthly review |
|
|
28
|
+
| `/pd-init` | Initialize strategy | New project startup |
|
|
30
29
|
| `/pd-bootstrap` | Environment tool scan and upgrade | Tool upgrade |
|
|
31
30
|
| `/pd-research` | Initiate tool upgrade research | Deep research |
|
|
32
31
|
| `/pd-thinking` | Manage mental models and candidates | Metacognition |
|
|
33
32
|
| `/pd-evolve` | Execute full evolution loop | Bug fix |
|
|
34
|
-
| `/pd-daily` | Configure and send evolution daily report | Daily review |
|
|
35
33
|
| `/pd-evolution-status` | View trust score and security stage | Permission check |
|
|
36
34
|
| `/pd-status` | View system status (GFI and Pain Dictionary) | Health check |
|
|
37
|
-
| `/pd-grooming` | Workspace digital cleanup | Entropy reduction |
|
|
38
35
|
| `/pd-help` | Get interactive command guidance | This skill |
|
|
39
36
|
|
|
40
37
|
---
|
|
@@ -46,7 +43,7 @@ I'm your intelligent mentor, helping you understand and use all features of Prin
|
|
|
46
43
|
**Trigger**: User says "I just created a new project" or similar
|
|
47
44
|
|
|
48
45
|
**Recommended Flow**:
|
|
49
|
-
1. `/pd-init` - Establish strategic vision
|
|
46
|
+
1. `/pd-init` - Establish strategic vision
|
|
50
47
|
2. `/pd-bootstrap` - Scan environment tools, get capability list
|
|
51
48
|
3. `/pd-thinking` - Establish project's mental model baseline
|
|
52
49
|
|
|
@@ -71,26 +68,14 @@ I'm your intelligent mentor, helping you understand and use all features of Prin
|
|
|
71
68
|
**Trigger**: User says "what did I do today", "check progress", "give me a report"
|
|
72
69
|
|
|
73
70
|
**Recommended Flow**:
|
|
74
|
-
1. `/pd-
|
|
75
|
-
2. `/pd-
|
|
76
|
-
3. `/pd-okr` - Check OKR alignment
|
|
71
|
+
1. `/pd-evolution-status` - View current trust score
|
|
72
|
+
2. `/pd-status` - Check GFI and Pain Dictionary status
|
|
77
73
|
|
|
78
|
-
**Script**: "
|
|
74
|
+
**Script**: "Let me help you review the current system status."
|
|
79
75
|
|
|
80
76
|
---
|
|
81
77
|
|
|
82
|
-
### Scenario 4:
|
|
83
|
-
|
|
84
|
-
**Trigger**: User says "project is too messy", "too many files", "need to organize"
|
|
85
|
-
|
|
86
|
-
**Recommended Flow**:
|
|
87
|
-
1. `/pd-grooming` - Start workspace cleanup
|
|
88
|
-
|
|
89
|
-
**Script**: "Digital cleanliness is a virtue. Let me help you reduce entropy."
|
|
90
|
-
|
|
91
|
-
---
|
|
92
|
-
|
|
93
|
-
### Scenario 5: Permission or Security Related
|
|
78
|
+
### Scenario 4: Permission or Security Related
|
|
94
79
|
|
|
95
80
|
**Trigger**: User says "not enough permissions", "blocked", "security level"
|
|
96
81
|
|
|
@@ -186,8 +171,8 @@ For complex scenarios, combine multiple skills:
|
|
|
186
171
|
| Scenario | Combined Flow |
|
|
187
172
|
|----------|---------------|
|
|
188
173
|
| Major refactor | `/pd-evolve` → `deductive-audit` → execute |
|
|
189
|
-
| System optimization | `/pd-status` → `
|
|
190
|
-
| Project review | `/pd-
|
|
174
|
+
| System optimization | `/pd-status` → `root-cause` → optimize |
|
|
175
|
+
| Project review | `/pd-evolution-status` → `/pd-status` → `reflection-log` |
|
|
191
176
|
|
|
192
177
|
### Internal Skill Calls
|
|
193
178
|
|
|
@@ -162,20 +162,7 @@ memory/
|
|
|
162
162
|
|
|
163
163
|
如果用户同意,**执行以下命令:**
|
|
164
164
|
|
|
165
|
-
### 1.
|
|
166
|
-
|
|
167
|
-
**功能:** 每天清理工作区临时文件,保持项目整洁。
|
|
168
|
-
|
|
169
|
-
```bash
|
|
170
|
-
openclaw cron add --name "pd-grooming-daily" \
|
|
171
|
-
--cron "0 2 * * *" --tz "UTC" \
|
|
172
|
-
--session isolated \
|
|
173
|
-
--light-context \
|
|
174
|
-
--no-deliver \
|
|
175
|
-
--message '执行 pd-grooming 技能:检查工作区根目录,清理临时文件和数字垃圾。严格遵循安全红线,不要删除业务代码。'
|
|
176
|
-
```
|
|
177
|
-
|
|
178
|
-
### 2. 环境健康检查(每 4 小时)
|
|
165
|
+
### 1. 环境健康检查(每 4 小时)
|
|
179
166
|
|
|
180
167
|
**功能:** 验证核心工具(rg, node, python)是否可用,异常时告警。
|
|
181
168
|
|
|
@@ -220,7 +207,7 @@ openclaw cron add --name "weekly-governance" \
|
|
|
220
207
|
--cron "0 0 * * 0" --tz "UTC" \
|
|
221
208
|
--session isolated \
|
|
222
209
|
--timeout 300000 \
|
|
223
|
-
--message '执行周治理:1) 验证 CURRENT_FOCUS.md 声称(PR 合并?文档存在?测试通过?),2) 更新 WEEK_STATE.json 指标,3) 记录到 WEEK_EVENTS.jsonl,4)
|
|
210
|
+
--message '执行周治理:1) 验证 CURRENT_FOCUS.md 声称(PR 合并?文档存在?测试通过?),2) 更新 WEEK_STATE.json 指标,3) 记录到 WEEK_EVENTS.jsonl,4) 如果任务队列为空,提醒用户规划下一阶段任务'
|
|
224
211
|
```
|
|
225
212
|
|
|
226
213
|
**JSON 配置参考:**
|
|
@@ -46,7 +46,7 @@ disable-model-invocation: true
|
|
|
46
46
|
## Environment Capabilities
|
|
47
47
|
Check @.state/SYSTEM_CAPABILITIES.json for high-performance tools (e.g., ripgrep, ast-grep) available in this environment. Use them!
|
|
48
48
|
```
|
|
49
|
-
- 提示用户运行 `/
|
|
49
|
+
- 提示用户运行 `/pd-status` 或 `/admin diagnose` 以让 Agent 感知新能力。
|
|
50
50
|
## 核心原则
|
|
51
51
|
- **喜新厌旧**: 敢于推荐新工具替代旧工具(如推荐 `pnpm` 替 `npm`,推荐 `vitest` 替 `jest`),但要说明理由。
|
|
52
52
|
- **安全第一**: 在安装前必须获得用户明确授权。
|
|
@@ -25,16 +25,13 @@ disable-model-invocation: true
|
|
|
25
25
|
|
|
26
26
|
| 命令 | 用途 | 适用场景 |
|
|
27
27
|
|------|------|----------|
|
|
28
|
-
| `/pd-init` |
|
|
29
|
-
| `/pd-okr` | 目标与关键结果管理 | 周/月度复盘 |
|
|
28
|
+
| `/pd-init` | 初始化战略 | 新项目启动 |
|
|
30
29
|
| `/pd-bootstrap` | 环境工具扫描与升级 | 装备升级 |
|
|
31
30
|
| `/pd-research` | 发起工具升级研究 | 深度调研 |
|
|
32
31
|
| `/pd-thinking` | 管理思维模型与候选方案 | 元认知管理 |
|
|
33
32
|
| `/pd-evolve` | 执行完整进化循环 | 问题修复 |
|
|
34
|
-
| `/pd-daily` | 配置并发送进化日报 | 日常查看 |
|
|
35
33
|
| `/pd-evolution-status` | 查看EP等级与安全状态 | 状态查询 |
|
|
36
34
|
| `/pd-status` | 查看系统状态(GFI和痛苦词典) | 健康检查 |
|
|
37
|
-
| `/pd-grooming` | 工作区数字大扫除 | 熵减维护 |
|
|
38
35
|
| `/pd-help` | 获取交互式命令引导 | 本技能 |
|
|
39
36
|
|
|
40
37
|
---
|
|
@@ -71,26 +68,14 @@ disable-model-invocation: true
|
|
|
71
68
|
**触发条件**: 用户说"今天干了什么"、"看看进度"、"汇报一下"
|
|
72
69
|
|
|
73
70
|
**推荐流程**:
|
|
74
|
-
1. `/pd-
|
|
75
|
-
2. `/pd-
|
|
76
|
-
3. `/pd-okr` - 检查 OKR 对齐情况
|
|
71
|
+
1. `/pd-evolution-status` - 查看当前信任积分
|
|
72
|
+
2. `/pd-status` - 查看 GFI 和痛苦词典状态
|
|
77
73
|
|
|
78
|
-
**话术**: "
|
|
74
|
+
**话术**: "让我帮你回顾一下系统状态。"
|
|
79
75
|
|
|
80
76
|
---
|
|
81
77
|
|
|
82
|
-
### 场景 4:
|
|
83
|
-
|
|
84
|
-
**触发条件**: 用户说"项目太乱了"、"文件一堆"、"需要整理"
|
|
85
|
-
|
|
86
|
-
**推荐流程**:
|
|
87
|
-
1. `/pd-grooming` - 启动工作区大扫除
|
|
88
|
-
|
|
89
|
-
**话术**: "数字洁癖是一种美德。让我帮你熵减。"
|
|
90
|
-
|
|
91
|
-
---
|
|
92
|
-
|
|
93
|
-
### 场景 5: 权限或安全相关
|
|
78
|
+
### 场景 4: 权限或安全相关
|
|
94
79
|
|
|
95
80
|
**触发条件**: 用户说"权限不够"、"被拦截了"、"安全等级"
|
|
96
81
|
|
|
@@ -186,8 +171,8 @@ disable-model-invocation: true
|
|
|
186
171
|
| 场景 | 组合流程 |
|
|
187
172
|
|------|----------|
|
|
188
173
|
| 大型重构 | `/pd-evolve` → `deductive-audit` → 执行 |
|
|
189
|
-
| 系统优化 | `/pd-status` → `
|
|
190
|
-
| 项目复盘 | `/pd-
|
|
174
|
+
| 系统优化 | `/pd-status` → `root-cause` → 优化实施 |
|
|
175
|
+
| 项目复盘 | `/pd-evolution-status` → `/pd-status` → `reflection-log` |
|
|
191
176
|
|
|
192
177
|
### 内部技能调用
|
|
193
178
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { handleInitStrategy
|
|
2
|
+
import { handleInitStrategy } from '../../src/commands/strategy';
|
|
3
3
|
|
|
4
4
|
describe('Slash Commands Hook', () => {
|
|
5
5
|
it('should handle /init-strategy command', () => {
|
|
6
|
-
const mockCtx = {
|
|
6
|
+
const mockCtx = {
|
|
7
7
|
workspaceDir: '/mock/workspace',
|
|
8
8
|
commandBody: '/init-strategy',
|
|
9
9
|
channel: 'cli',
|
|
@@ -16,19 +16,4 @@ describe('Slash Commands Hook', () => {
|
|
|
16
16
|
expect(result).toBeDefined();
|
|
17
17
|
expect(result.text).toContain('Strategy Initialization');
|
|
18
18
|
});
|
|
19
|
-
|
|
20
|
-
it('should handle /manage-okr command', () => {
|
|
21
|
-
const mockCtx = {
|
|
22
|
-
workspaceDir: '/mock/workspace',
|
|
23
|
-
commandBody: '/manage-okr',
|
|
24
|
-
channel: 'cli',
|
|
25
|
-
isAuthorizedSender: true,
|
|
26
|
-
config: {} as any
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
const result = handleManageOkr(mockCtx as any);
|
|
30
|
-
|
|
31
|
-
expect(result).toBeDefined();
|
|
32
|
-
expect(result.text).toContain('OKR Management');
|
|
33
|
-
});
|
|
34
|
-
});
|
|
19
|
+
});
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import * as os from 'os';
|
|
4
|
+
import * as path from 'path';
|
|
5
|
+
import { WorkspaceContext } from '../../src/core/workspace-context.js';
|
|
6
|
+
|
|
7
|
+
const mockLearner = {
|
|
8
|
+
getStore: vi.fn(() => ({ keywords: [{ term: 'wrong', weight: 0.5, hitCount: 3, truePositiveCount: 1, falsePositiveCount: 2 }] })),
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const mockDb = {
|
|
12
|
+
listRecentSessions: vi.fn(() => [{ sessionId: 'session-1' }]),
|
|
13
|
+
listUserTurnsForSession: vi.fn(() => [{ rawExcerpt: 'User said wrong input', correctionDetected: true, correctionCue: 'wrong' }]),
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const mockOptimizationService = {
|
|
17
|
+
buildTrajectoryHistory: vi.fn(async () => [
|
|
18
|
+
{ sessionId: 'session-1', timestamp: 'now', term: 'wrong', userMessage: '' }
|
|
19
|
+
]),
|
|
20
|
+
applyResult: vi.fn(),
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
vi.mock('../../src/core/correction-cue-learner.js', () => ({
|
|
24
|
+
CorrectionCueLearner: { get: vi.fn(() => mockLearner) },
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
vi.mock('../../src/core/trajectory.js', () => ({
|
|
28
|
+
TrajectoryRegistry: {
|
|
29
|
+
get: vi.fn(() => mockDb),
|
|
30
|
+
clear: vi.fn(),
|
|
31
|
+
},
|
|
32
|
+
}));
|
|
33
|
+
|
|
34
|
+
vi.mock('../../src/service/keyword-optimization-service.js', () => ({
|
|
35
|
+
KeywordOptimizationService: { get: vi.fn(() => mockOptimizationService) },
|
|
36
|
+
}));
|
|
37
|
+
|
|
38
|
+
const mockDispatch = vi.fn().mockResolvedValue({
|
|
39
|
+
updated: true,
|
|
40
|
+
summary: 'Keyword store optimized',
|
|
41
|
+
updates: { wrong: { action: 'update', weight: 0.4, reasoning: 'slightly high FP' } }
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const mockRegister = vi.fn();
|
|
45
|
+
|
|
46
|
+
vi.mock('@principles/core/runtime-v2', () => {
|
|
47
|
+
return {
|
|
48
|
+
WorkflowFunnelLoader: class {
|
|
49
|
+
getFunnel = vi.fn(() => ({
|
|
50
|
+
policy: {
|
|
51
|
+
runtimeKind: 'pi-ai',
|
|
52
|
+
provider: 'anthropic',
|
|
53
|
+
model: 'anthropic/claude-3-5-sonnet',
|
|
54
|
+
apiKeyEnv: 'ANTHROPIC_API_KEY',
|
|
55
|
+
timeoutMs: 30000,
|
|
56
|
+
}
|
|
57
|
+
}));
|
|
58
|
+
},
|
|
59
|
+
PiAiRuntimeAdapter: class {},
|
|
60
|
+
CorrectionObserver: class {},
|
|
61
|
+
AgentScheduler: class {
|
|
62
|
+
register = mockRegister;
|
|
63
|
+
dispatch = mockDispatch;
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
import { CorrectionObserverService, runCorrectionObserverCycle } from '../../src/service/correction-observer-service.js';
|
|
69
|
+
import { safeRmDir } from '../test-utils.js';
|
|
70
|
+
|
|
71
|
+
describe('CorrectionObserverService — Independent Service (PRI-293)', () => {
|
|
72
|
+
beforeEach(() => {
|
|
73
|
+
vi.useFakeTimers();
|
|
74
|
+
vi.clearAllMocks();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
afterEach(() => {
|
|
78
|
+
vi.useRealTimers();
|
|
79
|
+
CorrectionObserverService.stop?.({} as any);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('has correct service id', () => {
|
|
83
|
+
expect(CorrectionObserverService.id).toBe('principles-correction-observer');
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('starts and schedules periodic cycles', async () => {
|
|
87
|
+
const workspaceDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pd-corr-obs-'));
|
|
88
|
+
const stateDir = path.join(workspaceDir, '.state');
|
|
89
|
+
fs.mkdirSync(stateDir, { recursive: true });
|
|
90
|
+
|
|
91
|
+
const logger = { info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn() };
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
CorrectionObserverService.start({
|
|
95
|
+
workspaceDir,
|
|
96
|
+
stateDir,
|
|
97
|
+
logger,
|
|
98
|
+
config: { get: () => undefined },
|
|
99
|
+
} as any);
|
|
100
|
+
|
|
101
|
+
expect(logger.info).toHaveBeenCalledWith(
|
|
102
|
+
expect.stringContaining('[PD:CorrectionObserver] Starting')
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
await vi.advanceTimersByTimeAsync(10_000);
|
|
106
|
+
for (let i = 0; i < 20; i++) {
|
|
107
|
+
await Promise.resolve();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
expect(mockRegister).toHaveBeenCalled();
|
|
111
|
+
expect(mockDispatch).toHaveBeenCalledWith('correction-observer', expect.objectContaining({
|
|
112
|
+
parentSessionId: 'correction-observer-service',
|
|
113
|
+
workspaceDir,
|
|
114
|
+
recentMessages: ['User said wrong input'],
|
|
115
|
+
}));
|
|
116
|
+
|
|
117
|
+
expect(mockOptimizationService.applyResult).toHaveBeenCalledWith(expect.objectContaining({
|
|
118
|
+
updated: true,
|
|
119
|
+
summary: 'Keyword store optimized',
|
|
120
|
+
}));
|
|
121
|
+
} finally {
|
|
122
|
+
CorrectionObserverService.stop?.({} as any);
|
|
123
|
+
safeRmDir(workspaceDir);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('stops cleanly and cancels pending timer', () => {
|
|
128
|
+
const workspaceDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pd-corr-obs-stop-'));
|
|
129
|
+
const stateDir = path.join(workspaceDir, '.state');
|
|
130
|
+
fs.mkdirSync(stateDir, { recursive: true });
|
|
131
|
+
|
|
132
|
+
const logger = { info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn() };
|
|
133
|
+
|
|
134
|
+
try {
|
|
135
|
+
CorrectionObserverService.start({
|
|
136
|
+
workspaceDir,
|
|
137
|
+
stateDir,
|
|
138
|
+
logger,
|
|
139
|
+
config: { get: () => undefined },
|
|
140
|
+
} as any);
|
|
141
|
+
|
|
142
|
+
CorrectionObserverService.stop?.({} as any);
|
|
143
|
+
|
|
144
|
+
vi.advanceTimersByTime(30_000);
|
|
145
|
+
|
|
146
|
+
expect(mockDispatch).not.toHaveBeenCalled();
|
|
147
|
+
} finally {
|
|
148
|
+
safeRmDir(workspaceDir);
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('does not reschedule after stop during active cycle (P2 fix)', async () => {
|
|
153
|
+
const workspaceDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pd-corr-obs-race-'));
|
|
154
|
+
const stateDir = path.join(workspaceDir, '.state');
|
|
155
|
+
fs.mkdirSync(stateDir, { recursive: true });
|
|
156
|
+
|
|
157
|
+
const logger = { info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn() };
|
|
158
|
+
|
|
159
|
+
let cycleResolve: () => void;
|
|
160
|
+
const cyclePromise = new Promise<void>(r => { cycleResolve = r; });
|
|
161
|
+
mockDispatch.mockImplementationOnce(async () => {
|
|
162
|
+
cycleResolve!();
|
|
163
|
+
return { updated: false, summary: 'in-flight' };
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
try {
|
|
167
|
+
CorrectionObserverService.start({
|
|
168
|
+
workspaceDir,
|
|
169
|
+
stateDir,
|
|
170
|
+
logger,
|
|
171
|
+
config: { get: () => undefined },
|
|
172
|
+
} as any);
|
|
173
|
+
|
|
174
|
+
await vi.advanceTimersByTimeAsync(10_000);
|
|
175
|
+
await cyclePromise;
|
|
176
|
+
|
|
177
|
+
CorrectionObserverService.stop?.({} as any);
|
|
178
|
+
|
|
179
|
+
vi.advanceTimersByTime(15 * 60 * 1000 * 2);
|
|
180
|
+
|
|
181
|
+
expect(mockDispatch).toHaveBeenCalledTimes(1);
|
|
182
|
+
} finally {
|
|
183
|
+
CorrectionObserverService.stop?.({} as any);
|
|
184
|
+
safeRmDir(workspaceDir);
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('logs structured reason when workspaceDir is missing (ERR-002)', () => {
|
|
189
|
+
const logger = { info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn() };
|
|
190
|
+
|
|
191
|
+
CorrectionObserverService.start({
|
|
192
|
+
workspaceDir: undefined as any,
|
|
193
|
+
logger,
|
|
194
|
+
config: { get: () => undefined },
|
|
195
|
+
} as any);
|
|
196
|
+
|
|
197
|
+
expect(logger.warn).toHaveBeenCalledWith(
|
|
198
|
+
expect.stringContaining('workspaceDir not found')
|
|
199
|
+
);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it('double start same workspace only dispatches one loop (P1 fix)', async () => {
|
|
203
|
+
const workspaceDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pd-corr-dbl-'));
|
|
204
|
+
const stateDir = path.join(workspaceDir, '.state');
|
|
205
|
+
fs.mkdirSync(stateDir, { recursive: true });
|
|
206
|
+
|
|
207
|
+
const logger1 = { info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn() };
|
|
208
|
+
const logger2 = { info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn() };
|
|
209
|
+
|
|
210
|
+
try {
|
|
211
|
+
CorrectionObserverService.start({
|
|
212
|
+
workspaceDir,
|
|
213
|
+
stateDir,
|
|
214
|
+
logger: logger1,
|
|
215
|
+
config: { get: () => undefined },
|
|
216
|
+
} as any);
|
|
217
|
+
|
|
218
|
+
CorrectionObserverService.start({
|
|
219
|
+
workspaceDir,
|
|
220
|
+
stateDir,
|
|
221
|
+
logger: logger2,
|
|
222
|
+
config: { get: () => undefined },
|
|
223
|
+
} as any);
|
|
224
|
+
|
|
225
|
+
expect(logger2.info).toHaveBeenCalledWith(
|
|
226
|
+
expect.stringContaining('Already started')
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
await vi.advanceTimersByTimeAsync(10_000);
|
|
230
|
+
for (let i = 0; i < 20; i++) {
|
|
231
|
+
await Promise.resolve();
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
expect(mockDispatch).toHaveBeenCalledTimes(1);
|
|
235
|
+
} finally {
|
|
236
|
+
CorrectionObserverService.stop?.({} as any);
|
|
237
|
+
safeRmDir(workspaceDir);
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it('stop after double start cancels all timers and allows clean restart', async () => {
|
|
242
|
+
const workspaceDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pd-corr-stopdbl-'));
|
|
243
|
+
const stateDir = path.join(workspaceDir, '.state');
|
|
244
|
+
fs.mkdirSync(stateDir, { recursive: true });
|
|
245
|
+
|
|
246
|
+
const logger = { info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn() };
|
|
247
|
+
|
|
248
|
+
try {
|
|
249
|
+
CorrectionObserverService.start({
|
|
250
|
+
workspaceDir,
|
|
251
|
+
stateDir,
|
|
252
|
+
logger,
|
|
253
|
+
config: { get: () => undefined },
|
|
254
|
+
} as any);
|
|
255
|
+
|
|
256
|
+
CorrectionObserverService.start({
|
|
257
|
+
workspaceDir,
|
|
258
|
+
stateDir,
|
|
259
|
+
logger,
|
|
260
|
+
config: { get: () => undefined },
|
|
261
|
+
} as any);
|
|
262
|
+
|
|
263
|
+
CorrectionObserverService.stop?.({} as any);
|
|
264
|
+
|
|
265
|
+
vi.advanceTimersByTime(30_000);
|
|
266
|
+
|
|
267
|
+
expect(mockDispatch).not.toHaveBeenCalled();
|
|
268
|
+
|
|
269
|
+
CorrectionObserverService.start({
|
|
270
|
+
workspaceDir,
|
|
271
|
+
stateDir,
|
|
272
|
+
logger,
|
|
273
|
+
config: { get: () => undefined },
|
|
274
|
+
} as any);
|
|
275
|
+
|
|
276
|
+
expect(logger.info).toHaveBeenCalledWith(
|
|
277
|
+
expect.stringContaining('[PD:CorrectionObserver] Starting')
|
|
278
|
+
);
|
|
279
|
+
} finally {
|
|
280
|
+
CorrectionObserverService.stop?.({} as any);
|
|
281
|
+
safeRmDir(workspaceDir);
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
describe('runCorrectionObserverCycle — Independent Execution', () => {
|
|
287
|
+
it('skips cycle when no recent sessions exist', async () => {
|
|
288
|
+
const workspaceDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pd-corr-cycle-'));
|
|
289
|
+
const stateDir = path.join(workspaceDir, '.state');
|
|
290
|
+
fs.mkdirSync(stateDir, { recursive: true });
|
|
291
|
+
|
|
292
|
+
const wctx = WorkspaceContext.fromHookContext({ workspaceDir });
|
|
293
|
+
const logger = { info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn() };
|
|
294
|
+
|
|
295
|
+
mockDb.listRecentSessions.mockReturnValueOnce([]);
|
|
296
|
+
|
|
297
|
+
try {
|
|
298
|
+
await runCorrectionObserverCycle(wctx, logger as any);
|
|
299
|
+
|
|
300
|
+
expect(logger.info).toHaveBeenCalledWith(
|
|
301
|
+
expect.stringContaining('No recent sessions found')
|
|
302
|
+
);
|
|
303
|
+
expect(mockDispatch).not.toHaveBeenCalled();
|
|
304
|
+
} finally {
|
|
305
|
+
safeRmDir(workspaceDir);
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
it('logs structured error on cycle failure (ERR-002)', async () => {
|
|
310
|
+
const workspaceDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pd-corr-err-'));
|
|
311
|
+
const stateDir = path.join(workspaceDir, '.state');
|
|
312
|
+
fs.mkdirSync(stateDir, { recursive: true });
|
|
313
|
+
|
|
314
|
+
const wctx = WorkspaceContext.fromHookContext({ workspaceDir });
|
|
315
|
+
const logger = { info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn() };
|
|
316
|
+
|
|
317
|
+
mockDb.listRecentSessions.mockImplementationOnce(() => {
|
|
318
|
+
throw new Error('DB connection failed');
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
try {
|
|
322
|
+
await runCorrectionObserverCycle(wctx, logger as any);
|
|
323
|
+
|
|
324
|
+
expect(logger.warn).toHaveBeenCalledWith(
|
|
325
|
+
expect.stringContaining('Correction observer cycle failed')
|
|
326
|
+
);
|
|
327
|
+
} finally {
|
|
328
|
+
safeRmDir(workspaceDir);
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
});
|