principles-disciple 1.40.0 → 1.42.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/.planning/codebase/ARCHITECTURE.md +157 -0
- package/.planning/codebase/CONCERNS.md +145 -0
- package/.planning/codebase/CONVENTIONS.md +148 -0
- package/.planning/codebase/INTEGRATIONS.md +81 -0
- package/.planning/codebase/STACK.md +87 -0
- package/.planning/codebase/STRUCTURE.md +193 -0
- package/.planning/codebase/TESTING.md +243 -0
- package/esbuild.config.js +32 -3
- package/openclaw.plugin.json +1 -1
- package/package.json +2 -1
- package/scripts/compile-principles.mjs +94 -0
- package/scripts/sync-plugin.mjs +96 -281
- package/src/commands/pain.ts +12 -5
- package/src/commands/promote-impl.ts +13 -7
- package/src/commands/rollback.ts +10 -3
- package/src/core/event-log.ts +8 -6
- package/src/core/evolution-types.ts +33 -1
- package/src/core/principle-compiler/code-validator.ts +120 -0
- package/src/core/principle-compiler/compiler.ts +242 -0
- package/src/core/principle-compiler/index.ts +10 -0
- package/src/core/principle-compiler/ledger-registrar.ts +107 -0
- package/src/core/principle-compiler/template-generator.ts +108 -0
- package/src/core/reflection/reflection-context.ts +228 -0
- package/src/hooks/message-sanitize.ts +18 -5
- package/src/hooks/prompt.ts +15 -4
- package/src/hooks/subagent.ts +2 -3
- package/src/http/principles-console-route.ts +21 -4
- package/src/service/evolution-worker.ts +89 -365
- package/src/service/queue-io.ts +375 -0
- package/src/service/queue-migration.ts +122 -0
- package/src/service/sleep-cycle.ts +157 -0
- package/src/service/subagent-workflow/runtime-direct-driver.ts +1 -1
- package/src/service/workflow-watchdog.ts +168 -0
- package/src/tools/deep-reflect.ts +22 -11
- package/src/types/event-payload.ts +80 -0
- package/src/types/queue.ts +70 -0
- package/src/utils/file-lock.ts +2 -2
- package/src/utils/io.ts +11 -3
- package/tests/core/code-validator.test.ts +197 -0
- package/tests/core/evolution-migration.test.ts +325 -1
- package/tests/core/ledger-registrar.test.ts +232 -0
- package/tests/core/principle-compiler.test.ts +348 -0
- package/tests/core/queue-purge.test.ts +337 -0
- package/tests/core/reflection-context.test.ts +356 -0
- package/tests/core/template-generator.test.ts +101 -0
- package/tests/fixtures/legacy-queue-v1.json +74 -0
- package/tests/integration/principle-compiler-e2e.test.ts +335 -0
- package/tests/queue/async-lock.test.ts +200 -0
- package/tests/service/evolution-worker.queue.test.ts +296 -0
- package/tests/service/queue-io.test.ts +229 -0
- package/tests/service/queue-migration.test.ts +147 -0
- package/tests/service/workflow-watchdog.test.ts +372 -0
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# Codebase Structure
|
|
2
|
+
|
|
3
|
+
**Analysis Date:** 2026-04-15
|
|
4
|
+
|
|
5
|
+
## Directory Layout
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
openclaw-plugin/
|
|
9
|
+
├── src/ # Main source code
|
|
10
|
+
│ ├── commands/ # Slash command implementations
|
|
11
|
+
│ ├── config/ # Configuration defaults and errors
|
|
12
|
+
│ ├── constants/ # Shared constants
|
|
13
|
+
│ ├── core/ # Core business logic
|
|
14
|
+
│ │ ├── hygiene/ # Hygiene tracking
|
|
15
|
+
│ │ ├── principle-internalization/ # Principle lifecycle
|
|
16
|
+
│ │ └── schema/ # Database schema and migrations
|
|
17
|
+
│ ├── hooks/ # OpenClaw hook handlers
|
|
18
|
+
│ ├── http/ # HTTP route handlers
|
|
19
|
+
│ ├── i18n/ # Internationalization
|
|
20
|
+
│ ├── service/ # Background services
|
|
21
|
+
│ │ └── subagent-workflow/ # Workflow managers
|
|
22
|
+
│ ├── tools/ # Plugin tools
|
|
23
|
+
│ ├── types/ # TypeScript type definitions
|
|
24
|
+
│ └── utils/ # Utility functions
|
|
25
|
+
├── ui/ # React UI
|
|
26
|
+
│ └── src/
|
|
27
|
+
│ ├── components/ # React components
|
|
28
|
+
│ ├── context/ # React contexts
|
|
29
|
+
│ ├── hooks/ # React hooks
|
|
30
|
+
│ └── pages/ # Page components
|
|
31
|
+
├── tests/ # Test suite
|
|
32
|
+
│ ├── commands/ # Command tests
|
|
33
|
+
│ ├── core/ # Core module tests
|
|
34
|
+
│ ├── fixtures/ # Test fixtures
|
|
35
|
+
│ ├── hooks/ # Hook tests
|
|
36
|
+
│ ├── integration/ # Integration tests
|
|
37
|
+
│ ├── service/ # Service tests
|
|
38
|
+
│ └── utils/ # Utility tests
|
|
39
|
+
├── templates/ # Workspace templates
|
|
40
|
+
│ ├── langs/ # Language-specific templates
|
|
41
|
+
│ └── workspace/ # Workspace structure templates
|
|
42
|
+
├── dist/ # Build output
|
|
43
|
+
├── scripts/ # Build scripts
|
|
44
|
+
├── .state/ # Runtime state (gitignored)
|
|
45
|
+
└── .tmp/ # Temporary files (gitignored)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Directory Purposes
|
|
49
|
+
|
|
50
|
+
**src/commands/:**
|
|
51
|
+
- Purpose: Slash command implementations
|
|
52
|
+
- Contains: 20+ command handlers (strategy, focus, pain, rollback, nocturnal-review, nocturnal-train, etc.)
|
|
53
|
+
- Key files: `strategy.ts`, `focus.ts`, `nocturnal-train.ts`, `nocturnal-rollout.ts`
|
|
54
|
+
|
|
55
|
+
**src/core/:**
|
|
56
|
+
- Purpose: Core business logic (evolution, trajectory, pain, training, rules)
|
|
57
|
+
- Contains: 70+ core modules including `evolution-engine.ts`, `trajectory.ts`, `nocturnal-trinity.ts`, `pain.ts`, `principle-tree-ledger.ts`
|
|
58
|
+
- Key files: `evolution-engine.ts`, `nocturnal-trinity.ts`, `rule-host.ts`
|
|
59
|
+
|
|
60
|
+
**src/hooks/:**
|
|
61
|
+
- Purpose: OpenClaw hook handlers for intercepting agent behavior
|
|
62
|
+
- Contains: `prompt.ts`, `gate.ts`, `pain.ts`, `llm.ts`, `lifecycle.ts`, `subagent.ts`, `trajectory-collector.ts`
|
|
63
|
+
- Key files: `gate.ts` (security), `prompt.ts` (context injection)
|
|
64
|
+
|
|
65
|
+
**src/service/:**
|
|
66
|
+
- Purpose: Background worker services
|
|
67
|
+
- Contains: `evolution-worker.ts` (main worker), `nocturnal-service.ts`, `trajectory-service.ts`, `central-database.ts`
|
|
68
|
+
- Key files: `evolution-worker.ts` (144KB, main async processor)
|
|
69
|
+
|
|
70
|
+
**src/service/subagent-workflow/:**
|
|
71
|
+
- Purpose: Workflow orchestration for complex subagent operations
|
|
72
|
+
- Contains: `nocturnal-workflow-manager.ts`, `deep-reflect-workflow-manager.ts`, `empathy-observer-workflow-manager.ts`
|
|
73
|
+
- Key files: `workflow-manager-base.ts`, `dynamic-timeout.ts`
|
|
74
|
+
|
|
75
|
+
**src/utils/:**
|
|
76
|
+
- Purpose: Shared utility functions
|
|
77
|
+
- Contains: `io.ts` (atomic writes), `plugin-logger.ts`, `retry.ts`, `hashing.ts`, `file-lock.ts`
|
|
78
|
+
- Key files: `io.ts` (critical for safe file operations)
|
|
79
|
+
|
|
80
|
+
**src/schema/:**
|
|
81
|
+
- Purpose: SQLite database schema and migrations
|
|
82
|
+
- Contains: `schema-definitions.ts`, `migration-runner.ts`, `migrations/*.ts`
|
|
83
|
+
- Migrations: 4 migrations (001-004)
|
|
84
|
+
|
|
85
|
+
**ui/src/:**
|
|
86
|
+
- Purpose: React-based plugin UI
|
|
87
|
+
- Contains: Pages (Overview, Evolution, Feedback, GateMonitor), components (Shell, ProtectedRoute)
|
|
88
|
+
- Key files: `App.tsx`, `pages/EvolutionPage.tsx`, `pages/FeedbackPage.tsx`
|
|
89
|
+
|
|
90
|
+
## Key File Locations
|
|
91
|
+
|
|
92
|
+
**Entry Points:**
|
|
93
|
+
- `src/index.ts`: Plugin entry point, registers all hooks/commands/tools
|
|
94
|
+
|
|
95
|
+
**Configuration:**
|
|
96
|
+
- `src/core/config.ts`: PainSettings defaults
|
|
97
|
+
- `src/core/paths.ts`: Directory and file path constants (PD_DIRS, PD_FILES)
|
|
98
|
+
- `openclaw.plugin.json`: Plugin manifest
|
|
99
|
+
|
|
100
|
+
**Core Logic:**
|
|
101
|
+
- `src/core/evolution-engine.ts`: Evolution processing (18KB)
|
|
102
|
+
- `src/core/nocturnal-trinity.ts`: Nocturnal training orchestration (87KB - largest file)
|
|
103
|
+
- `src/core/trajectory.ts`: Trajectory tracking (64KB)
|
|
104
|
+
- `src/core/principle-tree-ledger.ts`: Principle lifecycle management (22KB)
|
|
105
|
+
- `src/core/rule-host.ts`: Sandboxed rule execution (7KB)
|
|
106
|
+
|
|
107
|
+
**Service Layer:**
|
|
108
|
+
- `src/service/evolution-worker.ts`: Background evolution worker (144KB - largest file)
|
|
109
|
+
- `src/service/nocturnal-service.ts`: Nocturnal training service (59KB)
|
|
110
|
+
- `src/service/nocturnal-runtime.ts`: Runtime for nocturnal operations (24KB)
|
|
111
|
+
|
|
112
|
+
**Testing:**
|
|
113
|
+
- `tests/`: Test suite with unit and integration layers
|
|
114
|
+
- `vitest.config.ts`: Test configuration with unit/integration project separation
|
|
115
|
+
|
|
116
|
+
## Naming Conventions
|
|
117
|
+
|
|
118
|
+
**Files:**
|
|
119
|
+
- TypeScript: `kebab-case.ts` or `camelCase.ts` depending on module type
|
|
120
|
+
- Commands: `kebab-case.ts` (e.g., `nocturnal-review.ts`)
|
|
121
|
+
- Core modules: `camelCase.ts` (e.g., `evolutionEngine.ts`)
|
|
122
|
+
- React components: `PascalCase.tsx`
|
|
123
|
+
|
|
124
|
+
**Directories:**
|
|
125
|
+
- kebab-case: `subagent-workflow`, `principle-internalization`
|
|
126
|
+
|
|
127
|
+
**Types:**
|
|
128
|
+
- Interfaces: `PascalCase` (e.g., `PainSettings`, `EvolutionContext`)
|
|
129
|
+
- Type aliases: `PascalCase`
|
|
130
|
+
- Enums: `PascalCase`
|
|
131
|
+
|
|
132
|
+
## Where to Add New Code
|
|
133
|
+
|
|
134
|
+
**New Command:**
|
|
135
|
+
- Primary code: `src/commands/<name>.ts`
|
|
136
|
+
- Handler registration: `src/index.ts` in `registerCommandWithAlias()` or `api.registerCommand()`
|
|
137
|
+
- Tests: `tests/commands/<name>.test.ts`
|
|
138
|
+
|
|
139
|
+
**New Hook Handler:**
|
|
140
|
+
- Implementation: `src/hooks/<name>.ts`
|
|
141
|
+
- Registration: `src/index.ts` in `api.on('<hook_name>', ...)` call
|
|
142
|
+
- Tests: `tests/hooks/<name>.test.ts`
|
|
143
|
+
|
|
144
|
+
**New Core Service:**
|
|
145
|
+
- Implementation: `src/core/<name>.ts` or `src/service/<name>.ts`
|
|
146
|
+
- Registration: `src/index.ts` in `api.registerService()`
|
|
147
|
+
- Tests: `tests/core/<name>.test.ts` or `tests/service/<name>.test.ts`
|
|
148
|
+
|
|
149
|
+
**New Workflow Manager:**
|
|
150
|
+
- Implementation: `src/service/subagent-workflow/<name>-workflow-manager.ts`
|
|
151
|
+
- Base class: `src/service/subagent-workflow/workflow-manager-base.ts`
|
|
152
|
+
- Tests: `tests/service/subagent-workflow/<name>.test.ts`
|
|
153
|
+
|
|
154
|
+
**New Database Migration:**
|
|
155
|
+
- Implementation: `src/core/schema/migrations/<number>-<description>.ts`
|
|
156
|
+
- Registration: `src/core/schema/migrations/index.ts`
|
|
157
|
+
- Tests: Integration test in `tests/core/control-ui-db.test.ts`
|
|
158
|
+
|
|
159
|
+
**New Utility:**
|
|
160
|
+
- Shared: `src/utils/<name>.ts`
|
|
161
|
+
- Tests: `tests/utils/<name>.test.ts`
|
|
162
|
+
|
|
163
|
+
## Special Directories
|
|
164
|
+
|
|
165
|
+
**.state/:**
|
|
166
|
+
- Purpose: Runtime state (per workspace)
|
|
167
|
+
- Generated: Yes (created at runtime)
|
|
168
|
+
- Committed: No (gitignored)
|
|
169
|
+
|
|
170
|
+
**.tmp/:**
|
|
171
|
+
- Purpose: Temporary files during build/dev
|
|
172
|
+
- Generated: Yes
|
|
173
|
+
- Committed: No (gitignored)
|
|
174
|
+
|
|
175
|
+
**dist/:**
|
|
176
|
+
- Purpose: Build output
|
|
177
|
+
- Generated: Yes (by `npm run build`)
|
|
178
|
+
- Committed: Yes (in some branches)
|
|
179
|
+
|
|
180
|
+
**templates/:**
|
|
181
|
+
- Purpose: Workspace template files copied on init
|
|
182
|
+
- Generated: No
|
|
183
|
+
- Committed: Yes
|
|
184
|
+
- Contains: Language-specific templates (en, zh), workspace structure
|
|
185
|
+
|
|
186
|
+
**node_modules/:**
|
|
187
|
+
- Purpose: Dependencies
|
|
188
|
+
- Generated: Yes (by pnpm install)
|
|
189
|
+
- Committed: No
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
*Structure analysis: 2026-04-15*
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
# Testing Patterns
|
|
2
|
+
|
|
3
|
+
**Analysis Date:** 2026-04-15
|
|
4
|
+
|
|
5
|
+
## Test Framework
|
|
6
|
+
|
|
7
|
+
**Runner:** Vitest 4.1.0
|
|
8
|
+
- Config: `vitest.config.ts`
|
|
9
|
+
- Environment: `node`
|
|
10
|
+
- Pool: `threads` (required for `better-sqlite3` native handle cleanup)
|
|
11
|
+
|
|
12
|
+
**Assertion Library:** Vitest built-in (`expect`)
|
|
13
|
+
|
|
14
|
+
**Coverage:** `@vitest/coverage-v8`
|
|
15
|
+
- Thresholds: lines 70%, functions 70%, branches 60%, statements 70%
|
|
16
|
+
- Excludes: `tests/**`
|
|
17
|
+
|
|
18
|
+
**Run Commands:**
|
|
19
|
+
```bash
|
|
20
|
+
npm test # Run unit tests (fast, parallel)
|
|
21
|
+
npm run test:unit # Alias for above
|
|
22
|
+
npm run test:integration # Run integration tests only
|
|
23
|
+
npm run test:coverage # Run with coverage report
|
|
24
|
+
npm run test:all # Run all tests (unit + integration)
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Test File Organization
|
|
28
|
+
|
|
29
|
+
**Location:**
|
|
30
|
+
- Tests co-located in `tests/` directory, mirroring `src/` structure
|
|
31
|
+
- `tests/core/`, `tests/commands/`, `tests/hooks/`, `tests/service/`, `tests/utils/`
|
|
32
|
+
- Integration tests in `tests/integration/`
|
|
33
|
+
|
|
34
|
+
**Naming:**
|
|
35
|
+
- `*.test.ts` suffix for all test files
|
|
36
|
+
- Example: `tests/core/detection-service.test.ts`
|
|
37
|
+
|
|
38
|
+
**Structure:**
|
|
39
|
+
```
|
|
40
|
+
tests/
|
|
41
|
+
├── commands/ # Command handlers
|
|
42
|
+
├── core/ # Core services and logic
|
|
43
|
+
├── fixtures/ # Shared fixtures
|
|
44
|
+
├── hooks/ # Hook handlers
|
|
45
|
+
├── integration/ # End-to-end tests
|
|
46
|
+
├── scripts/ # Script tests
|
|
47
|
+
└── service/ # Service layer
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Test Structure
|
|
51
|
+
|
|
52
|
+
**Suite Organization:**
|
|
53
|
+
```typescript
|
|
54
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
55
|
+
|
|
56
|
+
describe('DetectionService', () => {
|
|
57
|
+
beforeEach(() => {
|
|
58
|
+
vi.clearAllMocks();
|
|
59
|
+
// Reset state between tests
|
|
60
|
+
DetectionService.reset();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('should create a new instance on first get', () => {
|
|
64
|
+
// Test implementation
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
**Patterns:**
|
|
70
|
+
- `beforeEach` for setup, `afterEach` for teardown
|
|
71
|
+
- `vi.clearAllMocks()` between tests (not `vi.resetAllMocks()`)
|
|
72
|
+
- `vi.useFakeTimers()` / `vi.useRealTimers()` for time-sensitive tests
|
|
73
|
+
|
|
74
|
+
## Mocking
|
|
75
|
+
|
|
76
|
+
**Framework:** Vitest's `vi.fn()` and `vi.mock()`
|
|
77
|
+
|
|
78
|
+
**Module Mocking:**
|
|
79
|
+
```typescript
|
|
80
|
+
vi.mock('../../src/core/dictionary-service.js');
|
|
81
|
+
vi.mock('../../src/core/detection-funnel.js');
|
|
82
|
+
|
|
83
|
+
// Then configure mock implementations
|
|
84
|
+
vi.mocked(DictionaryService.get).mockReturnValue(mockDict as any);
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
**Built-in Module Mocking:**
|
|
88
|
+
```typescript
|
|
89
|
+
vi.mock('fs');
|
|
90
|
+
vi.mocked(fs.existsSync).mockReturnValue(true);
|
|
91
|
+
vi.mocked(fs.readFileSync).mockImplementation((p) => {
|
|
92
|
+
if (p.toString() === configPath) return JSON.stringify(mockConfig);
|
|
93
|
+
return '';
|
|
94
|
+
});
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
**Mock Reset:**
|
|
98
|
+
- `vi.clearAllMocks()` clears call history but keeps implementations
|
|
99
|
+
- `vi.resetAllMocks()` clears both (use with caution)
|
|
100
|
+
- Reset singleton state: `DetectionService.reset()`, `WorkspaceContext.clearCache()`
|
|
101
|
+
|
|
102
|
+
**Partial Mocks:**
|
|
103
|
+
```typescript
|
|
104
|
+
vi.mocked(fs.existsSync).mockImplementation((p) => p.toString() === configPath);
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Fixtures and Factories
|
|
108
|
+
|
|
109
|
+
**Temp Directory Pattern (integration tests):**
|
|
110
|
+
```typescript
|
|
111
|
+
const tempDirs: string[] = [];
|
|
112
|
+
|
|
113
|
+
function makeWorkspace(): string {
|
|
114
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'pd-runtime-summary-'));
|
|
115
|
+
tempDirs.push(dir);
|
|
116
|
+
fs.mkdirSync(path.join(dir, '.state', 'sessions'), { recursive: true });
|
|
117
|
+
return dir;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
afterEach(() => {
|
|
121
|
+
for (const dir of tempDirs.splice(0)) {
|
|
122
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
**JSON Fixture Writing:**
|
|
128
|
+
```typescript
|
|
129
|
+
function writeJson(filePath: string, value: unknown): void {
|
|
130
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
131
|
+
fs.writeFileSync(filePath, JSON.stringify(value, null, 2), 'utf8');
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
**Session File Helper:**
|
|
136
|
+
```typescript
|
|
137
|
+
function writeSession(workspace: string, sessionId: string, payload: Record<string, unknown>): void {
|
|
138
|
+
writeJson(path.join(workspace, '.state', 'sessions', `${sessionId}.json`), {
|
|
139
|
+
sessionId,
|
|
140
|
+
...payload,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Integration Tests
|
|
146
|
+
|
|
147
|
+
**Requirements:**
|
|
148
|
+
- Real SQLite database via `better-sqlite3`
|
|
149
|
+
- Thread pool required (not `vm` pool) due to native handle cleanup issues
|
|
150
|
+
- Explicit file list in `vitest.config.ts` integration array
|
|
151
|
+
|
|
152
|
+
**Integration Test Files:**
|
|
153
|
+
```typescript
|
|
154
|
+
const integrationTests = [
|
|
155
|
+
'tests/core/control-ui-db.test.ts',
|
|
156
|
+
'tests/core/evolution-logger.test.ts',
|
|
157
|
+
'tests/core/nocturnal-e2e.test.ts',
|
|
158
|
+
// ...
|
|
159
|
+
];
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## Common Patterns
|
|
163
|
+
|
|
164
|
+
**Async Testing with Fake Timers:**
|
|
165
|
+
```typescript
|
|
166
|
+
beforeEach(() => {
|
|
167
|
+
vi.useFakeTimers();
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
afterEach(() => {
|
|
171
|
+
vi.useRealTimers();
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('should retry on retryable error', async () => {
|
|
175
|
+
const fn = vi.fn()
|
|
176
|
+
.mockRejectedValueOnce(new Error('ETIMEDOUT'))
|
|
177
|
+
.mockResolvedValue('success');
|
|
178
|
+
|
|
179
|
+
const resultPromise = retryAsync(fn, { initialDelayMs: 100 });
|
|
180
|
+
await vi.advanceTimersByTimeAsync(100);
|
|
181
|
+
const result = await resultPromise;
|
|
182
|
+
expect(result).toBe('success');
|
|
183
|
+
});
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
**Error Testing:**
|
|
187
|
+
```typescript
|
|
188
|
+
it('should throw after max retries exceeded', async () => {
|
|
189
|
+
vi.useRealTimers();
|
|
190
|
+
const fn = vi.fn().mockRejectedValue(new Error('ETIMEDOUT'));
|
|
191
|
+
|
|
192
|
+
await expect(retryAsync(fn, { maxRetries: 1, initialDelayMs: 1, logger: { warn: vi.fn() } }))
|
|
193
|
+
.rejects.toThrow('ETIMEDOUT');
|
|
194
|
+
});
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
**Singleton Reset Pattern:**
|
|
198
|
+
```typescript
|
|
199
|
+
// Many services use singleton pattern requiring reset between tests
|
|
200
|
+
DetectionService.reset();
|
|
201
|
+
WorkspaceContext.clearCache();
|
|
202
|
+
clearSession('live-session');
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## Test Naming and Documentation
|
|
206
|
+
|
|
207
|
+
**Descriptive Test Names:**
|
|
208
|
+
```typescript
|
|
209
|
+
it('should block risk path writes at Seed tier (EP system)', () => { ... });
|
|
210
|
+
it('should return default values if file does not exist', () => { ... });
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
**Test Comments for Context:**
|
|
214
|
+
```typescript
|
|
215
|
+
// Task 4: Default Values Consistency Tests
|
|
216
|
+
describe('Gate Default Values Consistency', () => {
|
|
217
|
+
/**
|
|
218
|
+
* PURPOSE: Prove that gate.ts inline defaults match PROFILE_DEFAULTS.
|
|
219
|
+
* If gate.ts has inline defaults that differ from normalizeProfile(),
|
|
220
|
+
* this is a bug - the defaults should come from a single source of truth.
|
|
221
|
+
*/
|
|
222
|
+
});
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
## Test Isolation
|
|
226
|
+
|
|
227
|
+
**Environment Variable Injection:**
|
|
228
|
+
```typescript
|
|
229
|
+
// Set env before module load
|
|
230
|
+
process.env.PD_TEST_AGENTS_DIR = TEST_AGENTS_DIR;
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
**Path Traversal Protection Tests:**
|
|
234
|
+
```typescript
|
|
235
|
+
it('rejects path traversal session IDs', async () => {
|
|
236
|
+
const result = await extractRecentConversation('../../etc/passwd', 'main');
|
|
237
|
+
expect(result).toBe('');
|
|
238
|
+
});
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
*Testing analysis: 2026-04-15*
|
package/esbuild.config.js
CHANGED
|
@@ -25,6 +25,7 @@ function copyRecursive(src, dest) {
|
|
|
25
25
|
|
|
26
26
|
async function bundlePlugin() {
|
|
27
27
|
try {
|
|
28
|
+
// 1. Build the main bundle for OpenClaw
|
|
28
29
|
await build({
|
|
29
30
|
entryPoints: ['src/index.ts'],
|
|
30
31
|
outfile: 'dist/bundle.js',
|
|
@@ -44,7 +45,35 @@ async function bundlePlugin() {
|
|
|
44
45
|
metafile: true,
|
|
45
46
|
});
|
|
46
47
|
|
|
47
|
-
console.log('
|
|
48
|
+
console.log('Main bundle created: dist/bundle.js');
|
|
49
|
+
|
|
50
|
+
// 2. Build core tools for CLI usage (bootstrap-rules, etc)
|
|
51
|
+
// We keep these separate and un-minified for easier debugging and CLI importing
|
|
52
|
+
await build({
|
|
53
|
+
entryPoints: {
|
|
54
|
+
'core/bootstrap-rules': 'src/core/bootstrap-rules.ts',
|
|
55
|
+
'core/principle-tree-ledger': 'src/core/principle-tree-ledger.ts',
|
|
56
|
+
'core/principle-training-state': 'src/core/principle-training-state.ts',
|
|
57
|
+
'core/principle-compiler/index': 'src/core/principle-compiler/index.ts',
|
|
58
|
+
'core/trajectory/index': 'src/core/trajectory.ts',
|
|
59
|
+
},
|
|
60
|
+
outdir: 'dist',
|
|
61
|
+
bundle: true,
|
|
62
|
+
platform: 'node',
|
|
63
|
+
target: 'node20',
|
|
64
|
+
format: 'esm',
|
|
65
|
+
outbase: 'src',
|
|
66
|
+
external: [
|
|
67
|
+
'openclaw',
|
|
68
|
+
'@openclaw/sdk',
|
|
69
|
+
'@openclaw/plugin-kit',
|
|
70
|
+
'better-sqlite3',
|
|
71
|
+
],
|
|
72
|
+
sourcemap: false,
|
|
73
|
+
minify: false,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
console.log('Core CLI tools built in dist/core/');
|
|
48
77
|
|
|
49
78
|
const staticFiles = ['templates', 'openclaw.plugin.json'];
|
|
50
79
|
const distDir = 'dist';
|
|
@@ -65,9 +94,9 @@ async function bundlePlugin() {
|
|
|
65
94
|
console.log(`Copied: ${file} -> dist/${file}`);
|
|
66
95
|
}
|
|
67
96
|
|
|
68
|
-
console.log('\nPlugin
|
|
97
|
+
console.log('\nPlugin build ready for distribution.');
|
|
69
98
|
} catch (error) {
|
|
70
|
-
console.error('
|
|
99
|
+
console.error('Build failed:', error);
|
|
71
100
|
process.exit(1);
|
|
72
101
|
}
|
|
73
102
|
}
|
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "principles-disciple",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.42.0",
|
|
4
4
|
"description": "Native OpenClaw plugin for Principles Disciple",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/bundle.js",
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
"test:all": "vitest run",
|
|
38
38
|
"lint": "eslint src/",
|
|
39
39
|
"bootstrap-rules": "node scripts/bootstrap-rules.mjs",
|
|
40
|
+
"compile-principles": "node scripts/compile-principles.mjs",
|
|
40
41
|
"validate-live-path": "tsx scripts/validate-live-path.ts"
|
|
41
42
|
},
|
|
42
43
|
"devDependencies": {
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Principle Compiler CLI
|
|
5
|
+
*
|
|
6
|
+
* Compiles eligible principles (those derived from pain events) into
|
|
7
|
+
* auto-generated rules via the PrincipleCompiler pipeline.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* npm run compile-principles
|
|
11
|
+
* WORKSPACE_DIR=/path/to/workspace npm run compile-principles
|
|
12
|
+
* node scripts/compile-principles.mjs /path/to/workspace
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { join, dirname } from 'path';
|
|
16
|
+
import { fileURLToPath } from 'url';
|
|
17
|
+
|
|
18
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
19
|
+
const __dirname = dirname(__filename);
|
|
20
|
+
|
|
21
|
+
// Resolve workspace directory: CLI arg > env var > default
|
|
22
|
+
const WORKSPACE_DIR = process.argv[2]
|
|
23
|
+
|| process.env.WORKSPACE_DIR
|
|
24
|
+
|| join(process.env.HOME, '.openclaw', 'workspace-main');
|
|
25
|
+
|
|
26
|
+
const STATE_DIR = join(WORKSPACE_DIR, '.state');
|
|
27
|
+
|
|
28
|
+
async function run() {
|
|
29
|
+
console.log('Principle Compiler CLI');
|
|
30
|
+
console.log(` Workspace: ${WORKSPACE_DIR}`);
|
|
31
|
+
console.log(` State dir: ${STATE_DIR}`);
|
|
32
|
+
|
|
33
|
+
let compilerModule, trajectoryModule;
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
compilerModule = await import('../dist/core/principle-compiler/index.js');
|
|
37
|
+
} catch {
|
|
38
|
+
console.error('PrincipleCompiler module not found in dist/. Build first: node esbuild.config.js');
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
trajectoryModule = await import('../dist/core/trajectory/index.js');
|
|
44
|
+
} catch {
|
|
45
|
+
console.error('TrajectoryDatabase module not found in dist/. Build first: node esbuild.config.js');
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const { PrincipleCompiler } = compilerModule;
|
|
50
|
+
const { TrajectoryDatabase } = trajectoryModule;
|
|
51
|
+
|
|
52
|
+
let trajectory;
|
|
53
|
+
try {
|
|
54
|
+
trajectory = new TrajectoryDatabase({ workspaceDir: WORKSPACE_DIR });
|
|
55
|
+
} catch (err) {
|
|
56
|
+
console.error(`Failed to open trajectory database: ${err.message}`);
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
const compiler = new PrincipleCompiler(STATE_DIR, trajectory);
|
|
62
|
+
|
|
63
|
+
console.log('\nCompiling eligible principles...');
|
|
64
|
+
const results = compiler.compileAll();
|
|
65
|
+
|
|
66
|
+
const succeeded = results.filter(r => r.success);
|
|
67
|
+
const failed = results.filter(r => !r.success);
|
|
68
|
+
|
|
69
|
+
console.log(`\nResults: ${succeeded.length} succeeded, ${failed.length} failed`);
|
|
70
|
+
|
|
71
|
+
for (const r of succeeded) {
|
|
72
|
+
console.log(` + ${r.principleId} -> rule ${r.ruleId} (impl: ${r.implementationId})`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
for (const r of failed) {
|
|
76
|
+
console.log(` x ${r.principleId}: ${r.reason}`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (results.length === 0) {
|
|
80
|
+
console.log(' No eligible principles found for compilation.');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (failed.length > 0) {
|
|
84
|
+
process.exitCode = 1;
|
|
85
|
+
}
|
|
86
|
+
} finally {
|
|
87
|
+
trajectory.dispose();
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
run().catch((err) => {
|
|
92
|
+
console.error(`Fatal: ${err.message}`);
|
|
93
|
+
process.exit(1);
|
|
94
|
+
});
|