principles-disciple 1.73.0 → 1.74.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/INSTALL.md +1 -3
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/src/core/event-log.ts +0 -9
- package/src/core/migration.ts +0 -1
- package/src/core/path-resolver.ts +0 -1
- package/src/core/paths.ts +0 -1
- package/src/hooks/gate-block-helper.ts +25 -20
- package/src/hooks/gate.ts +13 -61
- package/src/hooks/prompt.ts +1 -61
- package/src/types/event-types.ts +0 -1
- package/src/utils/io.ts +0 -22
- package/templates/langs/en/core/AGENTS.md +5 -5
- package/templates/langs/en/principles/THINKING_OS.md +3 -2
- package/templates/langs/en/skills/ai-sprint-orchestration/runtime/.gitignore +2 -2
- package/templates/langs/en/skills/evolve-task/SKILL.md +2 -2
- package/templates/langs/en/skills/pd-mentor/SKILL.md +1 -2
- package/templates/langs/zh/core/AGENTS.md +5 -5
- package/templates/langs/zh/principles/THINKING_OS.md +3 -2
- package/templates/langs/zh/skills/ai-sprint-orchestration/runtime/.gitignore +2 -2
- package/templates/langs/zh/skills/evolve-task/SKILL.md +2 -2
- package/templates/langs/zh/skills/pd-mentor/SKILL.md +1 -2
- package/tests/core/migration.test.ts +7 -7
- package/tests/core/path-resolver.test.ts +1 -1
- package/tests/core/paths-refactor.test.ts +0 -22
- package/tests/core/workspace-context.test.ts +2 -2
- package/tests/core-anti-growth.test.ts +0 -1
- package/tests/hooks/confirm-first-removal.test.ts +188 -0
- package/tests/hooks/gate-no-path-write-tool.test.ts +172 -0
- package/src/core/confirm-first-gate.ts +0 -255
- package/templates/langs/en/skills/plan-script/SKILL.md +0 -32
- package/templates/langs/zh/skills/plan-script/SKILL.md +0 -32
- package/tests/hooks/confirm-first-gate.test.ts +0 -333
package/INSTALL.md
CHANGED
|
@@ -236,9 +236,7 @@ After installation, enable the PLAN whitelist feature:
|
|
|
236
236
|
}
|
|
237
237
|
```
|
|
238
238
|
|
|
239
|
-
2.
|
|
240
|
-
|
|
241
|
-
3. Restart your agent session
|
|
239
|
+
2. Restart your agent session
|
|
242
240
|
|
|
243
241
|
Now even Stage 1 agents can edit files when a READY plan exists!
|
|
244
242
|
|
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
package/src/core/event-log.ts
CHANGED
|
@@ -28,7 +28,6 @@ import type {
|
|
|
28
28
|
RuleHostAutoCorrectProposedEventData,
|
|
29
29
|
RuleHostAutoCorrectAppliedEventData,
|
|
30
30
|
RuntimeV2PromptActivationsInjectedEventData,
|
|
31
|
-
RuntimeV2ConfirmFirstGateEventData,
|
|
32
31
|
} from '../types/event-types.js';
|
|
33
32
|
import { createEmptyDailyStats } from '../types/event-types.js';
|
|
34
33
|
import { atomicWriteFileSync } from '../utils/io.js';
|
|
@@ -210,14 +209,6 @@ export class EventLog {
|
|
|
210
209
|
this.record('runtime_v2_prompt_activations_injected', 'injected', data.sessionId, data);
|
|
211
210
|
}
|
|
212
211
|
|
|
213
|
-
recordConfirmFirstGateBlocked(data: RuntimeV2ConfirmFirstGateEventData): void {
|
|
214
|
-
this.record('runtime_v2_confirm_first_gate_blocked', 'blocked', data.sessionId, data);
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
recordConfirmFirstGateApproved(data: RuntimeV2ConfirmFirstGateEventData): void {
|
|
218
|
-
this.record('runtime_v2_confirm_first_gate_approved', 'approved', data.sessionId, data);
|
|
219
|
-
}
|
|
220
|
-
|
|
221
212
|
private record(
|
|
222
213
|
type: EventType,
|
|
223
214
|
category: EventCategory,
|
package/src/core/migration.ts
CHANGED
|
@@ -19,7 +19,6 @@ export function migrateDirectoryStructure(api: OpenClawPluginApi, workspaceDir:
|
|
|
19
19
|
{ legacy: path.join(legacyDocsDir, 'PRINCIPLES.md'), newKey: 'PRINCIPLES' },
|
|
20
20
|
{ legacy: path.join(legacyDocsDir, 'THINKING_OS.md'), newKey: 'THINKING_OS' },
|
|
21
21
|
{ legacy: path.join(legacyDocsDir, 'DECISION_POLICY.json'), newKey: 'DECISION_POLICY' },
|
|
22
|
-
{ legacy: path.join(legacyDocsDir, 'PLAN.md'), newKey: 'PLAN' },
|
|
23
22
|
{ legacy: path.join(legacyDocsDir, 'evolution_queue.json'), newKey: 'EVOLUTION_QUEUE' },
|
|
24
23
|
{ legacy: path.join(legacyDocsDir, '.pain_flag'), newKey: 'PAIN_FLAG' },
|
|
25
24
|
{ legacy: path.join(legacyDocsDir, 'SYSTEM_CAPABILITIES.json'), newKey: 'SYSTEM_CAPABILITIES' },
|
|
@@ -306,7 +306,6 @@ export class PathResolver {
|
|
|
306
306
|
'THINKING_OS': workspacePath.join(workspace, '.principles', 'THINKING_OS.md'),
|
|
307
307
|
'DECISION_POLICY': workspacePath.join(workspace, '.principles', 'DECISION_POLICY.json'),
|
|
308
308
|
'MODELS_DIR': workspacePath.join(workspace, '.principles', 'models'),
|
|
309
|
-
'PLAN': workspacePath.join(workspace, 'PLAN.md'),
|
|
310
309
|
'AGENT_SCORECARD': workspacePath.join(state, 'AGENT_SCORECARD.json'),
|
|
311
310
|
'PAIN_FLAG': workspacePath.join(state, '.pain_flag'),
|
|
312
311
|
'EVOLUTION_QUEUE': workspacePath.join(state, 'evolution_queue.json'),
|
package/src/core/paths.ts
CHANGED
|
@@ -149,34 +149,39 @@ export function recordGateBlockAndReturn(
|
|
|
149
149
|
}
|
|
150
150
|
}
|
|
151
151
|
|
|
152
|
-
// 6. Return consistent block result with operator guidance
|
|
152
|
+
// 6. Return consistent block result with contextual operator guidance
|
|
153
|
+
const blockMessage = buildContextualBlockMessage({ filePath, reason });
|
|
154
|
+
|
|
153
155
|
return {
|
|
154
156
|
block: true,
|
|
155
|
-
blockReason:
|
|
157
|
+
blockReason: blockMessage,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Build contextual block message based on block source.
|
|
163
|
+
* - rule-host: principle-based guidance
|
|
164
|
+
* - default/gate: generic security gate message
|
|
165
|
+
*/
|
|
166
|
+
function buildContextualBlockMessage({
|
|
167
|
+
filePath,
|
|
168
|
+
reason,
|
|
169
|
+
}: {
|
|
170
|
+
filePath: string;
|
|
171
|
+
reason: string;
|
|
172
|
+
}): string {
|
|
173
|
+
// rule-host or generic gate blocks
|
|
174
|
+
return `[Principles Disciple] Security Gate Blocked this action.
|
|
156
175
|
File: ${filePath}
|
|
157
176
|
Reason: ${reason}
|
|
158
177
|
|
|
159
178
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
160
179
|
📋 How to unblock this operation:
|
|
161
180
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
- Target Files: ${filePath}
|
|
167
|
-
- Steps: What you want to do (be specific)
|
|
168
|
-
- Metrics: How to verify success
|
|
169
|
-
- Active Mental Models: Select 2 relevant models from .principles/THINKING_OS.md
|
|
170
|
-
- Rollback: How to restore if it fails
|
|
171
|
-
|
|
172
|
-
3. After completing the plan, set STATUS: READY in PLAN.md
|
|
173
|
-
|
|
174
|
-
4. Retry the operation
|
|
175
|
-
|
|
176
|
-
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
177
|
-
This is a mandatory security gate. The operation was blocked because the modification exceeds the allowed threshold for your current evolution tier.
|
|
178
|
-
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`,
|
|
179
|
-
};
|
|
181
|
+
This action was blocked by a Rule Host principle.
|
|
182
|
+
If the blocked path is correct and safe, explain the reasoning to the owner
|
|
183
|
+
and ask for explicit confirmation to proceed.
|
|
184
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`;
|
|
180
185
|
}
|
|
181
186
|
|
|
182
187
|
/**
|
package/src/hooks/gate.ts
CHANGED
|
@@ -9,9 +9,7 @@
|
|
|
9
9
|
* 2. Rule Host: Dynamic principle-based evaluation (sole gate)
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import
|
|
13
|
-
import * as path from 'path';
|
|
14
|
-
import { normalizePath, planStatus } from '../utils/io.js';
|
|
12
|
+
import { normalizePath } from '../utils/io.js';
|
|
15
13
|
import { WorkspaceContext } from '../core/workspace-context.js';
|
|
16
14
|
import { recordGateBlockAndReturn } from './gate-block-helper.js';
|
|
17
15
|
import { RuleHost } from '../core/rule-host.js';
|
|
@@ -19,7 +17,6 @@ import type { RuleHostInput } from '@principles/core/runtime-v2';
|
|
|
19
17
|
import { validateCorrectionProposal, validateProposedPathBounds } from '@principles/core/runtime-v2';
|
|
20
18
|
import type { PluginHookBeforeToolCallEvent, PluginHookToolContext, PluginHookBeforeToolCallResult, PluginLogger } from '../openclaw-sdk.js';
|
|
21
19
|
import { AGENT_TOOLS, BASH_TOOLS_SET, WRITE_TOOLS } from '../constants/tools.js';
|
|
22
|
-
import { evaluateConfirmFirstGateSync } from '../core/confirm-first-gate.js';
|
|
23
20
|
import { getSession, hasRecentThinking } from '../core/session-tracker.js';
|
|
24
21
|
import { getEvolutionEngine } from '../core/evolution-engine.js';
|
|
25
22
|
import { EventLogService } from '../core/event-log.js';
|
|
@@ -42,42 +39,6 @@ export function handleBeforeToolCall(
|
|
|
42
39
|
|
|
43
40
|
const wctx = WorkspaceContext.fromHookContext(ctx);
|
|
44
41
|
|
|
45
|
-
// 1.5. Confirm-First Gate — runs BEFORE filePath resolution to catch apply_patch/no-path cases
|
|
46
|
-
try {
|
|
47
|
-
const cfResult = evaluateConfirmFirstGateSync(
|
|
48
|
-
ctx.sessionId,
|
|
49
|
-
event.toolName,
|
|
50
|
-
event.params,
|
|
51
|
-
);
|
|
52
|
-
|
|
53
|
-
if (cfResult.action === 'block') {
|
|
54
|
-
const eventLog = EventLogService.get(wctx.stateDir, logger as PluginLogger | undefined);
|
|
55
|
-
eventLog.recordConfirmFirstGateBlocked({
|
|
56
|
-
sessionId: ctx.sessionId ?? 'unknown',
|
|
57
|
-
workspaceDir: ctx.workspaceDir,
|
|
58
|
-
toolName: event.toolName,
|
|
59
|
-
reason: cfResult.reason ?? 'confirm_first_required',
|
|
60
|
-
principleId: cfResult.principleId ?? 'unknown',
|
|
61
|
-
nextAction: cfResult.nextAction ?? '',
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
// Use safe placeholder when filePath is unavailable (e.g., apply_patch with no path)
|
|
65
|
-
const safePath = (event.params?.file_path || event.params?.path || event.params?.file || event.params?.target)
|
|
66
|
-
?? `<tool:${event.toolName}>`;
|
|
67
|
-
|
|
68
|
-
return recordGateBlockAndReturn(wctx, {
|
|
69
|
-
filePath: typeof safePath === 'string' ? safePath : `<tool:${event.toolName}>`,
|
|
70
|
-
reason: cfResult.reason ?? 'confirm_first_required',
|
|
71
|
-
toolName: event.toolName,
|
|
72
|
-
sessionId: ctx.sessionId,
|
|
73
|
-
blockSource: 'confirm-first-gate',
|
|
74
|
-
}, logger);
|
|
75
|
-
}
|
|
76
|
-
} catch (cfErr) {
|
|
77
|
-
// ERR-002: fail loud — log but do not crash the gate
|
|
78
|
-
logger?.warn?.(`[PD:ConfirmFirst] Gate evaluation failed (non-blocking): ${String(cfErr)}`);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
42
|
// 2. Resolve the target file path
|
|
82
43
|
let filePath = event.params?.file_path || event.params?.path || event.params?.file || event.params?.target;
|
|
83
44
|
|
|
@@ -94,6 +55,12 @@ export function handleBeforeToolCall(
|
|
|
94
55
|
}
|
|
95
56
|
}
|
|
96
57
|
|
|
58
|
+
// Write tools without a file path must still go through RuleHost evaluation.
|
|
59
|
+
// Use a synthetic path so RuleHost can evaluate and potentially block.
|
|
60
|
+
if (!filePath && isWriteTool) {
|
|
61
|
+
filePath = `<tool:${event.toolName}>`;
|
|
62
|
+
}
|
|
63
|
+
|
|
97
64
|
if (typeof filePath !== 'string') return;
|
|
98
65
|
|
|
99
66
|
const relPath = normalizePath(filePath, ctx.workspaceDir);
|
|
@@ -109,8 +76,12 @@ export function handleBeforeToolCall(
|
|
|
109
76
|
},
|
|
110
77
|
workspace: {
|
|
111
78
|
isRiskPath: false, // Rule Host determines risk dynamically
|
|
112
|
-
|
|
113
|
-
|
|
79
|
+
// DEPRECATED (PRI-286): planStatus/hasPlanFile are legacy compatibility fields.
|
|
80
|
+
// Live PD no longer reads or manages PLAN.md state. These fields must not be
|
|
81
|
+
// used for new MVP behavior. Future "plan-first" enforcement must come from
|
|
82
|
+
// owner-approved RuleHost/code_tool_hook activation, not built-in state.
|
|
83
|
+
planStatus: 'NONE' as const,
|
|
84
|
+
hasPlanFile: false,
|
|
114
85
|
},
|
|
115
86
|
session: {
|
|
116
87
|
sessionId: ctx.sessionId,
|
|
@@ -377,25 +348,6 @@ function _extractParamsSummary(params: Record<string, unknown>): Record<string,
|
|
|
377
348
|
return summary;
|
|
378
349
|
}
|
|
379
350
|
|
|
380
|
-
function _getPlanStatus(workspaceDir: string): 'NONE' | 'DRAFT' | 'READY' | 'UNKNOWN' {
|
|
381
|
-
try {
|
|
382
|
-
const status = planStatus(workspaceDir);
|
|
383
|
-
if (status === 'READY') return 'READY';
|
|
384
|
-
if (status === 'DRAFT') return 'DRAFT';
|
|
385
|
-
if (status === '') return 'NONE';
|
|
386
|
-
return 'UNKNOWN';
|
|
387
|
-
} catch {
|
|
388
|
-
return 'UNKNOWN';
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
function _hasPlanFile(workspaceDir: string): boolean {
|
|
393
|
-
try {
|
|
394
|
-
return fs.existsSync(path.join(workspaceDir, 'PLAN.md'));
|
|
395
|
-
} catch {
|
|
396
|
-
return false;
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
351
|
|
|
400
352
|
function _getCurrentGfi(sessionId?: string): number {
|
|
401
353
|
if (!sessionId) return 0;
|
package/src/hooks/prompt.ts
CHANGED
|
@@ -8,11 +8,10 @@ import { WorkspaceContext } from '../core/workspace-context.js';
|
|
|
8
8
|
import type { ContextInjectionConfig} from '../types.js';
|
|
9
9
|
import { defaultContextConfig } from '../types.js';
|
|
10
10
|
import { classifyTask, type RoutingInput } from '../core/local-worker-routing.js';
|
|
11
|
-
import { detectApprovalMarker, setConfirmFirstApproval, setConfirmFirstDirective, hydrateFromStore, pruneStoreStaleRows, setConfirmFirstStore, resetConfirmFirst } from '../core/confirm-first-gate.js';
|
|
12
11
|
import { extractSummary, getHistoryVersions, parseWorkingMemorySection, workingMemoryToInjection, autoCompressFocus, safeReadCurrentFocus } from '../core/focus-history.js';
|
|
13
12
|
import { PathResolver } from '../core/path-resolver.js';
|
|
14
13
|
import { selectPrinciplesForInjection, DEFAULT_PRINCIPLE_BUDGET } from '../core/principle-injection.js';
|
|
15
|
-
import { getCachedMaskedPrincipleSet, WorkflowFunnelLoader, PiAiRuntimeAdapter, EmpathyObserver, AgentScheduler
|
|
14
|
+
import { getCachedMaskedPrincipleSet, WorkflowFunnelLoader, PiAiRuntimeAdapter, EmpathyObserver, AgentScheduler } from '@principles/core/runtime-v2';
|
|
16
15
|
import { truncateInjectionToBudget } from '@principles/core/prompt-builder';
|
|
17
16
|
import { PromptActivationReader, RUNTIME_V2_PRINCIPLE_BUDGET } from '../core/runtime-v2-prompt-activation-reader.js';
|
|
18
17
|
import {
|
|
@@ -77,7 +76,6 @@ function cachedReadFile(filePath: string): string {
|
|
|
77
76
|
// Module-level empathy state — shared across calls to avoid per-turn I/O
|
|
78
77
|
let _empathyTurnCounter = 0;
|
|
79
78
|
let _empathyKeywordCache: { store: ReturnType<typeof loadKeywordStore>; lang: string } | null = null;
|
|
80
|
-
let _confirmFirstHydrationCounter = 0;
|
|
81
79
|
|
|
82
80
|
/**
|
|
83
81
|
* Model configuration with primary model and optional fallback models
|
|
@@ -265,19 +263,6 @@ export function getDiagnosticianModel(api: PromptHookApi | null, logger?: Plugin
|
|
|
265
263
|
*/
|
|
266
264
|
|
|
267
265
|
|
|
268
|
-
function ensureConfirmFirstStore(workspaceDir: string): void {
|
|
269
|
-
if (!_confirmFirstStoreInitialized) {
|
|
270
|
-
try {
|
|
271
|
-
const connection = new SqliteConnection({ workspaceDir, readonly: false });
|
|
272
|
-
setConfirmFirstStore(new SqliteConfirmFirstStateStore(connection));
|
|
273
|
-
_confirmFirstStoreInitialized = true;
|
|
274
|
-
} catch (err) {
|
|
275
|
-
console.warn(`[PD:ConfirmFirst] Failed to initialize store: ${String(err)}`);
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
let _confirmFirstStoreInitialized = false;
|
|
280
|
-
|
|
281
266
|
export async function handleBeforePromptBuild(
|
|
282
267
|
event: PluginHookBeforePromptBuildEvent,
|
|
283
268
|
ctx: PluginHookAgentContext & { api?: PromptHookApi }
|
|
@@ -297,18 +282,6 @@ export async function handleBeforePromptBuild(
|
|
|
297
282
|
wctx.trajectory?.recordSession?.({ sessionId });
|
|
298
283
|
}
|
|
299
284
|
|
|
300
|
-
if (sessionId) {
|
|
301
|
-
ensureConfirmFirstStore(workspaceDir);
|
|
302
|
-
hydrateFromStore(sessionId);
|
|
303
|
-
_confirmFirstHydrationCounter++;
|
|
304
|
-
if (_confirmFirstHydrationCounter % 100 === 0) {
|
|
305
|
-
const pruned = pruneStoreStaleRows();
|
|
306
|
-
if (pruned > 0) {
|
|
307
|
-
logger?.info?.(`[PD:ConfirmFirst] Pruned ${pruned} stale rows from confirm_first_state`);
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
|
|
312
285
|
if (sessionId && trigger === 'user' && Array.isArray(event.messages) && event.messages.length > 0) {
|
|
313
286
|
const latestUserIndex = [...event.messages]
|
|
314
287
|
.map((message, index) => ({ message, index }))
|
|
@@ -318,25 +291,6 @@ export async function handleBeforePromptBuild(
|
|
|
318
291
|
if (latestUserIndex) {
|
|
319
292
|
const userText = getTextContent(latestUserIndex.message);
|
|
320
293
|
|
|
321
|
-
// ── Confirm-first approval detection ──
|
|
322
|
-
// If user sends approval language, mark session as approved for confirm-first gate
|
|
323
|
-
if (sessionId && detectApprovalMarker(userText)) {
|
|
324
|
-
setConfirmFirstApproval(sessionId);
|
|
325
|
-
// P2: Emit approval telemetry for observability (ERR-002)
|
|
326
|
-
try {
|
|
327
|
-
wctx.eventLog.recordConfirmFirstGateApproved({
|
|
328
|
-
sessionId,
|
|
329
|
-
workspaceDir: wctx.workspaceDir,
|
|
330
|
-
toolName: '(approval)',
|
|
331
|
-
reason: 'user_approval_detected',
|
|
332
|
-
principleId: 'confirm-first',
|
|
333
|
-
nextAction: 'mutating tools now permitted',
|
|
334
|
-
});
|
|
335
|
-
} catch (logErr) {
|
|
336
|
-
logger?.warn?.(`[PD:ConfirmFirst] Failed to emit approval event: ${String(logErr)}`);
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
|
|
340
294
|
// Use CorrectionCueLearner for detection — supports learned keywords, not just hardcoded list
|
|
341
295
|
let correctionCue: string | null = null;
|
|
342
296
|
try {
|
|
@@ -990,22 +944,8 @@ ${heartbeatChecklist}
|
|
|
990
944
|
} catch (logErr) {
|
|
991
945
|
logger?.warn?.(`[PD:RuntimeV2] Failed to emit activation observability event: ${String(logErr)}`);
|
|
992
946
|
}
|
|
993
|
-
|
|
994
|
-
// ── Set confirm-first directive state for gate enforcement ──
|
|
995
|
-
if (sessionId) {
|
|
996
|
-
const cfPrinciple = dedupedV2.find(
|
|
997
|
-
(p) =>
|
|
998
|
-
p.principleId === 'princ-mvp-acceptance-confirm-first' ||
|
|
999
|
-
(p.text.toLowerCase().includes('confirm requirements') &&
|
|
1000
|
-
p.text.toLowerCase().includes('owner approval')),
|
|
1001
|
-
);
|
|
1002
|
-
setConfirmFirstDirective(sessionId, !!cfPrinciple, cfPrinciple?.principleId);
|
|
1003
|
-
}
|
|
1004
947
|
} catch (e) {
|
|
1005
948
|
logger?.warn?.(`[PD:RuntimeV2] Failed to read Runtime V2 prompt activations: ${String(e)}`);
|
|
1006
|
-
if (sessionId) {
|
|
1007
|
-
resetConfirmFirst(sessionId);
|
|
1008
|
-
}
|
|
1009
949
|
}
|
|
1010
950
|
|
|
1011
951
|
// Build appendSystemContext with recency effect
|
package/src/types/event-types.ts
CHANGED
package/src/utils/io.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import * as path from 'path';
|
|
2
2
|
import * as fs from 'fs';
|
|
3
|
-
import { resolvePdPath } from '../core/paths.js';
|
|
4
3
|
|
|
5
4
|
/**
|
|
6
5
|
* Atomic file write — write to temp then rename to prevent partial writes on crash.
|
|
@@ -148,27 +147,6 @@ export function serializeKvLines(data: Record<string, any>): string {
|
|
|
148
147
|
return lines.join('\n');
|
|
149
148
|
}
|
|
150
149
|
|
|
151
|
-
export function planStatus(projectDir: string): string {
|
|
152
|
-
const planPath = resolvePdPath(projectDir, 'PLAN');
|
|
153
|
-
try {
|
|
154
|
-
if (!fs.existsSync(planPath)) return '';
|
|
155
|
-
const content = fs.readFileSync(planPath, 'utf8');
|
|
156
|
-
const lines = content.split('\n');
|
|
157
|
-
for (const line of lines) {
|
|
158
|
-
if (line.startsWith('STATUS:')) {
|
|
159
|
-
const parts = line.split(':');
|
|
160
|
-
if (parts.length > 1) {
|
|
161
|
-
return parts[1].trim().split(/\s+/)[0] || '';
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
/* eslint-disable @typescript-eslint/no-unused-vars -- Reason: Error is intentionally ignored for graceful degradation */
|
|
166
|
-
} catch (_e) {
|
|
167
|
-
// Ignore read errors
|
|
168
|
-
}
|
|
169
|
-
return '';
|
|
170
|
-
}
|
|
171
|
-
|
|
172
150
|
/**
|
|
173
151
|
* Normalize command arguments from PluginCommandContext.args.
|
|
174
152
|
* Handles string | string[] | undefined union by joining arrays with spaces.
|
|
@@ -17,7 +17,6 @@ As Principles Disciple, you must distinguish between two physical spaces:
|
|
|
17
17
|
Make decisions based on relative paths in the **Project Battlefield**:
|
|
18
18
|
|
|
19
19
|
- **Strategic Focus**: `./memory/STRATEGY.md`
|
|
20
|
-
- **Physical Plan**: `./PLAN.md`
|
|
21
20
|
- **Pain Signal**: Runtime V2 `PainSignalBridge` (`pd pain record` for manual trigger; `.state/.pain_flag` is legacy compatibility only)
|
|
22
21
|
- **System Capabilities**: `./.state/SYSTEM_CAPABILITIES.json`
|
|
23
22
|
|
|
@@ -166,12 +165,13 @@ On platforms that support reactions (Discord, Slack), use emoji reactions natura
|
|
|
166
165
|
You default to architect mode.
|
|
167
166
|
|
|
168
167
|
- **L1 (Direct Execution)**: Single-file tweaks, doc maintenance → do it directly
|
|
169
|
-
- **L2 (Delegation Protocol)**: Major changes →
|
|
168
|
+
- **L2 (Delegation Protocol)**: Major changes → recommended to describe the plan and get owner confirmation before executing
|
|
170
169
|
|
|
171
|
-
###
|
|
170
|
+
### Planning Guidance
|
|
172
171
|
|
|
173
|
-
-
|
|
174
|
-
-
|
|
172
|
+
- For complex tasks, consider drafting a plan document and getting owner approval before making large changes
|
|
173
|
+
- This is a behavioral suggestion, not a built-in gate — PD does not enforce plan-before-action by default
|
|
174
|
+
- If an owner-approved RuleHost rule enforces planning behavior, that rule takes effect automatically
|
|
175
175
|
- **Prevent pollution**: Never write execution details back to strategic documents
|
|
176
176
|
|
|
177
177
|
---
|
|
@@ -39,8 +39,9 @@ LLMs are highly sensitive to XML tags; this structure is designed to boost instr
|
|
|
39
39
|
<!-- 执行与物理限制 (Execution & Physical Constraints) -->
|
|
40
40
|
<directive id="T-05" name="PHYSICAL_DEFENSE_AND_ORCHESTRATION">
|
|
41
41
|
<trigger>When asked to perform a major refactoring, multi-file change (>2 files), or an architectural shift.</trigger>
|
|
42
|
-
<
|
|
43
|
-
<
|
|
42
|
+
<should>For complex changes, describe your plan and get owner confirmation before executing.</should>
|
|
43
|
+
<must>Limit your blast radius. After any code change, you MUST run canary tests (e.g., `npm test`, linters) to verify integrity.</must>
|
|
44
|
+
<forbidden>Executing large-scale unstructured changes directly, or skipping post-modification validation.</forbidden>
|
|
44
45
|
</directive>
|
|
45
46
|
|
|
46
47
|
<directive id="T-06" name="OCCAMS_RAZOR_MVC">
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
*
|
|
2
|
-
!.gitignore
|
|
1
|
+
*
|
|
2
|
+
!.gitignore
|
|
@@ -51,7 +51,7 @@ Output:
|
|
|
51
51
|
|
|
52
52
|
## Step 6: Delegate Planner (Movie Script Plan)
|
|
53
53
|
- Planner outputs Plan (steps/commands/metrics/rollback).
|
|
54
|
-
- Write plan to
|
|
54
|
+
- Write plan to a planning document for owner review.
|
|
55
55
|
- **Task Sync**:
|
|
56
56
|
- If `CLAUDE_CODE_TASK_LIST_ID` is set, you must convert the Plan's core steps to Native Tasks (via natural language command "Add task..." or related tools).
|
|
57
57
|
- If not set and in interactive mode, prompt user: "Recommend running `export CLAUDE_CODE_TASK_LIST_ID=task-$(date +%s)` to enable persistent task tracking."
|
|
@@ -59,7 +59,7 @@ Output:
|
|
|
59
59
|
- **Performance Evaluation**: After task completion, write to `.state/.verdict.json`. Format follows `@.principles/schemas/agent_verdict_schema.json`.
|
|
60
60
|
|
|
61
61
|
## Step 7: Delegate Implementer (Execution)
|
|
62
|
-
- Implementer
|
|
62
|
+
- Implementer executes according to the plan from Step 6. Any deviation must first be approved by updating the plan.
|
|
63
63
|
- **Performance Evaluation**: After task completion, write to `.state/.verdict.json` based on verification results. Format follows `@.principles/schemas/agent_verdict_schema.json`.
|
|
64
64
|
|
|
65
65
|
## Step 8: Delegate Reviewer (Review)
|
|
@@ -185,7 +185,7 @@ For complex scenarios, combine multiple skills:
|
|
|
185
185
|
|
|
186
186
|
| Scenario | Combined Flow |
|
|
187
187
|
|----------|---------------|
|
|
188
|
-
| Major refactor | `/pd-evolve` → `
|
|
188
|
+
| Major refactor | `/pd-evolve` → `deductive-audit` → execute |
|
|
189
189
|
| System optimization | `/pd-status` → `evolve-system` → `root-cause` |
|
|
190
190
|
| Project review | `/pd-daily` → `/pd-okr` → `reflection-log` |
|
|
191
191
|
|
|
@@ -196,7 +196,6 @@ These skills are usually called automatically by the system, but advanced users
|
|
|
196
196
|
- `triage` - Issue triage
|
|
197
197
|
- `root-cause` - Root cause analysis
|
|
198
198
|
- `deductive-audit` - Deductive audit
|
|
199
|
-
- `plan-script` - Plan orchestration
|
|
200
199
|
- `reflection` - Metacognitive reflection
|
|
201
200
|
- `reflection-log` - Reflection logging
|
|
202
201
|
|
|
@@ -17,7 +17,6 @@
|
|
|
17
17
|
基于**项目战场**中的相对路径进行决策:
|
|
18
18
|
|
|
19
19
|
- **项目最高战略**: `./memory/STRATEGY.md`
|
|
20
|
-
- **项目物理计划**: `./PLAN.md`
|
|
21
20
|
- **痛觉反射信号**: Runtime V2 `PainSignalBridge`(手动触发使用 `pd pain record`;`.state/.pain_flag` 仅为 legacy compatibility)
|
|
22
21
|
- **系统能力快照**: `./.state/SYSTEM_CAPABILITIES.json`
|
|
23
22
|
|
|
@@ -158,12 +157,13 @@
|
|
|
158
157
|
你默认处于架构师模式。
|
|
159
158
|
|
|
160
159
|
- **L1 (直接执行)**:单文件微调、文档维护 → 直接操作
|
|
161
|
-
- **L2 (委派协议)**:重大变更 →
|
|
160
|
+
- **L2 (委派协议)**:重大变更 → 建议先描述计划并获得 owner 确认后再执行
|
|
162
161
|
|
|
163
|
-
###
|
|
162
|
+
### 计划引导 (Planning Guidance)
|
|
164
163
|
|
|
165
|
-
-
|
|
166
|
-
-
|
|
164
|
+
- 对复杂任务,建议先起草计划文档并获得 owner 批准后再做大幅修改
|
|
165
|
+
- 这是行为建议,不是内置门禁 — PD 默认不强制"先计划后执行"
|
|
166
|
+
- 若 owner 批准的 RuleHost 规则强制了计划行为,该规则会自动生效
|
|
167
167
|
- **防止污染**:禁止将执行层细节写回战略文档
|
|
168
168
|
|
|
169
169
|
---
|
|
@@ -39,8 +39,9 @@
|
|
|
39
39
|
<!-- 执行与物理限制 (Execution & Physical Constraints) -->
|
|
40
40
|
<directive id="T-05" name="PHYSICAL_DEFENSE_AND_ORCHESTRATION">
|
|
41
41
|
<trigger>当被要求执行大型重构、多文件修改(>2 个文件)或架构变更时。</trigger>
|
|
42
|
-
<
|
|
43
|
-
<
|
|
42
|
+
<should>对复杂变更,先描述计划并获得 owner 确认后再执行。</should>
|
|
43
|
+
<must>限制爆炸半径。在修改任何代码后,必须运行金丝雀测试(例如 `npm test`、linters)以验证完整性。</must>
|
|
44
|
+
<forbidden>直接执行大规模非结构化变更,或跳过修改后的验证环节。</forbidden>
|
|
44
45
|
</directive>
|
|
45
46
|
|
|
46
47
|
<directive id="T-06" name="OCCAMS_RAZOR_MVC">
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
*
|
|
2
|
-
!.gitignore
|
|
1
|
+
*
|
|
2
|
+
!.gitignore
|
|
@@ -51,7 +51,7 @@ disable-model-invocation: true
|
|
|
51
51
|
|
|
52
52
|
## Step 6: 委派 Planner(电影剧本计划)
|
|
53
53
|
- Planner 输出 Plan(步骤/命令/指标/回滚)。
|
|
54
|
-
-
|
|
54
|
+
- 将计划写入计划文档供 owner 审阅。
|
|
55
55
|
- **任务同步 (Task Sync)**:
|
|
56
56
|
- 如果 `CLAUDE_CODE_TASK_LIST_ID` 已设置,你必须将上述 Plan 的核心步骤直接转化为 Native Tasks(通过自然语言指令"Add task..."或相关工具)。
|
|
57
57
|
- 如果未设置且为交互模式,提示用户:"建议运行 `export CLAUDE_CODE_TASK_LIST_ID=task-$(date +%s)` 以启用持久化任务追踪。"
|
|
@@ -59,7 +59,7 @@ disable-model-invocation: true
|
|
|
59
59
|
- **绩效评估**: 任务完成后,写入 `.state/.verdict.json`。格式遵循 `@.principles/schemas/agent_verdict_schema.json`。
|
|
60
60
|
|
|
61
61
|
## Step 7: 委派 Implementer(执行)
|
|
62
|
-
- Implementer
|
|
62
|
+
- Implementer 按照 Step 6 的计划执行。任何偏离必须先更新计划并获得确认。
|
|
63
63
|
- **绩效评估**: 任务完成后,根据验证结果写入 `.state/.verdict.json`。格式遵循 `@.principles/schemas/agent_verdict_schema.json`。
|
|
64
64
|
|
|
65
65
|
## Step 8: 委派 Reviewer(审查)
|
|
@@ -185,7 +185,7 @@ disable-model-invocation: true
|
|
|
185
185
|
|
|
186
186
|
| 场景 | 组合流程 |
|
|
187
187
|
|------|----------|
|
|
188
|
-
| 大型重构 | `/pd-evolve` → `
|
|
188
|
+
| 大型重构 | `/pd-evolve` → `deductive-audit` → 执行 |
|
|
189
189
|
| 系统优化 | `/pd-status` → `evolve-system` → `root-cause` |
|
|
190
190
|
| 项目复盘 | `/pd-daily` → `/pd-okr` → `reflection-log` |
|
|
191
191
|
|
|
@@ -196,7 +196,6 @@ disable-model-invocation: true
|
|
|
196
196
|
- `triage` - 问题分诊
|
|
197
197
|
- `root-cause` - 根因分析
|
|
198
198
|
- `deductive-audit` - 演绎审计
|
|
199
|
-
- `plan-script` - 计划编排
|
|
200
199
|
- `reflection` - 元认知反思
|
|
201
200
|
- `reflection-log` - 反思落盘
|
|
202
201
|
|
|
@@ -18,25 +18,25 @@ describe('Directory Structure Migration', () => {
|
|
|
18
18
|
vi.clearAllMocks();
|
|
19
19
|
});
|
|
20
20
|
|
|
21
|
-
it('should move
|
|
22
|
-
const
|
|
23
|
-
const
|
|
21
|
+
it('should move THINKING_OS.md from docs/ to .principles/', () => {
|
|
22
|
+
const legacyThinkingOs = path.join(workspaceDir, 'docs', 'THINKING_OS.md');
|
|
23
|
+
const newThinkingOs = '/mock/workspace/.principles/THINKING_OS.md';
|
|
24
24
|
|
|
25
25
|
vi.mocked(fs.existsSync).mockImplementation((p) => {
|
|
26
26
|
const pathStr = p.toString();
|
|
27
27
|
if (pathStr === path.join(workspaceDir, 'docs')) return true;
|
|
28
|
-
if (pathStr ===
|
|
28
|
+
if (pathStr === legacyThinkingOs) return true;
|
|
29
29
|
// Destination directories don't exist yet
|
|
30
30
|
if (pathStr === path.join(workspaceDir, '.principles')) return false;
|
|
31
31
|
// Destination files don't exist yet
|
|
32
|
-
if (pathStr ===
|
|
32
|
+
if (pathStr === newThinkingOs) return false;
|
|
33
33
|
return false;
|
|
34
34
|
});
|
|
35
35
|
|
|
36
36
|
migrateDirectoryStructure(mockApi, workspaceDir);
|
|
37
37
|
|
|
38
|
-
// Verify it moved
|
|
39
|
-
expect(fs.renameSync).toHaveBeenCalledWith(
|
|
38
|
+
// Verify it moved THINKING_OS.md to .principles/
|
|
39
|
+
expect(fs.renameSync).toHaveBeenCalledWith(legacyThinkingOs, newThinkingOs);
|
|
40
40
|
|
|
41
41
|
expect(mockLogger.info).toHaveBeenCalledWith(expect.stringContaining('Successfully migrated'));
|
|
42
42
|
});
|
|
@@ -20,7 +20,7 @@ describe('PathResolver', () => {
|
|
|
20
20
|
const { PathResolver } = await import('../../src/core/path-resolver.js');
|
|
21
21
|
const resolver = new PathResolver({ workspaceDir: '/test/workspace' });
|
|
22
22
|
|
|
23
|
-
const requiredKeys = ['PROFILE', '
|
|
23
|
+
const requiredKeys = ['PROFILE', 'AGENT_SCORECARD', 'PAIN_FLAG', 'EVOLUTION_QUEUE', 'THINKING_OS', 'THINKING_OS_USAGE', 'THINKING_OS_CANDIDATES'];
|
|
24
24
|
|
|
25
25
|
for (const key of requiredKeys) {
|
|
26
26
|
expect(() => resolver.resolve(key)).not.toThrow();
|