principles-disciple 1.8.2 → 1.9.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 (73) hide show
  1. package/openclaw.plugin.json +4 -4
  2. package/package.json +1 -1
  3. package/src/core/pain-context-extractor.ts +286 -0
  4. package/src/core/pain.ts +83 -1
  5. package/src/hooks/lifecycle.ts +7 -6
  6. package/src/hooks/llm.ts +7 -6
  7. package/src/hooks/pain.ts +5 -6
  8. package/src/hooks/subagent.ts +5 -6
  9. package/src/service/evolution-worker.ts +59 -2
  10. package/templates/langs/en/skills/ai-sprint-orchestration/EXAMPLES.md +63 -0
  11. package/templates/langs/en/skills/ai-sprint-orchestration/REFERENCE.md +136 -0
  12. package/templates/langs/en/skills/ai-sprint-orchestration/SKILL.md +67 -0
  13. package/templates/langs/en/skills/ai-sprint-orchestration/references/agent-registry.json +214 -0
  14. package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/bugfix-complex-template.json +107 -0
  15. package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/feature-complex-template.json +107 -0
  16. package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal-verify.json +105 -0
  17. package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal.json +108 -0
  18. package/templates/langs/en/skills/ai-sprint-orchestration/references/workflow-v1-acceptance-checklist.md +58 -0
  19. package/templates/langs/en/skills/ai-sprint-orchestration/references/workflow-v1.4-work-unit-handoff.md +190 -0
  20. package/templates/langs/en/skills/ai-sprint-orchestration/runtime/.gitignore +2 -0
  21. package/templates/langs/en/skills/ai-sprint-orchestration/scripts/lib/archive.mjs +310 -0
  22. package/templates/langs/en/skills/ai-sprint-orchestration/scripts/lib/contract-enforcement.mjs +683 -0
  23. package/templates/langs/en/skills/ai-sprint-orchestration/scripts/lib/decision.mjs +604 -0
  24. package/templates/langs/en/skills/ai-sprint-orchestration/scripts/lib/state-store.mjs +32 -0
  25. package/templates/langs/en/skills/ai-sprint-orchestration/scripts/lib/task-specs.mjs +707 -0
  26. package/templates/langs/en/skills/ai-sprint-orchestration/scripts/run.mjs +3419 -0
  27. package/templates/langs/en/skills/pd-auditor/SKILL.md +61 -0
  28. package/templates/langs/en/skills/pd-daily/SKILL.md +1 -1
  29. package/templates/langs/en/skills/pd-diagnostician/SKILL.md +370 -0
  30. package/templates/langs/en/skills/pd-explorer/SKILL.md +65 -0
  31. package/templates/langs/en/skills/pd-grooming/SKILL.md +1 -1
  32. package/templates/langs/en/skills/pd-implementer/SKILL.md +68 -0
  33. package/templates/langs/en/skills/pd-mentor/SKILL.md +1 -1
  34. package/templates/langs/en/skills/pd-pain-signal/SKILL.md +37 -0
  35. package/templates/langs/en/skills/pd-planner/SKILL.md +65 -0
  36. package/templates/langs/zh/core/PRINCIPLES.md +7 -0
  37. package/templates/langs/zh/skills/ai-sprint-orchestration/EXAMPLES.md +63 -0
  38. package/templates/langs/zh/skills/ai-sprint-orchestration/REFERENCE.md +136 -0
  39. package/templates/langs/zh/skills/ai-sprint-orchestration/SKILL.md +67 -0
  40. package/templates/langs/zh/skills/ai-sprint-orchestration/references/agent-registry.json +214 -0
  41. package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/bugfix-complex-template.json +107 -0
  42. package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/feature-complex-template.json +107 -0
  43. package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal-verify.json +105 -0
  44. package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal.json +108 -0
  45. package/templates/langs/zh/skills/ai-sprint-orchestration/references/workflow-v1-acceptance-checklist.md +58 -0
  46. package/templates/langs/zh/skills/ai-sprint-orchestration/references/workflow-v1.4-work-unit-handoff.md +190 -0
  47. package/templates/langs/zh/skills/ai-sprint-orchestration/runtime/.gitignore +2 -0
  48. package/templates/langs/zh/skills/ai-sprint-orchestration/scripts/lib/archive.mjs +310 -0
  49. package/templates/langs/zh/skills/ai-sprint-orchestration/scripts/lib/contract-enforcement.mjs +683 -0
  50. package/templates/langs/zh/skills/ai-sprint-orchestration/scripts/lib/decision.mjs +604 -0
  51. package/templates/langs/zh/skills/ai-sprint-orchestration/scripts/lib/state-store.mjs +32 -0
  52. package/templates/langs/zh/skills/ai-sprint-orchestration/scripts/lib/task-specs.mjs +707 -0
  53. package/templates/langs/zh/skills/ai-sprint-orchestration/scripts/run.mjs +3419 -0
  54. package/templates/langs/zh/skills/ai-sprint-orchestration/test/archive.test.mjs +230 -0
  55. package/templates/langs/zh/skills/ai-sprint-orchestration/test/contract-enforcement.test.mjs +672 -0
  56. package/templates/langs/zh/skills/ai-sprint-orchestration/test/decision.test.mjs +1321 -0
  57. package/templates/langs/zh/skills/ai-sprint-orchestration/test/run.test.mjs +1419 -0
  58. package/templates/langs/zh/skills/pd-auditor/SKILL.md +1 -1
  59. package/templates/langs/zh/skills/pd-daily/SKILL.md +1 -1
  60. package/templates/langs/zh/skills/pd-diagnostician/SKILL.md +37 -23
  61. package/templates/langs/zh/skills/pd-explorer/SKILL.md +1 -1
  62. package/templates/langs/zh/skills/pd-grooming/SKILL.md +1 -1
  63. package/templates/langs/zh/skills/pd-implementer/SKILL.md +1 -1
  64. package/templates/langs/zh/skills/pd-mentor/SKILL.md +1 -1
  65. package/templates/langs/zh/skills/pd-pain-signal/SKILL.md +37 -0
  66. package/templates/langs/zh/skills/pd-planner/SKILL.md +1 -1
  67. package/tests/core/pain-context-extractor.test.ts +278 -0
  68. package/tests/core/pain.test.ts +100 -1
  69. package/tests/hooks/pain.test.ts +1 -1
  70. package/templates/langs/en/skills/pain/SKILL.md +0 -19
  71. package/templates/langs/zh/skills/pain/SKILL.md +0 -19
  72. package/templates/langs/zh/skills/pd-reporter/SKILL.md +0 -78
  73. package/templates/langs/zh/skills/pd-reviewer/SKILL.md +0 -66
@@ -0,0 +1,707 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import os from 'os';
4
+ import { fileURLToPath } from 'url';
5
+
6
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
7
+ const packageRoot = path.resolve(__dirname, '..', '..');
8
+ const REFERENCES_DIR = path.join(packageRoot, 'references');
9
+ const SPECS_DIR = path.join(REFERENCES_DIR, 'specs');
10
+ const REGISTRY_PATH = path.join(REFERENCES_DIR, 'agent-registry.json');
11
+ const DEFAULT_RUNTIME_ROOT = path.join(packageRoot, 'runtime');
12
+
13
+ function resolveSkillPlaceholder(str) {
14
+ if (typeof str !== 'string') return str;
15
+ const runtimeRoot = process.env.AI_SPRINT_RUNTIME_ROOT
16
+ ? path.resolve(process.env.AI_SPRINT_RUNTIME_ROOT)
17
+ : DEFAULT_RUNTIME_ROOT;
18
+ return str
19
+ .replaceAll('__SKILL_PACKAGE_ROOT__', packageRoot)
20
+ .replaceAll('__SKILL_REFERENCES_ROOT__', REFERENCES_DIR)
21
+ .replaceAll('__SKILL_RUNTIME_ROOT__', runtimeRoot);
22
+ }
23
+
24
+ /** Load the agent registry, or return null if unavailable. */
25
+ function loadAgentRegistry() {
26
+ try {
27
+ if (!fs.existsSync(REGISTRY_PATH)) return null;
28
+ return JSON.parse(fs.readFileSync(REGISTRY_PATH, 'utf8'));
29
+ } catch {
30
+ return null;
31
+ }
32
+ }
33
+
34
+ /** Validate spec agent/model entries against the registry. Throws on mismatch. */
35
+ function validateSpecAgents(spec) {
36
+ const registry = loadAgentRegistry();
37
+ if (!registry?.agents) return; // Registry unavailable — skip validation
38
+
39
+ const roles = [
40
+ { name: 'producer', config: spec.producer },
41
+ { name: 'reviewerA', config: spec.reviewerA },
42
+ { name: 'reviewerB', config: spec.reviewerB },
43
+ ];
44
+ if (spec.escalationReviewer) {
45
+ roles.push({ name: 'escalationReviewer', config: spec.escalationReviewer });
46
+ }
47
+
48
+ for (const { name, config } of roles) {
49
+ if (!config) continue;
50
+ const { agent, model } = config;
51
+ if (!agent) throw new Error(`Spec role '${name}' is missing 'agent' field.`);
52
+ if (!model) throw new Error(`Spec role '${name}' is missing 'model' field.`);
53
+
54
+ const agentEntry = registry.agents[agent];
55
+ if (!agentEntry) {
56
+ const available = Object.keys(registry.agents).join(', ');
57
+ throw new Error(`Spec role '${name}' uses unknown agent '${agent}'. Available: ${available}.`);
58
+ }
59
+
60
+ const modelEntry = agentEntry.models[model];
61
+ if (!modelEntry) {
62
+ const available = Object.keys(agentEntry.models).join(', ');
63
+ throw new Error(`Spec role '${name}' uses unknown model '${model}' for agent '${agent}'. Available: ${available}.`);
64
+ }
65
+ }
66
+ }
67
+
68
+ function isPlaceholderValue(value) {
69
+ return typeof value === 'string' && /<[^>]+>|TBD|REPLACE_ME/i.test(value);
70
+ }
71
+
72
+ function normalizeStringList(value) {
73
+ return Array.isArray(value)
74
+ ? value.map((item) => String(item ?? '').trim()).filter(Boolean)
75
+ : [];
76
+ }
77
+
78
+ function validateTaskContract(spec) {
79
+ if (spec.requiresTaskContract !== true) return;
80
+ const contract = spec.taskContract ?? {};
81
+ const errors = [];
82
+
83
+ if (!contract.goal || isPlaceholderValue(contract.goal)) errors.push('taskContract.goal');
84
+ if (!Array.isArray(contract.inScope) || contract.inScope.length === 0 || contract.inScope.some(isPlaceholderValue)) errors.push('taskContract.inScope');
85
+ if (!Array.isArray(contract.outOfScope) || contract.outOfScope.length === 0 || contract.outOfScope.some(isPlaceholderValue)) errors.push('taskContract.outOfScope');
86
+ if (!Array.isArray(contract.validationCommands) || contract.validationCommands.length === 0 || contract.validationCommands.some(isPlaceholderValue)) errors.push('taskContract.validationCommands');
87
+ if (!Array.isArray(contract.expectedArtifacts) || contract.expectedArtifacts.length === 0 || contract.expectedArtifacts.some(isPlaceholderValue)) errors.push('taskContract.expectedArtifacts');
88
+
89
+ if (errors.length > 0) {
90
+ throw new Error(`Spec '${spec.id}' is missing the minimum task contract. Fill these fields before running: ${errors.join(', ')}.`);
91
+ }
92
+ }
93
+
94
+ function validateWorkUnitField(stage, unit, key, errors) {
95
+ const value = unit?.[key];
96
+ if (typeof value === 'string') {
97
+ if (!value.trim() || isPlaceholderValue(value)) {
98
+ errors.push(`workUnits.${stage}.${unit.workUnitId ?? '<missing-id>'}.${key}`);
99
+ }
100
+ return;
101
+ }
102
+
103
+ const list = normalizeStringList(value);
104
+ if (list.length === 0 || list.some(isPlaceholderValue)) {
105
+ errors.push(`workUnits.${stage}.${unit.workUnitId ?? '<missing-id>'}.${key}`);
106
+ }
107
+ }
108
+
109
+ function validateWorkUnits(spec) {
110
+ const stageMap = spec.workUnits;
111
+ if (!stageMap || typeof stageMap !== 'object' || Array.isArray(stageMap)) return;
112
+
113
+ const errors = [];
114
+ for (const [stage, units] of Object.entries(stageMap)) {
115
+ if (!Array.isArray(units) || units.length === 0) {
116
+ errors.push(`workUnits.${stage}`);
117
+ continue;
118
+ }
119
+ for (const unit of units) {
120
+ if (!unit || typeof unit !== 'object') {
121
+ errors.push(`workUnits.${stage}[]`);
122
+ continue;
123
+ }
124
+ validateWorkUnitField(stage, unit, 'workUnitId', errors);
125
+ validateWorkUnitField(stage, unit, 'workUnitGoal', errors);
126
+ validateWorkUnitField(stage, unit, 'allowedFiles', errors);
127
+ validateWorkUnitField(stage, unit, 'unitChecks', errors);
128
+ validateWorkUnitField(stage, unit, 'unitDeliverables', errors);
129
+ validateWorkUnitField(stage, unit, 'unitSummary', errors);
130
+ validateWorkUnitField(stage, unit, 'carryForwardSummary', errors);
131
+ }
132
+ }
133
+
134
+ if (errors.length > 0) {
135
+ throw new Error(`Spec '${spec.id}' is missing the minimum work-unit contract. Fill these fields before running: ${errors.join(', ')}.`);
136
+ }
137
+ }
138
+
139
+ export function getStageWorkUnits(spec, stage) {
140
+ const units = spec?.workUnits?.[stage];
141
+ if (!Array.isArray(units)) return [];
142
+ return units.map((unit, index) => ({
143
+ workUnitId: String(unit.workUnitId ?? '').trim(),
144
+ workUnitGoal: String(unit.workUnitGoal ?? '').trim(),
145
+ allowedFiles: normalizeStringList(unit.allowedFiles),
146
+ unitChecks: normalizeStringList(unit.unitChecks),
147
+ unitDeliverables: normalizeStringList(unit.unitDeliverables),
148
+ unitSummary: String(unit.unitSummary ?? '').trim(),
149
+ carryForwardSummary: String(unit.carryForwardSummary ?? '').trim(),
150
+ stage,
151
+ order: index,
152
+ }));
153
+ }
154
+
155
+ export function getActiveWorkUnit(spec, stage, options = {}) {
156
+ const { workUnitIndex = 0 } = options;
157
+ const units = getStageWorkUnits(spec, stage);
158
+ if (units.length === 0) return null;
159
+ const safeIndex = Math.max(0, Math.min(workUnitIndex, units.length - 1));
160
+ return units[safeIndex];
161
+ }
162
+
163
+ // ============================================================================
164
+ // Cross-environment path normalization
165
+ // Converts Windows paths (D:/Code/xxx) to Linux (/home/xxx) and vice versa.
166
+ // This allows specs created on one OS to work on another.
167
+ // ============================================================================
168
+
169
+ /** Mapping of known Windows → Linux path equivalents. */
170
+ const PATH_MAP = [
171
+ { win: 'D:/Code/principles', linux: '/home/csuzngjh/code/principles' },
172
+ { win: 'D:/Code/openclaw', linux: '/home/csuzngjh/code/openclaw' },
173
+ { win: 'D:/Code/principles-arch-docs',linux: '/home/csuzngjh/code/principles-arch-docs' },
174
+ { win: 'D:/Code/principles-empathy-fix', linux: '/home/csuzngjh/code/principles-empathy-fix' },
175
+ { win: 'D:/Code/principles-subagent-helper-deep-reflect', linux: '/home/csuzngjh/code/principles-subagent-helper-deep-reflect' },
176
+ { win: 'D:/Code/principles-subagent-helper-empathy', linux: '/home/csuzngjh/code/principles-subagent-helper-empathy' },
177
+ { win: 'D:/Code/principles-workflow-validation', linux: '/home/csuzngjh/code/principles-workflow-validation' },
178
+ ];
179
+
180
+ /** Spec fields that contain paths and should be normalized. */
181
+ const PATH_FIELDS = new Set([
182
+ 'workspace', 'branchWorkspace', 'openclaw', 'specPath',
183
+ 'principlesRoot', 'branch', 'baseBranch',
184
+ ]);
185
+
186
+ /** Normalize a single path for the current OS. */
187
+ function normalizePath(str) {
188
+ if (typeof str !== 'string') return str;
189
+ let result = resolveSkillPlaceholder(str);
190
+ const isWin = process.platform === 'win32';
191
+ for (const mapping of PATH_MAP) {
192
+ const from = isWin ? mapping.linux : mapping.win;
193
+ const to = isWin ? mapping.win : mapping.linux;
194
+ // Replace both forward-slash and back-slash variants
195
+ result = result.split(from).join(to);
196
+ result = result.split(from.replace(/\//g, '\\\\')).join(to);
197
+ }
198
+ return result;
199
+ }
200
+
201
+ /** Recursively normalize path values in a spec object — only for known path fields. */
202
+ function normalizeSpecPaths(obj, key = null) {
203
+ if (typeof obj === 'string') {
204
+ // Only normalize if this is a known path field or looks like an absolute path
205
+ if (key && PATH_FIELDS.has(key)) return normalizePath(obj);
206
+ if (/^(\/|[A-Z]:[\\/])/i.test(obj)) return normalizePath(obj);
207
+ return obj;
208
+ }
209
+ if (Array.isArray(obj)) return obj.map((item) => normalizeSpecPaths(item, key));
210
+ if (obj && typeof obj === 'object') {
211
+ const normalized = {};
212
+ for (const [k, value] of Object.entries(obj)) {
213
+ normalized[k] = normalizeSpecPaths(value, k);
214
+ }
215
+ return normalized;
216
+ }
217
+ return obj;
218
+ }
219
+
220
+ export function getTaskSpec(taskId, specPath) {
221
+ // Priority: explicit path > filesystem spec > error
222
+ const candidates = specPath
223
+ ? [specPath]
224
+ : [
225
+ path.join(SPECS_DIR, `${taskId}.json`),
226
+ path.join(SPECS_DIR, taskId),
227
+ ];
228
+
229
+ for (const candidate of candidates) {
230
+ if (fs.existsSync(candidate)) {
231
+ const raw = JSON.parse(fs.readFileSync(candidate, 'utf8'));
232
+ const spec = normalizeSpecPaths(raw);
233
+ validateSpecAgents(spec); // Strict validation — throws on unknown agent/model
234
+ validateTaskContract(spec);
235
+ validateWorkUnits(spec);
236
+ return spec;
237
+ }
238
+ }
239
+
240
+ throw new Error(`Unknown task spec: ${taskId}. Place a spec file in ${SPECS_DIR}/<task-id>.json or pass --task-spec <path>.`);
241
+ }
242
+
243
+ export function buildStageBrief(spec, stage, round, previousDecision, handoff = null, checkpointSummary = null, options = {}) {
244
+ const { workUnitIndex = 0, runDir = '<runDir>', stageDir = '<stageDir>' } = options;
245
+ const goals = spec.stageGoals[stage] ?? [];
246
+ const hypotheses = stage === 'investigate' ? (spec.investigateHypotheses ?? []) : [];
247
+ const stageCriteria = spec.stageCriteria?.[stage];
248
+ const scoringDimensions = stageCriteria?.scoringDimensions ?? [];
249
+ const dimensionThreshold = stageCriteria?.dimensionThreshold ?? 3;
250
+ const requiredDeliverables = stageCriteria?.requiredDeliverables ?? [];
251
+ const workUnit = getActiveWorkUnit(spec, stage, { workUnitIndex });
252
+
253
+ // Build structured carry forward from handoff or fall back to raw decision text
254
+ let carryForward;
255
+ if (handoff?.carryForwardSummary) {
256
+ carryForward = [
257
+ '## Carry Forward',
258
+ '',
259
+ handoff.carryForwardSummary.trim(),
260
+ '',
261
+ 'Use this compact carry-forward summary as the default continuation context. Consult the full prior decision only if this summary is insufficient.',
262
+ '',
263
+ ].join('\n');
264
+ } else if (checkpointSummary) {
265
+ carryForward = [
266
+ '## Carry Forward',
267
+ '',
268
+ checkpointSummary.trim(),
269
+ '',
270
+ 'Use this checkpoint summary as the primary carry-forward context. Only consult the full prior decision or handoff if the checkpoint is insufficient.',
271
+ '',
272
+ ].join('\n');
273
+ } else if (handoff) {
274
+ const accomplished = handoff.contractItems?.filter((i) => i.status === 'DONE') ?? [];
275
+ const incomplete = handoff.contractItems?.filter((i) => i.status !== 'DONE') ?? [];
276
+ carryForward = [
277
+ '## Carry Forward',
278
+ '',
279
+ '### What was accomplished',
280
+ ...(accomplished.length > 0 ? accomplished.map((i) => `- ${i.deliverable}`) : ['- None.']),
281
+ '',
282
+ '### What needs to change',
283
+ ...(handoff.blockers?.length > 0 ? handoff.blockers.map((b) => `- ${b}`) : ['- No blockers from previous round.']),
284
+ '',
285
+ '### Focus for this round',
286
+ ...(handoff.focusForNextRound ? [handoff.focusForNextRound] : ['- Follow stage goals.']),
287
+ '',
288
+ ].join('\n');
289
+ } else if (previousDecision) {
290
+ carryForward = `## Carry Forward\n\n${previousDecision}\n`;
291
+ } else {
292
+ carryForward = '## Carry Forward\n\n- None.\n';
293
+ }
294
+
295
+ return [
296
+ `# Stage Brief`,
297
+ '',
298
+ `- Task: ${spec.title}`,
299
+ `- Stage: ${stage}`,
300
+ `- Round: ${round}`,
301
+ '',
302
+ `## Goals`,
303
+ ...goals.map((goal) => `- ${goal}`),
304
+ '',
305
+ ...(workUnit
306
+ ? [
307
+ `## Active Work Unit`,
308
+ `- workUnitId: ${workUnit.workUnitId}`,
309
+ `- workUnitGoal: ${workUnit.workUnitGoal}`,
310
+ `- allowedFiles: ${workUnit.allowedFiles.join(', ')}`,
311
+ `- unitChecks: ${workUnit.unitChecks.join(' | ')}`,
312
+ `- unitDeliverables: ${workUnit.unitDeliverables.join(' | ')}`,
313
+ `- unitSummary: ${handoff?.unitSummary ?? workUnit.unitSummary}`,
314
+ `- carryForwardSummary: ${handoff?.carryForwardSummary ?? workUnit.carryForwardSummary}`,
315
+ '',
316
+ ]
317
+ : []),
318
+ ...(hypotheses.length
319
+ ? [
320
+ `## Required Hypotheses`,
321
+ ...hypotheses.map((item) => `- ${item}`),
322
+ '',
323
+ ]
324
+ : []),
325
+ carryForward.trimEnd(),
326
+ '',
327
+ `## Constraints`,
328
+ ...spec.context.map((line) => `- ${line}`),
329
+ '',
330
+ ...(spec.taskContract
331
+ ? [
332
+ `## Task Contract`,
333
+ `- Goal: ${spec.taskContract.goal ?? 'n/a'}`,
334
+ `- In scope: ${(spec.taskContract.inScope ?? []).join('; ') || 'n/a'}`,
335
+ `- Out of scope: ${(spec.taskContract.outOfScope ?? []).join('; ') || 'n/a'}`,
336
+ `- Validation commands: ${(spec.taskContract.validationCommands ?? []).join('; ') || 'n/a'}`,
337
+ `- Expected artifacts: ${(spec.taskContract.expectedArtifacts ?? []).join('; ') || 'n/a'}`,
338
+ '',
339
+ ]
340
+ : []),
341
+ ...(spec.executionScope
342
+ ? [
343
+ `## Execution Scope Limits`,
344
+ ...(spec.executionScope.maxFiles ? [`- Max files to modify in one round: ${spec.executionScope.maxFiles}`] : []),
345
+ ...(spec.executionScope.maxChecks ? [`- Max checks to run in one round: ${spec.executionScope.maxChecks}`] : []),
346
+ ...(spec.executionScope.maxDeliverables ? [`- Max deliverables to claim in one round: ${spec.executionScope.maxDeliverables}`] : []),
347
+ `- If the round needs more scope than the limits above, revise or continue with a narrower next round instead of forcing it through.`,
348
+ '',
349
+ ]
350
+ : []),
351
+ ...(stageCriteria?.requiredReviewerSections?.length
352
+ ? [
353
+ `## Required Reviewer Sections`,
354
+ `Your report MUST use exactly these section headings (markdown format):`,
355
+ ...stageCriteria.requiredReviewerSections.map((s) => `- ## ${s}`),
356
+ `Examples of valid headings: ## VERDICT, ## BLOCKERS, ## FINDINGS, ## TRANSPORT_ASSESSMENT, ## OPENCLAW_ASSUMPTION_REVIEW, ## NEXT_FOCUS, ## CHECKS, ## DIMENSIONS`,
357
+ `You MUST include ALL sections listed above. Omitting any section will cause the sprint to halt.`,
358
+ '',
359
+ ]
360
+ : []),
361
+ ...(stageCriteria?.requiredProducerSections?.length
362
+ ? [
363
+ `## Required Producer Sections`,
364
+ `The producer report MUST use exactly these section headings (markdown format):`,
365
+ ...stageCriteria.requiredProducerSections.map((s) => `- ## ${s}`),
366
+ '',
367
+ ]
368
+ : []),
369
+ ...(scoringDimensions.length > 0
370
+ ? [
371
+ `## Scoring Dimensions`,
372
+ `Reviewers will score this stage on a 1-5 scale across these dimensions:`,
373
+ ...scoringDimensions.map((d) => `- ${d}`),
374
+ `Threshold: each dimension must score at least ${dimensionThreshold}/5.`,
375
+ '',
376
+ ]
377
+ : []),
378
+ ...(requiredDeliverables.length > 0
379
+ ? [
380
+ `## Contract Template`,
381
+ `The producer must include a CONTRACT section declaring the status of each deliverable.`,
382
+ `Required deliverables:`,
383
+ ...requiredDeliverables.map((d) => `- ${d}`),
384
+ `Format: CONTRACT: followed by bullets like: - <description> status: DONE|PARTIAL|TODO`,
385
+ '',
386
+ ]
387
+ : []),
388
+ `## Exit Criteria`,
389
+ ...(stageCriteria?.globalReviewerRequired === true
390
+ ? [
391
+ `- reviewer_a returns VERDICT: APPROVE`,
392
+ `- reviewer_b returns VERDICT: APPROVE`,
393
+ `- global_reviewer returns VERDICT: APPROVE`,
394
+ `- No unresolved blocker remains in any reviewer output`,
395
+ ...(scoringDimensions.length > 0
396
+ ? [`- All scoring dimensions meet threshold (${dimensionThreshold}/5)`]
397
+ : []),
398
+ ...(requiredDeliverables.length > 0
399
+ ? ['- All contract deliverables reach status: DONE']
400
+ : []),
401
+ ...(stageCriteria?.requiredProducerSections?.length
402
+ ? [`- Producer report must contain sections: ${stageCriteria.requiredProducerSections.join(', ')}`]
403
+ : []),
404
+ ...(stageCriteria?.requiredReviewerSections?.length
405
+ ? [`- Reviewer reports must contain sections: ${stageCriteria.requiredReviewerSections.join(', ')}`]
406
+ : []),
407
+ ...(stageCriteria?.requiredGlobalReviewerSections?.length
408
+ ? [`- Global reviewer report must contain sections: ${stageCriteria.requiredGlobalReviewerSections.join(', ')}`]
409
+ : []),
410
+ ...(stageCriteria?.globalReviewerMustAnswer?.length
411
+ ? [`- Global reviewer must answer macro questions: ${stageCriteria.globalReviewerMustAnswer.join(', ')}`]
412
+ : []),
413
+ ]
414
+ : [
415
+ `- Both reviewers return VERDICT: APPROVE`,
416
+ `- No unresolved blocker remains in reviewer outputs`,
417
+ ...(scoringDimensions.length > 0
418
+ ? [`- All scoring dimensions meet threshold (${dimensionThreshold}/5)`]
419
+ : []),
420
+ ...(requiredDeliverables.length > 0
421
+ ? ['- All contract deliverables reach status: DONE']
422
+ : []),
423
+ ...(stageCriteria?.requiredProducerSections?.length
424
+ ? [`- Producer report must contain sections: ${stageCriteria.requiredProducerSections.join(', ')}`]
425
+ : []),
426
+ ...(stageCriteria?.requiredReviewerSections?.length
427
+ ? [`- Reviewer reports must contain sections: ${stageCriteria.requiredReviewerSections.join(', ')}`]
428
+ : []),
429
+ ]),
430
+ '',
431
+ ].join('\n');
432
+ }
433
+
434
+ export function buildRolePrompt({ spec, stage, round, role, runDir, stageDir, briefPath, producerPath, reviewerAPath, reviewerBPath, globalReviewerPath, workUnit = null }) {
435
+ const outputPathMap = {
436
+ producer: producerPath,
437
+ reviewer_a: reviewerAPath,
438
+ reviewer_b: reviewerBPath,
439
+ global_reviewer: globalReviewerPath,
440
+ };
441
+ const outputPath = outputPathMap[role] ?? producerPath;
442
+ const worklogPath = role === 'producer'
443
+ ? `${stageDir}/producer-worklog.md`
444
+ : role === 'reviewer_a'
445
+ ? `${stageDir}/reviewer-a-worklog.md`
446
+ : role === 'reviewer_b'
447
+ ? `${stageDir}/reviewer-b-worklog.md`
448
+ : `${stageDir}/global-reviewer-worklog.md`;
449
+ const roleStatePath = role === 'producer'
450
+ ? `${stageDir}/producer-state.json`
451
+ : role === 'reviewer_a'
452
+ ? `${stageDir}/reviewer-a-state.json`
453
+ : role === 'reviewer_b'
454
+ ? `${stageDir}/reviewer-b-state.json`
455
+ : `${stageDir}/global-reviewer-state.json`;
456
+ const home = os.homedir();
457
+ const sharedSkills = [
458
+ path.join(home, '.codex', 'skills', 'acpx', 'SKILL.md'),
459
+ path.join(home, '.codex', 'superpowers', 'skills', 'systematic-debugging', 'SKILL.md'),
460
+ path.join(home, '.codex', 'superpowers', 'skills', 'verification-before-completion', 'SKILL.md'),
461
+ path.join(home, '.agents', 'skills', 'self-improving-agent', 'SKILL.md'),
462
+ ];
463
+ const base = [
464
+ `You are acting as ${role} in an AI sprint orchestrator for the Principles repository.`,
465
+ `Current task: ${spec.title}`,
466
+ `Stage: ${stage}`,
467
+ `Round: ${round}`,
468
+ `Working directory for task artifacts: ${stageDir}`,
469
+ `Overall sprint directory: ${runDir}`,
470
+ `Read the stage brief first: ${briefPath}`,
471
+ `Your final report file: ${outputPath}`,
472
+ `Your worklog file: ${worklogPath}`,
473
+ `Your role state file: ${roleStatePath}`,
474
+ `Protected orchestrator-owned files that you must NOT modify: ${runDir}/sprint.json, ${stageDir}/decision.md, ${stageDir}/scorecard.json`,
475
+ `Shared skill references are available at:`,
476
+ ...sharedSkills.map((skill) => `- ${skill}`),
477
+ `Before substantial work, create or update your role state file with: role, stage, round, status, checklist, updatedAt.`,
478
+ `During work, append short checkpoints to your worklog whenever you complete a meaningful investigation step, code change, review finding, or verification step.`,
479
+ `If you get stuck, record the concrete blocker and next best action in both the role state file and worklog before ending.`,
480
+ `Prefer shell commands for file updates when direct write/edit tools are flaky in long sessions.`,
481
+ `When your final report is complete, stop immediately: do not keep exploring, do not retry extra reads, and do not update state/worklog again after the final report is written.`,
482
+ ...(workUnit
483
+ ? [
484
+ `Work-unit execution is mandatory for this run.`,
485
+ `Current workUnitId: ${workUnit.workUnitId}`,
486
+ `Current workUnitGoal: ${workUnit.workUnitGoal}`,
487
+ `Allowed files: ${workUnit.allowedFiles.join(', ')}`,
488
+ `Expected checks: ${workUnit.unitChecks.join(' | ')}`,
489
+ `Expected deliverables: ${workUnit.unitDeliverables.join(' | ')}`,
490
+ `Prior carry-forward summary: ${workUnit.carryForwardSummary}`,
491
+ `Stay within the allowed files unless the work-unit contract is insufficient; if it is insufficient, stop and explain why.`,
492
+ ]
493
+ : []),
494
+ ];
495
+
496
+ if (role === 'producer') {
497
+ const stageCriteria = spec.stageCriteria?.[stage];
498
+ const requiredDeliverables = stageCriteria?.requiredDeliverables ?? [];
499
+ const contractInstruction = requiredDeliverables.length > 0
500
+ ? [
501
+ `Your report must include a CONTRACT section listing each deliverable with its status.`,
502
+ `Format: CONTRACT: followed by bullets like: - <deliverable description> status: DONE|PARTIAL|TODO`,
503
+ `Required deliverables for this stage: ${requiredDeliverables.join(', ')}`,
504
+ `All deliverables must reach status: DONE for the stage to advance.`,
505
+ ]
506
+ : [];
507
+ const codeEvidenceInstruction = [
508
+ `Your report must include a CODE_EVIDENCE section. Format:`,
509
+ `- files_checked: <comma-separated list of files you examined>`,
510
+ `- evidence_source: local|remote|both`,
511
+ `- sha: <HEAD SHA at time of evidence collection>`,
512
+ `- branch/worktree: <branch name or worktree path if applicable>`,
513
+ `When OpenClaw runtime semantics are involved, add: evidence_scope: principles|openclaw|both`,
514
+ ].join('\n');
515
+
516
+ return [
517
+ ...base,
518
+ `You may inspect and modify repository code when the stage requires implementation.`,
519
+ `You are expected to work autonomously within this stage until you either satisfy the stage goals or hit a concrete blocker.`,
520
+ `Persist your intermediate findings frequently so a future agent can resume without relying on chat context.`,
521
+ `Before modifying code, write a short execution declaration in your worklog with: PLANNED_FILES, PLANNED_CHECKS, and DELIVERABLES for this round.`,
522
+ ...(spec.executionScope
523
+ ? [
524
+ `Do not exceed the declared execution scope for this round.`,
525
+ ...(spec.executionScope.maxFiles ? [`If you need to modify more than ${spec.executionScope.maxFiles} files, stop and explain why the work should be split into another round.`] : []),
526
+ ...(spec.executionScope.maxChecks ? [`If you need more than ${spec.executionScope.maxChecks} checks, reduce scope or move the rest to the next round.`] : []),
527
+ ...(spec.executionScope.maxDeliverables ? [`If you need to claim more than ${spec.executionScope.maxDeliverables} deliverables, split the work instead of overloading one round.`] : []),
528
+ ]
529
+ : []),
530
+ // WF-004 fix v2: Enforce schema compliance with hard constraints.
531
+ // Agents that omit required sections get their stage REJECTED — this wastes
532
+ // rounds and triggers orchestrator retries. Tell the agent explicitly.
533
+ ``,
534
+ `### 📋 REPORT SCHEMA — HARD CONSTRAINT (read this BEFORE writing your report)`,
535
+ ``,
536
+ `The orchestrator validates your report against a REQUIRED SECTIONS checklist.`,
537
+ `If ANY section below is missing, your stage will be REJECTED and you will be forced to retry.`,
538
+ `This has already cost multiple sprints their max_rounds budget — do NOT let it happen again.`,
539
+ ``,
540
+ `REQUIRED SECTIONS (MUST appear verbatim as ## headings, in this order):`,
541
+ ` 1. ## SUMMARY`,
542
+ ` 2. ## CHANGES`,
543
+ ` 3. ## EVIDENCE`,
544
+ ` 4. ## CODE_EVIDENCE`,
545
+ ` 5. ## KEY_EVENTS`,
546
+ ` 6. ## HYPOTHESIS_MATRIX`,
547
+ ` 7. ## CHECKS`,
548
+ ` 8. ## OPEN_RISKS`,
549
+ `${requiredDeliverables.length > 0 ? ` 9. ## CONTRACT` : ''}`,
550
+ ``,
551
+ `### SELF-CHECK — before you finish:`,
552
+ `Scan your report for EACH of the headings above. If any is missing, add it NOW.`,
553
+ `If you have nothing to say for a section, write "None" under the heading — NEVER skip it.`,
554
+ ``,
555
+ `Write your markdown report to ${outputPath} using EXACTLY the skeleton below:`,
556
+ ``,
557
+ `--- PRODUCER REPORT TEMPLATE ---
558
+ ## SUMMARY
559
+ <brief overview of what was done and found>
560
+
561
+ ## CHANGES
562
+ <list of files created/modified with brief descriptions, or "No changes in this stage">
563
+
564
+ ## EVIDENCE
565
+ <supporting evidence — git log, test output, file contents, sprint state>
566
+
567
+ ## CODE_EVIDENCE
568
+ - files_checked: <comma-separated list>
569
+ - evidence_source: local|remote|both
570
+ - sha: <HEAD SHA>
571
+ <add evidence_scope: principles|openclaw|both if applicable>
572
+
573
+ ## KEY_EVENTS
574
+ <bullets describing concrete completed milestones or validated events>
575
+
576
+ ## HYPOTHESIS_MATRIX
577
+ ${stage === 'investigate' ? '<one bullet per hypothesis: - H1: SUPPORTED|REFUTED|UNPROVEN — <evidence>.' : '<any remaining competing explanations or risk assumptions>'}
578
+
579
+ ## CHECKS
580
+ CHECKS: evidence=<ok/fail>;tests=<pass-count>-pass;scope=pd-only
581
+
582
+ ## OPEN_RISKS
583
+ <remaining risks, uncertainties, or items requiring human attention, or "None at this time">
584
+ ${requiredDeliverables.length > 0 ? `
585
+ ## CONTRACT
586
+ <contract compliance statement, one line per required deliverable>` : ''}`,
587
+ `--- END TEMPLATE — every heading above MUST appear in your report ---`,
588
+ codeEvidenceInstruction,
589
+ ...contractInstruction,
590
+ `Do not dump long reasoning logs to stdout. Stdout should only contain a short completion line such as: ROLE_STATUS: completed; report=${outputPath}`,
591
+ `After writing ${outputPath}, emit the completion line and exit the session immediately.`,
592
+ `Stay within Principles. Do not modify OpenClaw.`,
593
+ ].join('\n');
594
+ }
595
+
596
+ const counterpart = role === 'reviewer_a' ? producerPath : producerPath;
597
+
598
+ // Global reviewer case — macro goal alignment, business/data flow, architecture
599
+ if (role === 'global_reviewer') {
600
+ const stageCriteria = spec.stageCriteria?.[stage] ?? {};
601
+ const requiredMacroAnswers = stageCriteria?.globalReviewerMustAnswer ?? [];
602
+ const macroQuestions = [
603
+ 'Q1: OpenClaw compatibility — runtime hook assumptions verified via cross-repo source reading?',
604
+ 'Q2: Business flow closure — subagent results routed and persisted without loss windows?',
605
+ 'Q3: Architecture convergence — unified protocol or new implicit divergence introduced?',
606
+ 'Q4: Data flow closure — sessionKey/runId/parentSessionId chain correctly attributed in helper layer?',
607
+ 'Q5: Distance to goal — is this sprint actually closer to unified PD subagent workflow?',
608
+ ];
609
+ const macroAnswersInstruction = requiredMacroAnswers.length > 0
610
+ ? [
611
+ `You must answer the required macro questions in a MACRO_ANSWERS section.`,
612
+ `Format: MACRO_ANSWERS:\n${requiredMacroAnswers.map((q) => `${q}: <your answer> — <evidence or cross-repo reference>`).join('\n')}`,
613
+ `Required macro questions for this stage: ${requiredMacroAnswers.map((q) => q).join(', ')}`,
614
+ ].join('\n')
615
+ : [
616
+ `Include a MACRO_ANSWERS section answering the five standard macro questions:`,
617
+ `MACRO_ANSWERS:`,
618
+ `Q1: <OpenClaw compatibility answer> — <evidence or cross-repo reference>`,
619
+ `Q2: <Business flow closure answer> — <evidence>`,
620
+ `Q3: <Architecture convergence answer> — <architectural rationale or trade-off>`,
621
+ `Q4: <Data flow closure answer> — <dedupe/finalize risk assessment>`,
622
+ `Q5: <Distance to goal answer> — <remaining gap and next priority>`,
623
+ ].join('\n');
624
+
625
+ return [
626
+ ...base,
627
+ `You are the global reviewer — focused on macro goal alignment, business flow, data flow, architecture, and OpenClaw compatibility.`,
628
+ `You do NOT review local code correctness (that's reviewer_a's job) or runtime/compatibility (that's reviewer_b's job).`,
629
+ `You focus on: Does this change actually serve the end goal? Is the business flow closed? Is the architecture converging?`,
630
+ `Read the stage brief: ${briefPath}`,
631
+ `Read the producer report: ${producerPath}`,
632
+ `Read reviewer A's report: ${reviewerAPath}`,
633
+ `Read reviewer B's report: ${reviewerBPath}`,
634
+ macroAnswersInstruction,
635
+ `Your report must include a CODE_EVIDENCE section when your assessment relies on source reading. Format:`,
636
+ `- files_verified: <comma-separated list of files you examined>`,
637
+ `- evidence_source: local|remote|both`,
638
+ `- sha: <the SHA you verified against>`,
639
+ `When OpenClaw hook semantics are involved: evidence_scope: principles|openclaw|both`,
640
+ `At the end, write a markdown report to ${outputPath} with exactly these sections: VERDICT, MACRO_ANSWERS, BLOCKERS, FINDINGS, CODE_EVIDENCE, NEXT_FOCUS, CHECKS.`,
641
+ `VERDICT must be exactly one of: APPROVE, REVISE, BLOCK.`,
642
+ `BLOCK means: the macro goal is not served, or critical architecture/business flow risks exist even if local correctness is fine.`,
643
+ `CHECKS should be a single-line machine-readable summary such as: CHECKS: macro=aligned;business_flow=closed;architecture=converging`,
644
+ `Do not dump long reasoning logs to stdout. Stdout should only contain a short completion line such as: ROLE_STATUS: completed; report=${outputPath}`,
645
+ `After writing ${outputPath}, emit the completion line and exit the session immediately.`,
646
+ ].join('\n');
647
+ }
648
+
649
+ const stageCriteria = spec.stageCriteria?.[stage];
650
+ const scoringDimensions = stageCriteria?.scoringDimensions ?? [];
651
+ const dimensionThreshold = stageCriteria?.dimensionThreshold ?? 3;
652
+ const requiredDeliverables = stageCriteria?.requiredDeliverables ?? [];
653
+ const config = role === 'reviewer_a' ? spec.reviewerA : spec.reviewerB;
654
+
655
+ const reviewerFocus = role === 'reviewer_a'
656
+ ? [
657
+ `Your primary focus: correctness and root-cause analysis.`,
658
+ `Challenge whether the producer's evidence actually supports the claimed conclusions.`,
659
+ `Check for logical gaps, untested edge cases, and missing error paths.`,
660
+ `Verify that code citations (file paths, line numbers) are accurate by reading the referenced files.`,
661
+ ]
662
+ : [
663
+ `Your primary focus: scope control, regression risk, and test coverage.`,
664
+ `Check whether the producer's changes are the smallest sufficient fix, or if scope has crept.`,
665
+ `Identify missing tests, insufficient coverage, and potential side effects.`,
666
+ `Flag any unnecessary architectural expansion or gold-plating.`,
667
+ ];
668
+
669
+ const reviewerValueChecks = [
670
+ `Your review must explicitly judge whether the producer changed real system behavior or only produced plausible-looking text.`,
671
+ `State whether the verification evidence actually proves the claimed behavior change.`,
672
+ `Call out any risk that remains unverified even if the report structure is complete.`,
673
+ config?.focus ? `Additional reviewer focus: ${config.focus}` : null,
674
+ ].filter(Boolean);
675
+
676
+ const codeEvidenceReviewerInstruction = [
677
+ `Your report must include a CODE_EVIDENCE section. Format:`,
678
+ `- files_verified: <comma-separated list of files you read or checked for evidence>`,
679
+ `- evidence_source: local|remote|both`,
680
+ `- sha: <the SHA you verified against>`,
681
+ `When OpenClaw runtime semantics are involved, add: evidence_scope: principles|openclaw|both`,
682
+ ].join('\n');
683
+
684
+ return [
685
+ ...base,
686
+ `Read the producer report: ${counterpart}`,
687
+ ...reviewerFocus,
688
+ ...reviewerValueChecks,
689
+ `Review independently. Do not modify repository files unless explicitly needed for evidence collection.`,
690
+ `You are expected to challenge weak assumptions and record checkpoints while reviewing, not just emit a final verdict.`,
691
+ `At the end, write a markdown report to ${outputPath} with exactly these sections: VERDICT, BLOCKERS, FINDINGS, CODE_EVIDENCE, HYPOTHESIS_MATRIX, NEXT_FOCUS, CHECKS.`,
692
+ stage === 'investigate'
693
+ ? `HYPOTHESIS_MATRIX must classify each required hypothesis with one bullet in this exact shape: - <hypothesis_id>: SUPPORTED|REFUTED|UNPROVEN — <brief evidence>.`
694
+ : `HYPOTHESIS_MATRIX should capture any remaining competing explanations or risk assumptions.`,
695
+ `CHECKS should be a single-line machine-readable summary such as: CHECKS: criteria=met;blockers=0;verification=partial`,
696
+ scoringDimensions.length > 0
697
+ ? `You must include a DIMENSIONS line scoring each dimension 1-5. Format: DIMENSIONS: ${scoringDimensions.map((d) => `${d}=N`).join('; ')}. Any dimension below ${dimensionThreshold}/5 is a failure. Be honest and calibrated — do not inflate scores.`
698
+ : '',
699
+ requiredDeliverables.length > 0
700
+ ? `Check the producer's CONTRACT section. Verify each deliverable's status is honestly assessed. Flag any deliverable marked DONE that lacks convincing evidence.`
701
+ : '',
702
+ codeEvidenceReviewerInstruction,
703
+ `VERDICT must be exactly one of: APPROVE, REVISE, BLOCK.`,
704
+ `Do not dump long reasoning logs to stdout. Stdout should only contain a short completion line such as: ROLE_STATUS: completed; report=${outputPath}`,
705
+ `After writing ${outputPath}, emit the completion line and exit the session immediately.`,
706
+ ].filter(Boolean).join('\n');
707
+ }