principles-disciple 1.73.0 → 1.75.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 (46) hide show
  1. package/INSTALL.md +1 -3
  2. package/openclaw.plugin.json +1 -1
  3. package/package.json +1 -1
  4. package/src/core/event-log.ts +0 -9
  5. package/src/core/migration.ts +0 -1
  6. package/src/core/path-resolver.ts +0 -1
  7. package/src/core/paths.ts +0 -1
  8. package/src/core/workspace-guidance-migrator.ts +179 -0
  9. package/src/hooks/gate-block-helper.ts +25 -20
  10. package/src/hooks/gate.ts +13 -61
  11. package/src/hooks/prompt.ts +1 -61
  12. package/src/index.ts +8 -12
  13. package/src/types/event-types.ts +0 -1
  14. package/src/utils/io.ts +0 -22
  15. package/templates/langs/en/core/AGENTS.md +5 -5
  16. package/templates/langs/en/core/BOOTSTRAP.md +1 -1
  17. package/templates/langs/en/principles/THINKING_OS.md +4 -3
  18. package/templates/langs/en/skills/admin/SKILL.md +2 -2
  19. package/templates/langs/en/skills/ai-sprint-orchestration/runtime/.gitignore +2 -2
  20. package/templates/langs/en/skills/evolve-task/SKILL.md +2 -2
  21. package/templates/langs/en/skills/pd-grooming/SKILL.md +1 -1
  22. package/templates/langs/en/skills/pd-mentor/SKILL.md +1 -2
  23. package/templates/langs/en/skills/reflection/SKILL.md +2 -2
  24. package/templates/langs/en/skills/report/SKILL.md +1 -1
  25. package/templates/langs/zh/core/AGENTS.md +5 -5
  26. package/templates/langs/zh/core/BOOTSTRAP.md +1 -1
  27. package/templates/langs/zh/principles/THINKING_OS.md +4 -3
  28. package/templates/langs/zh/skills/admin/SKILL.md +2 -2
  29. package/templates/langs/zh/skills/ai-sprint-orchestration/runtime/.gitignore +2 -2
  30. package/templates/langs/zh/skills/evolve-task/SKILL.md +2 -2
  31. package/templates/langs/zh/skills/pd-grooming/SKILL.md +1 -1
  32. package/templates/langs/zh/skills/pd-mentor/SKILL.md +1 -2
  33. package/templates/langs/zh/skills/reflection/SKILL.md +2 -2
  34. package/templates/langs/zh/skills/report/SKILL.md +1 -1
  35. package/tests/core/migration.test.ts +7 -7
  36. package/tests/core/path-resolver.test.ts +1 -1
  37. package/tests/core/paths-refactor.test.ts +0 -22
  38. package/tests/core/workspace-context.test.ts +2 -2
  39. package/tests/core-anti-growth.test.ts +1 -1
  40. package/tests/hooks/confirm-first-removal.test.ts +188 -0
  41. package/tests/hooks/gate-no-path-write-tool.test.ts +172 -0
  42. package/src/core/confirm-first-gate.ts +0 -255
  43. package/templates/langs/en/skills/plan-script/SKILL.md +0 -32
  44. package/templates/langs/zh/skills/plan-script/SKILL.md +0 -32
  45. package/templates/workspace/PLAN.md +0 -2
  46. package/tests/hooks/confirm-first-gate.test.ts +0 -333
@@ -1,333 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
- import * as os from 'os';
3
- import * as path from 'path';
4
- import * as fs from 'fs';
5
- import {
6
- evaluateConfirmFirstGateSync,
7
- detectApprovalMarker,
8
- setConfirmFirstDirective,
9
- setConfirmFirstApproval,
10
- resetConfirmFirst,
11
- isSessionApproved,
12
- hasActiveDirective,
13
- clearAllConfirmFirstState,
14
- setConfirmFirstStore,
15
- hydrateFromStore,
16
- } from '../../src/core/confirm-first-gate.js';
17
- import { SqliteConnection } from '@principles/core/runtime-v2';
18
- import { SqliteConfirmFirstStateStore } from '@principles/core/runtime-v2';
19
-
20
- describe('Confirm-First Gate', () => {
21
- beforeEach(() => {
22
- clearAllConfirmFirstState();
23
- });
24
-
25
- describe('detectApprovalMarker', () => {
26
- it('detects Chinese approval markers', () => {
27
- expect(detectApprovalMarker('确认')).toBe(true);
28
- expect(detectApprovalMarker('批准')).toBe(true);
29
- expect(detectApprovalMarker('按计划执行')).toBe(true);
30
- expect(detectApprovalMarker('可以执行')).toBe(true);
31
- expect(detectApprovalMarker('就这么做')).toBe(true);
32
- expect(detectApprovalMarker('去执行')).toBe(true);
33
- expect(detectApprovalMarker('开始执行')).toBe(true);
34
- expect(detectApprovalMarker('执行吧')).toBe(true);
35
- expect(detectApprovalMarker('同意')).toBe(true);
36
- });
37
-
38
- it('detects English approval markers', () => {
39
- expect(detectApprovalMarker('approved')).toBe(true);
40
- expect(detectApprovalMarker('go ahead')).toBe(true);
41
- expect(detectApprovalMarker('lgtm')).toBe(true);
42
- expect(detectApprovalMarker('yes, do it')).toBe(true);
43
- expect(detectApprovalMarker('do it')).toBe(true);
44
- expect(detectApprovalMarker('yes, proceed')).toBe(true);
45
- expect(detectApprovalMarker('yes, execute')).toBe(true);
46
- expect(detectApprovalMarker('proceed with the plan')).toBe(true);
47
- expect(detectApprovalMarker('execute the plan')).toBe(true);
48
- expect(detectApprovalMarker('please proceed with the plan')).toBe(true);
49
- });
50
-
51
- it('rejects vague text', () => {
52
- expect(detectApprovalMarker('看看')).toBe(false);
53
- expect(detectApprovalMarker('继续想想')).toBe(false);
54
- expect(detectApprovalMarker('你决定')).toBe(false);
55
- expect(detectApprovalMarker('hello world')).toBe(false);
56
- expect(detectApprovalMarker('')).toBe(false);
57
- });
58
-
59
- it('rejects negated Chinese approval', () => {
60
- expect(detectApprovalMarker('不同意')).toBe(false);
61
- expect(detectApprovalMarker('不确认')).toBe(false);
62
- expect(detectApprovalMarker('先不执行')).toBe(false);
63
- expect(detectApprovalMarker('还没准备好确认')).toBe(false);
64
- expect(detectApprovalMarker('暂不批准')).toBe(false);
65
- });
66
-
67
- it('rejects negated English approval', () => {
68
- expect(detectApprovalMarker("don't proceed")).toBe(false);
69
- expect(detectApprovalMarker("don't do it")).toBe(false);
70
- expect(detectApprovalMarker("not ready to confirm")).toBe(false);
71
- expect(detectApprovalMarker("can't approve yet")).toBe(false);
72
- expect(detectApprovalMarker("won't proceed")).toBe(false);
73
- expect(detectApprovalMarker("stop")).toBe(false);
74
- });
75
-
76
- it('rejects ambiguous English phrases without explicit approval context', () => {
77
- expect(detectApprovalMarker('please confirm requirements before proceeding')).toBe(false);
78
- expect(detectApprovalMarker('how should we proceed?')).toBe(false);
79
- expect(detectApprovalMarker('confirm the requirement first')).toBe(false);
80
- expect(detectApprovalMarker('should I proceed?')).toBe(false);
81
- expect(detectApprovalMarker('I need to confirm something')).toBe(false);
82
- expect(detectApprovalMarker('let me confirm the plan')).toBe(false);
83
- });
84
- });
85
-
86
- describe('evaluateConfirmFirstGateSync', () => {
87
- it('skips when no sessionId', () => {
88
- const result = evaluateConfirmFirstGateSync(undefined, 'write', {});
89
- expect(result.action).toBe('skip');
90
- });
91
-
92
- it('skips when no confirm-first directive active', () => {
93
- const result = evaluateConfirmFirstGateSync('session-1', 'write', {});
94
- expect(result.action).toBe('skip');
95
- });
96
-
97
- it('allows non-mutating tools even with active directive', () => {
98
- setConfirmFirstDirective('session-1', true, 'princ-mvp-acceptance-confirm-first');
99
- const result = evaluateConfirmFirstGateSync('session-1', 'read', {});
100
- expect(result.action).toBe('allow');
101
- });
102
-
103
- it('blocks write tool when directive active and not approved', () => {
104
- setConfirmFirstDirective('session-1', true, 'princ-mvp-acceptance-confirm-first');
105
- const result = evaluateConfirmFirstGateSync('session-1', 'write', { path: 'test.json' });
106
- expect(result.action).toBe('block');
107
- expect(result.reason).toBe('confirm_first_required');
108
- expect(result.principleId).toBe('princ-mvp-acceptance-confirm-first');
109
- expect(result.nextAction).toContain('owner approval');
110
- });
111
-
112
- it('blocks edit tool when directive active and not approved', () => {
113
- setConfirmFirstDirective('session-1', true, 'princ-mvp-acceptance-confirm-first');
114
- const result = evaluateConfirmFirstGateSync('session-1', 'edit', { file_path: 'test.ts' });
115
- expect(result.action).toBe('block');
116
- expect(result.reason).toBe('confirm_first_required');
117
- });
118
-
119
- it('blocks delete_file when directive active and not approved', () => {
120
- setConfirmFirstDirective('session-1', true, 'princ-mvp-acceptance-confirm-first');
121
- const result = evaluateConfirmFirstGateSync('session-1', 'delete_file', { path: 'test.txt' });
122
- expect(result.action).toBe('block');
123
- });
124
-
125
- it('blocks mutating exec when directive active and not approved', () => {
126
- setConfirmFirstDirective('session-1', true, 'princ-mvp-acceptance-confirm-first');
127
- const result = evaluateConfirmFirstGateSync('session-1', 'exec', { command: 'rm -rf /tmp/test' });
128
- expect(result.action).toBe('block');
129
- });
130
-
131
- it('allows non-mutating exec when directive active and not approved', () => {
132
- setConfirmFirstDirective('session-1', true, 'princ-mvp-acceptance-confirm-first');
133
- const result = evaluateConfirmFirstGateSync('session-1', 'exec', { command: 'ls -la' });
134
- expect(result.action).toBe('allow');
135
- });
136
-
137
- it('allows bash with undefined params when directive active', () => {
138
- setConfirmFirstDirective('session-1', true, 'princ-mvp-acceptance-confirm-first');
139
- const result = evaluateConfirmFirstGateSync('session-1', 'bash', undefined);
140
- expect(result.action).toBe('allow');
141
- });
142
-
143
- it('allows read tool when directive active and not approved', () => {
144
- setConfirmFirstDirective('session-1', true, 'princ-mvp-acceptance-confirm-first');
145
- const result = evaluateConfirmFirstGateSync('session-1', 'read', { file_path: 'test.ts' });
146
- expect(result.action).toBe('allow');
147
- });
148
-
149
- it('allows write after approval', () => {
150
- setConfirmFirstDirective('session-1', true, 'princ-mvp-acceptance-confirm-first');
151
- setConfirmFirstApproval('session-1');
152
- const result = evaluateConfirmFirstGateSync('session-1', 'write', { path: 'test.json' });
153
- expect(result.action).toBe('allow');
154
- expect(isSessionApproved('session-1')).toBe(true);
155
- });
156
-
157
- it('approval is session-scoped', () => {
158
- setConfirmFirstDirective('session-1', true, 'princ-mvp-acceptance-confirm-first');
159
- setConfirmFirstDirective('session-2', true, 'princ-mvp-acceptance-confirm-first');
160
- setConfirmFirstApproval('session-1');
161
-
162
- expect(evaluateConfirmFirstGateSync('session-1', 'write', {}).action).toBe('allow');
163
- expect(evaluateConfirmFirstGateSync('session-2', 'write', {}).action).toBe('block');
164
- });
165
-
166
- it('blocks apply_patch with no path when directive active', () => {
167
- setConfirmFirstDirective('session-1', true, 'princ-mvp-acceptance-confirm-first');
168
- const result = evaluateConfirmFirstGateSync('session-1', 'apply_patch', { patch: '@@ -1 +1 @@\n-old\n+new' });
169
- expect(result.action).toBe('block');
170
- expect(result.reason).toBe('confirm_first_required');
171
- });
172
-
173
- it('allows apply_patch after approval', () => {
174
- setConfirmFirstDirective('session-1', true, 'princ-mvp-acceptance-confirm-first');
175
- setConfirmFirstApproval('session-1');
176
- const result = evaluateConfirmFirstGateSync('session-1', 'apply_patch', { patch: '@@ -1 +1 @@\n-old\n+new' });
177
- expect(result.action).toBe('allow');
178
- });
179
-
180
- it('reset clears both directive and approval state', () => {
181
- setConfirmFirstDirective('session-1', true, 'princ-mvp-acceptance-confirm-first');
182
- setConfirmFirstApproval('session-1');
183
- resetConfirmFirst('session-1');
184
-
185
- expect(hasActiveDirective('session-1')).toBe(false);
186
- expect(isSessionApproved('session-1')).toBe(false);
187
- expect(evaluateConfirmFirstGateSync('session-1', 'write', {}).action).toBe('skip');
188
- });
189
- });
190
- });
191
-
192
- describe('Cross-restart persistence', () => {
193
- let tmpDir: string;
194
- let connection: SqliteConnection;
195
- let store: SqliteConfirmFirstStateStore;
196
-
197
- beforeEach(() => {
198
- clearAllConfirmFirstState();
199
- setConfirmFirstStore(null);
200
- tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pd-cf-test-'));
201
- connection = new SqliteConnection(tmpDir);
202
- store = new SqliteConfirmFirstStateStore(connection);
203
- });
204
-
205
- afterEach(() => {
206
- setConfirmFirstStore(null);
207
- clearAllConfirmFirstState();
208
- try {
209
- connection.close();
210
- } catch {}
211
- try {
212
- fs.rmSync(tmpDir, { recursive: true, force: true });
213
- } catch {}
214
- });
215
-
216
- it('directive + approval survive restart', () => {
217
- setConfirmFirstStore(store);
218
- setConfirmFirstDirective('sess-restart', true, 'princ-123');
219
- setConfirmFirstApproval('sess-restart');
220
-
221
- expect(evaluateConfirmFirstGateSync('sess-restart', 'write', {}).action).toBe('allow');
222
-
223
- setConfirmFirstStore(null);
224
- clearAllConfirmFirstState();
225
- setConfirmFirstStore(store);
226
- hydrateFromStore('sess-restart');
227
-
228
- expect(evaluateConfirmFirstGateSync('sess-restart', 'write', {}).action).toBe('allow');
229
- expect(hasActiveDirective('sess-restart')).toBe(true);
230
- expect(isSessionApproved('sess-restart')).toBe(true);
231
- });
232
-
233
- it('directive without approval survives restart', () => {
234
- setConfirmFirstStore(store);
235
- setConfirmFirstDirective('sess-restart', true, 'princ-456');
236
-
237
- expect(evaluateConfirmFirstGateSync('sess-restart', 'write', {}).action).toBe('block');
238
-
239
- setConfirmFirstStore(null);
240
- clearAllConfirmFirstState();
241
- setConfirmFirstStore(store);
242
- hydrateFromStore('sess-restart');
243
-
244
- expect(evaluateConfirmFirstGateSync('sess-restart', 'write', {}).action).toBe('block');
245
- expect(hasActiveDirective('sess-restart')).toBe(true);
246
- expect(isSessionApproved('sess-restart')).toBe(false);
247
- });
248
-
249
- it('no directive survives restart', () => {
250
- setConfirmFirstStore(store);
251
-
252
- setConfirmFirstStore(null);
253
- clearAllConfirmFirstState();
254
- setConfirmFirstStore(store);
255
- hydrateFromStore('sess-noexist');
256
-
257
- expect(evaluateConfirmFirstGateSync('sess-noexist', 'write', {}).action).toBe('skip');
258
- expect(hasActiveDirective('sess-noexist')).toBe(false);
259
- });
260
- });
261
-
262
- describe('Store degradation (ERR-002)', () => {
263
- afterEach(() => {
264
- setConfirmFirstStore(null);
265
- clearAllConfirmFirstState();
266
- });
267
-
268
- it('store write failure degrades gracefully to cache-only', () => {
269
- const throwingStore = {
270
- upsertDirective: () => { throw new Error('DB unavailable'); },
271
- upsertApproval: () => { throw new Error('DB unavailable'); },
272
- getState: () => null,
273
- deleteState: () => { throw new Error('DB unavailable'); },
274
- deleteAllState: () => { throw new Error('DB unavailable'); },
275
- pruneStaleRows: () => 0,
276
- getAllState: () => [],
277
- } as unknown as SqliteConfirmFirstStateStore;
278
-
279
- setConfirmFirstStore(throwingStore);
280
- setConfirmFirstDirective('sess-degrade', true, 'princ-123');
281
-
282
- expect(hasActiveDirective('sess-degrade')).toBe(true);
283
-
284
- setConfirmFirstApproval('sess-degrade');
285
- expect(isSessionApproved('sess-degrade')).toBe(true);
286
- expect(evaluateConfirmFirstGateSync('sess-degrade', 'write', {}).action).toBe('allow');
287
- });
288
- });
289
-
290
- describe('Stale directive cleared on reset (PRI-266)', () => {
291
- beforeEach(() => {
292
- clearAllConfirmFirstState();
293
- });
294
-
295
- it('resetConfirmFirst clears directive and approval from cache and store', () => {
296
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pd-cf-stale-'));
297
- try {
298
- const connection = new SqliteConnection(tmpDir);
299
- const store = new SqliteConfirmFirstStateStore(connection);
300
- setConfirmFirstStore(store);
301
-
302
- setConfirmFirstDirective('sess-stale', true, 'princ-stale');
303
- setConfirmFirstApproval('sess-stale');
304
-
305
- expect(hasActiveDirective('sess-stale')).toBe(true);
306
- expect(isSessionApproved('sess-stale')).toBe(true);
307
-
308
- resetConfirmFirst('sess-stale');
309
-
310
- expect(hasActiveDirective('sess-stale')).toBe(false);
311
- expect(isSessionApproved('sess-stale')).toBe(false);
312
- expect(evaluateConfirmFirstGateSync('sess-stale', 'write', {}).action).toBe('skip');
313
-
314
- connection.close();
315
- } finally {
316
- setConfirmFirstStore(null);
317
- fs.rmSync(tmpDir, { recursive: true, force: true });
318
- }
319
- });
320
-
321
- it('resetConfirmFirst without store clears in-memory cache only', () => {
322
- setConfirmFirstDirective('sess-nostore', true, 'princ-nostore');
323
- setConfirmFirstApproval('sess-nostore');
324
-
325
- expect(hasActiveDirective('sess-nostore')).toBe(true);
326
- expect(isSessionApproved('sess-nostore')).toBe(true);
327
-
328
- resetConfirmFirst('sess-nostore');
329
-
330
- expect(hasActiveDirective('sess-nostore')).toBe(false);
331
- expect(isSessionApproved('sess-nostore')).toBe(false);
332
- });
333
- });