principles-disciple 1.8.2 → 1.8.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/openclaw.plugin.json +4 -4
- package/package.json +1 -1
- package/templates/langs/en/skills/ai-sprint-orchestration/EXAMPLES.md +63 -0
- package/templates/langs/en/skills/ai-sprint-orchestration/REFERENCE.md +136 -0
- package/templates/langs/en/skills/ai-sprint-orchestration/SKILL.md +67 -0
- package/templates/langs/en/skills/ai-sprint-orchestration/references/agent-registry.json +214 -0
- package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/bugfix-complex-template.json +107 -0
- package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/feature-complex-template.json +107 -0
- package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal-verify.json +105 -0
- package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal.json +108 -0
- package/templates/langs/en/skills/ai-sprint-orchestration/references/workflow-v1-acceptance-checklist.md +58 -0
- package/templates/langs/en/skills/ai-sprint-orchestration/references/workflow-v1.4-work-unit-handoff.md +190 -0
- package/templates/langs/en/skills/ai-sprint-orchestration/runtime/.gitignore +2 -0
- package/templates/langs/en/skills/ai-sprint-orchestration/scripts/lib/archive.mjs +310 -0
- package/templates/langs/en/skills/ai-sprint-orchestration/scripts/lib/contract-enforcement.mjs +683 -0
- package/templates/langs/en/skills/ai-sprint-orchestration/scripts/lib/decision.mjs +604 -0
- package/templates/langs/en/skills/ai-sprint-orchestration/scripts/lib/state-store.mjs +32 -0
- package/templates/langs/en/skills/ai-sprint-orchestration/scripts/lib/task-specs.mjs +707 -0
- package/templates/langs/en/skills/ai-sprint-orchestration/scripts/run.mjs +3419 -0
- package/templates/langs/zh/skills/ai-sprint-orchestration/EXAMPLES.md +63 -0
- package/templates/langs/zh/skills/ai-sprint-orchestration/REFERENCE.md +136 -0
- package/templates/langs/zh/skills/ai-sprint-orchestration/SKILL.md +67 -0
- package/templates/langs/zh/skills/ai-sprint-orchestration/references/agent-registry.json +214 -0
- package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/bugfix-complex-template.json +107 -0
- package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/feature-complex-template.json +107 -0
- package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal-verify.json +105 -0
- package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal.json +108 -0
- package/templates/langs/zh/skills/ai-sprint-orchestration/references/workflow-v1-acceptance-checklist.md +58 -0
- package/templates/langs/zh/skills/ai-sprint-orchestration/references/workflow-v1.4-work-unit-handoff.md +190 -0
- package/templates/langs/zh/skills/ai-sprint-orchestration/runtime/.gitignore +2 -0
- package/templates/langs/zh/skills/ai-sprint-orchestration/scripts/lib/archive.mjs +310 -0
- package/templates/langs/zh/skills/ai-sprint-orchestration/scripts/lib/contract-enforcement.mjs +683 -0
- package/templates/langs/zh/skills/ai-sprint-orchestration/scripts/lib/decision.mjs +604 -0
- package/templates/langs/zh/skills/ai-sprint-orchestration/scripts/lib/state-store.mjs +32 -0
- package/templates/langs/zh/skills/ai-sprint-orchestration/scripts/lib/task-specs.mjs +707 -0
- package/templates/langs/zh/skills/ai-sprint-orchestration/scripts/run.mjs +3419 -0
- package/templates/langs/zh/skills/ai-sprint-orchestration/test/archive.test.mjs +230 -0
- package/templates/langs/zh/skills/ai-sprint-orchestration/test/contract-enforcement.test.mjs +672 -0
- package/templates/langs/zh/skills/ai-sprint-orchestration/test/decision.test.mjs +1321 -0
- package/templates/langs/zh/skills/ai-sprint-orchestration/test/run.test.mjs +1419 -0
package/templates/langs/en/skills/ai-sprint-orchestration/scripts/lib/contract-enforcement.mjs
ADDED
|
@@ -0,0 +1,683 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Contract Enforcement Module
|
|
3
|
+
*
|
|
4
|
+
* Defines strict schemas for agent output and provides validation functions.
|
|
5
|
+
* Orchestrator MUST validate reports against these contracts before consuming.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// ============================================================================
|
|
9
|
+
// Schema Definitions
|
|
10
|
+
// ============================================================================
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Producer Report Schema
|
|
14
|
+
* Required sections for a valid producer report.
|
|
15
|
+
*/
|
|
16
|
+
export const PRODUCER_SCHEMA = {
|
|
17
|
+
requiredSections: ['SUMMARY', 'CHANGES', 'EVIDENCE', 'CODE_EVIDENCE', 'KEY_EVENTS', 'HYPOTHESIS_MATRIX', 'CHECKS', 'OPEN_RISKS'],
|
|
18
|
+
optionalSections: ['CONTRACT'],
|
|
19
|
+
requiredFields: {
|
|
20
|
+
CHECKS: { format: 'key=value pairs', example: 'evidence=ok;tests=not-run;scope=pd-only' },
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Reviewer Report Schema
|
|
26
|
+
* Required sections for a valid reviewer report.
|
|
27
|
+
*/
|
|
28
|
+
export const REVIEWER_SCHEMA = {
|
|
29
|
+
requiredSections: ['VERDICT', 'BLOCKERS', 'FINDINGS', 'CODE_EVIDENCE', 'HYPOTHESIS_MATRIX', 'NEXT_FOCUS', 'CHECKS'],
|
|
30
|
+
optionalSections: ['DIMENSIONS'],
|
|
31
|
+
requiredFields: {
|
|
32
|
+
VERDICT: { allowedValues: ['APPROVE', 'REVISE', 'BLOCK'], format: 'exact match' },
|
|
33
|
+
CHECKS: { format: 'key=value pairs', example: 'criteria=met;blockers=0' },
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Global Reviewer Report Schema
|
|
39
|
+
* Required sections for a valid global reviewer report.
|
|
40
|
+
*/
|
|
41
|
+
export const GLOBAL_REVIEWER_SCHEMA = {
|
|
42
|
+
requiredSections: ['VERDICT', 'MACRO_ANSWERS', 'BLOCKERS', 'FINDINGS', 'CODE_EVIDENCE', 'NEXT_FOCUS', 'CHECKS'],
|
|
43
|
+
optionalSections: [],
|
|
44
|
+
requiredFields: {
|
|
45
|
+
VERDICT: { allowedValues: ['APPROVE', 'REVISE', 'BLOCK'], format: 'exact match' },
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Output Quality Levels
|
|
51
|
+
*/
|
|
52
|
+
export const OUTPUT_QUALITY = {
|
|
53
|
+
SHADOW_COMPLETE: 'shadow_complete',
|
|
54
|
+
PRODUCTION_READY: 'production_ready',
|
|
55
|
+
NEEDS_WORK: 'needs_work',
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// ============================================================================
|
|
59
|
+
// Validation Result Types
|
|
60
|
+
// ============================================================================
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* @typedef {Object} ContractValidationResult
|
|
64
|
+
* @property {boolean} valid - Whether the report satisfies the contract
|
|
65
|
+
* @property {string[]} missingSections - Sections required but not found
|
|
66
|
+
* @property {string[]} invalidFields - Fields that don't match expected format
|
|
67
|
+
* @property {Object} extractedData - Successfully extracted structured data
|
|
68
|
+
* @property {string} errorSummary - Human-readable error summary
|
|
69
|
+
*/
|
|
70
|
+
|
|
71
|
+
// ============================================================================
|
|
72
|
+
// Validation Functions
|
|
73
|
+
// ============================================================================
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Check if a section exists in the report text.
|
|
77
|
+
* Supports both "SECTION:" and "## SECTION" markdown formats.
|
|
78
|
+
*
|
|
79
|
+
* @param {string} text - Report text
|
|
80
|
+
* @param {string} heading - Section heading to find
|
|
81
|
+
* @returns {boolean}
|
|
82
|
+
*/
|
|
83
|
+
export function hasSectionStrict(text, heading) {
|
|
84
|
+
const source = String(text ?? '');
|
|
85
|
+
// Match "SECTION:" at start of line
|
|
86
|
+
const colonPattern = new RegExp(`(^|\\n)${heading}\\s*:`, 'i');
|
|
87
|
+
// Match "## SECTION" markdown heading (with optional colon after)
|
|
88
|
+
const mdPattern = new RegExp(`(^|\\n)##\\s+${heading}\\b`, 'i');
|
|
89
|
+
return colonPattern.test(source) || mdPattern.test(source);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Extract section content between headings.
|
|
94
|
+
*
|
|
95
|
+
* @param {string} text - Report text
|
|
96
|
+
* @param {string} heading - Section heading
|
|
97
|
+
* @returns {string|null} Section content or null if not found
|
|
98
|
+
*/
|
|
99
|
+
export function extractSectionContent(text, heading) {
|
|
100
|
+
const source = String(text ?? '');
|
|
101
|
+
// Match section start
|
|
102
|
+
const startPattern = new RegExp(`(?:^|\n)(?:##\s+)?${heading}\s*:?\n`, 'i');
|
|
103
|
+
const startMatch = source.match(startPattern);
|
|
104
|
+
if (!startMatch) return null;
|
|
105
|
+
|
|
106
|
+
const contentStart = startMatch.index + startMatch[0].length;
|
|
107
|
+
const afterStart = source.slice(contentStart);
|
|
108
|
+
|
|
109
|
+
// Match next section (## HEADING or HEADING:)
|
|
110
|
+
const endPattern = /\n(?:##\s+)?[A-Z][A-Z_ ]+\s*(:|\n)/;
|
|
111
|
+
const endMatch = afterStart.match(endPattern);
|
|
112
|
+
|
|
113
|
+
return endMatch ? afterStart.slice(0, endMatch.index).trim() : afterStart.trim();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Validate VERDICT field against allowed values.
|
|
118
|
+
*
|
|
119
|
+
* @param {string} text - Report text
|
|
120
|
+
* @returns {{valid: boolean, value: string|null, error: string|null}}
|
|
121
|
+
*/
|
|
122
|
+
export function validateVerdict(text) {
|
|
123
|
+
const source = String(text ?? '');
|
|
124
|
+
// Strict pattern: VERDICT: followed by exactly APPROVE, REVISE, or BLOCK
|
|
125
|
+
const pattern = /(?:VERDICT:\s*\*{0,2}|##\s*VERDICT\s*\n+\*{0,2}\s*)(APPROVE|REVISE|BLOCK)\b/i;
|
|
126
|
+
const match = source.match(pattern);
|
|
127
|
+
|
|
128
|
+
if (!match) {
|
|
129
|
+
// Check if VERDICT section exists but has invalid value
|
|
130
|
+
const loosePattern = /(?:VERDICT:\s*|##\s*VERDICT\s*\n+)([A-Z_]+)/i;
|
|
131
|
+
const looseMatch = source.match(loosePattern);
|
|
132
|
+
if (looseMatch) {
|
|
133
|
+
return {
|
|
134
|
+
valid: false,
|
|
135
|
+
value: looseMatch[1].toUpperCase(),
|
|
136
|
+
error: `Invalid VERDICT value "${looseMatch[1]}". Must be one of: APPROVE, REVISE, BLOCK`,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
return {
|
|
140
|
+
valid: false,
|
|
141
|
+
value: null,
|
|
142
|
+
error: 'VERDICT section not found or malformed',
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
valid: true,
|
|
148
|
+
value: match[1].toUpperCase(),
|
|
149
|
+
error: null,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Validate CHECKS field format (key=value pairs).
|
|
155
|
+
*
|
|
156
|
+
* @param {string} text - Report text
|
|
157
|
+
* @returns {{valid: boolean, value: Object, error: string|null}}
|
|
158
|
+
*/
|
|
159
|
+
export function validateChecks(text) {
|
|
160
|
+
const source = String(text ?? '');
|
|
161
|
+
const pattern = /CHECKS:\s*(.+?)(?:\n|$)/i;
|
|
162
|
+
const match = source.match(pattern);
|
|
163
|
+
|
|
164
|
+
if (!match) {
|
|
165
|
+
return {
|
|
166
|
+
valid: false,
|
|
167
|
+
value: {},
|
|
168
|
+
error: 'CHECKS field not found',
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const checksStr = match[1].trim();
|
|
173
|
+
const checks = {};
|
|
174
|
+
const invalidParts = [];
|
|
175
|
+
|
|
176
|
+
for (const pair of checksStr.split(';')) {
|
|
177
|
+
const eq = pair.indexOf('=');
|
|
178
|
+
if (eq === -1) {
|
|
179
|
+
invalidParts.push(pair.trim());
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
const key = pair.slice(0, eq).trim();
|
|
183
|
+
const value = pair.slice(eq + 1).trim();
|
|
184
|
+
if (key) {
|
|
185
|
+
checks[key] = value;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (invalidParts.length > 0) {
|
|
190
|
+
return {
|
|
191
|
+
valid: false,
|
|
192
|
+
value: checks,
|
|
193
|
+
error: `Invalid CHECKS format: "${invalidParts.join(', ')}". Expected key=value pairs separated by semicolons`,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return {
|
|
198
|
+
valid: true,
|
|
199
|
+
value: checks,
|
|
200
|
+
error: null,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Validate a producer report against the producer schema.
|
|
206
|
+
*
|
|
207
|
+
* @param {string} text - Producer report text
|
|
208
|
+
* @param {Object} options - Additional options
|
|
209
|
+
* @param {string[]} options.requiredDeliverables - Contract deliverables required for this stage
|
|
210
|
+
* @returns {ContractValidationResult}
|
|
211
|
+
*/
|
|
212
|
+
export function validateProducerReport(text, options = {}) {
|
|
213
|
+
const source = String(text ?? '');
|
|
214
|
+
const missingSections = [];
|
|
215
|
+
const invalidFields = [];
|
|
216
|
+
const extractedData = {};
|
|
217
|
+
|
|
218
|
+
// Check required sections
|
|
219
|
+
for (const section of PRODUCER_SCHEMA.requiredSections) {
|
|
220
|
+
if (!hasSectionStrict(source, section)) {
|
|
221
|
+
missingSections.push(section);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Validate CHECKS field
|
|
226
|
+
const checksResult = validateChecks(source);
|
|
227
|
+
if (!checksResult.valid) {
|
|
228
|
+
invalidFields.push(`CHECKS: ${checksResult.error}`);
|
|
229
|
+
} else {
|
|
230
|
+
extractedData.checks = checksResult.value;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Extract CONTRACT if deliverables are required
|
|
234
|
+
if (options.requiredDeliverables && options.requiredDeliverables.length > 0) {
|
|
235
|
+
if (!hasSectionStrict(source, 'CONTRACT')) {
|
|
236
|
+
missingSections.push('CONTRACT');
|
|
237
|
+
} else {
|
|
238
|
+
const contractContent = extractSectionContent(source, 'CONTRACT');
|
|
239
|
+
if (contractContent) {
|
|
240
|
+
extractedData.contractContent = contractContent;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const valid = missingSections.length === 0 && invalidFields.length === 0;
|
|
246
|
+
|
|
247
|
+
return {
|
|
248
|
+
valid,
|
|
249
|
+
missingSections,
|
|
250
|
+
invalidFields,
|
|
251
|
+
extractedData,
|
|
252
|
+
errorSummary: valid
|
|
253
|
+
? null
|
|
254
|
+
: `Producer report contract violation: missing sections [${missingSections.join(', ')}], invalid fields [${invalidFields.join(', ')}]`,
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Validate a reviewer report against the reviewer schema.
|
|
260
|
+
*
|
|
261
|
+
* @param {string} text - Reviewer report text
|
|
262
|
+
* @param {Object} options - Additional options
|
|
263
|
+
* @param {string[]} options.scoringDimensions - Required scoring dimensions
|
|
264
|
+
* @returns {ContractValidationResult}
|
|
265
|
+
*/
|
|
266
|
+
export function validateReviewerReport(text, options = {}) {
|
|
267
|
+
const source = String(text ?? '');
|
|
268
|
+
const missingSections = [];
|
|
269
|
+
const invalidFields = [];
|
|
270
|
+
const extractedData = {};
|
|
271
|
+
|
|
272
|
+
// Check required sections
|
|
273
|
+
for (const section of REVIEWER_SCHEMA.requiredSections) {
|
|
274
|
+
if (!hasSectionStrict(source, section)) {
|
|
275
|
+
missingSections.push(section);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Validate VERDICT field (strict)
|
|
280
|
+
const verdictResult = validateVerdict(source);
|
|
281
|
+
if (!verdictResult.valid) {
|
|
282
|
+
invalidFields.push(`VERDICT: ${verdictResult.error}`);
|
|
283
|
+
} else {
|
|
284
|
+
extractedData.verdict = verdictResult.value;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Validate CHECKS field
|
|
288
|
+
const checksResult = validateChecks(source);
|
|
289
|
+
if (!checksResult.valid) {
|
|
290
|
+
invalidFields.push(`CHECKS: ${checksResult.error}`);
|
|
291
|
+
} else {
|
|
292
|
+
extractedData.checks = checksResult.value;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Check DIMENSIONS if scoring dimensions are required
|
|
296
|
+
if (options.scoringDimensions && options.scoringDimensions.length > 0) {
|
|
297
|
+
const dimsContent = extractSectionContent(source, 'DIMENSIONS');
|
|
298
|
+
if (!dimsContent && !hasSectionStrict(source, 'DIMENSIONS')) {
|
|
299
|
+
missingSections.push('DIMENSIONS');
|
|
300
|
+
} else if (dimsContent) {
|
|
301
|
+
extractedData.dimensionsContent = dimsContent;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const valid = missingSections.length === 0 && invalidFields.length === 0;
|
|
306
|
+
|
|
307
|
+
return {
|
|
308
|
+
valid,
|
|
309
|
+
missingSections,
|
|
310
|
+
invalidFields,
|
|
311
|
+
extractedData,
|
|
312
|
+
errorSummary: valid
|
|
313
|
+
? null
|
|
314
|
+
: `Reviewer report contract violation: missing sections [${missingSections.join(', ')}], invalid fields [${invalidFields.join(', ')}]`,
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Validate a global reviewer report against the global reviewer schema.
|
|
320
|
+
*
|
|
321
|
+
* @param {string} text - Global reviewer report text
|
|
322
|
+
* @param {Object} options - Additional options
|
|
323
|
+
* @param {string[]} options.requiredMacroQuestions - Required macro questions (Q1, Q2, etc.)
|
|
324
|
+
* @returns {ContractValidationResult}
|
|
325
|
+
*/
|
|
326
|
+
export function validateGlobalReviewerReport(text, options = {}) {
|
|
327
|
+
const source = String(text ?? '');
|
|
328
|
+
const missingSections = [];
|
|
329
|
+
const invalidFields = [];
|
|
330
|
+
const extractedData = {};
|
|
331
|
+
|
|
332
|
+
// Check required sections
|
|
333
|
+
for (const section of GLOBAL_REVIEWER_SCHEMA.requiredSections) {
|
|
334
|
+
if (!hasSectionStrict(source, section)) {
|
|
335
|
+
missingSections.push(section);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Validate VERDICT field (strict)
|
|
340
|
+
const verdictResult = validateVerdict(source);
|
|
341
|
+
if (!verdictResult.valid) {
|
|
342
|
+
invalidFields.push(`VERDICT: ${verdictResult.error}`);
|
|
343
|
+
} else {
|
|
344
|
+
extractedData.verdict = verdictResult.value;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Check MACRO_ANSWERS completeness
|
|
348
|
+
if (options.requiredMacroQuestions && options.requiredMacroQuestions.length > 0) {
|
|
349
|
+
const macroContent = extractSectionContent(source, 'MACRO_ANSWERS');
|
|
350
|
+
if (!macroContent) {
|
|
351
|
+
missingSections.push('MACRO_ANSWERS');
|
|
352
|
+
} else {
|
|
353
|
+
const missingQuestions = [];
|
|
354
|
+
for (const q of options.requiredMacroQuestions) {
|
|
355
|
+
const qPattern = new RegExp(`\\b${q}\\b[^\\n]*`, 'i');
|
|
356
|
+
if (!qPattern.test(macroContent)) {
|
|
357
|
+
missingQuestions.push(q);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
if (missingQuestions.length > 0) {
|
|
361
|
+
invalidFields.push(`MACRO_ANSWERS: missing answers for [${missingQuestions.join(', ')}]`);
|
|
362
|
+
} else {
|
|
363
|
+
extractedData.macroAnswersContent = macroContent;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const valid = missingSections.length === 0 && invalidFields.length === 0;
|
|
369
|
+
|
|
370
|
+
return {
|
|
371
|
+
valid,
|
|
372
|
+
missingSections,
|
|
373
|
+
invalidFields,
|
|
374
|
+
extractedData,
|
|
375
|
+
errorSummary: valid
|
|
376
|
+
? null
|
|
377
|
+
: `Global reviewer report contract violation: missing sections [${missingSections.join(', ')}], invalid fields [${invalidFields.join(', ')}]`,
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Validate all reports for a stage.
|
|
383
|
+
* Returns a consolidated validation result.
|
|
384
|
+
*
|
|
385
|
+
* @param {Object} reports - All role reports
|
|
386
|
+
* @param {string} reports.producer - Producer report text
|
|
387
|
+
* @param {string} reports.reviewerA - Reviewer A report text
|
|
388
|
+
* @param {string} reports.reviewerB - Reviewer B report text
|
|
389
|
+
* @param {string} [reports.globalReviewer] - Global reviewer report text (optional)
|
|
390
|
+
* @param {Object} options - Validation options
|
|
391
|
+
* @param {string[]} options.requiredDeliverables - Required contract deliverables
|
|
392
|
+
* @param {string[]} options.scoringDimensions - Required scoring dimensions
|
|
393
|
+
* @param {string[]} options.requiredMacroQuestions - Required macro questions for global reviewer
|
|
394
|
+
* @param {boolean} options.globalReviewerRequired - Whether global reviewer is required
|
|
395
|
+
* @returns {{valid: boolean, producer: ContractValidationResult, reviewerA: ContractValidationResult, reviewerB: ContractValidationResult, globalReviewer: ContractValidationResult|null, errorSummary: string|null}}
|
|
396
|
+
*/
|
|
397
|
+
export function validateStageReports(reports, options = {}) {
|
|
398
|
+
const producer = validateProducerReport(reports.producer, {
|
|
399
|
+
requiredDeliverables: options.requiredDeliverables,
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
const reviewerA = validateReviewerReport(reports.reviewerA, {
|
|
403
|
+
scoringDimensions: options.scoringDimensions,
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
const reviewerB = validateReviewerReport(reports.reviewerB, {
|
|
407
|
+
scoringDimensions: options.scoringDimensions,
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
let globalReviewer = null;
|
|
411
|
+
if (options.globalReviewerRequired || reports.globalReviewer) {
|
|
412
|
+
globalReviewer = validateGlobalReviewerReport(reports.globalReviewer || '', {
|
|
413
|
+
requiredMacroQuestions: options.requiredMacroQuestions,
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
const allValid = producer.valid && reviewerA.valid && reviewerB.valid && (globalReviewer ? globalReviewer.valid : true);
|
|
418
|
+
|
|
419
|
+
const errors = [];
|
|
420
|
+
if (!producer.valid) errors.push(producer.errorSummary);
|
|
421
|
+
if (!reviewerA.valid) errors.push(reviewerA.errorSummary);
|
|
422
|
+
if (!reviewerB.valid) errors.push(reviewerB.errorSummary);
|
|
423
|
+
if (globalReviewer && !globalReviewer.valid) errors.push(globalReviewer.errorSummary);
|
|
424
|
+
|
|
425
|
+
return {
|
|
426
|
+
valid: allValid,
|
|
427
|
+
producer,
|
|
428
|
+
reviewerA,
|
|
429
|
+
reviewerB,
|
|
430
|
+
globalReviewer,
|
|
431
|
+
errorSummary: allValid ? null : errors.join('\n'),
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// ============================================================================
|
|
436
|
+
// Output Quality Determination
|
|
437
|
+
// ============================================================================
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Determine output quality level based on validation and metrics.
|
|
441
|
+
*
|
|
442
|
+
* SHADOW_COMPLETE criteria:
|
|
443
|
+
* - All reports pass contract validation
|
|
444
|
+
* - All reviewers APPROVE
|
|
445
|
+
* - No blockers
|
|
446
|
+
* - All required sections present
|
|
447
|
+
* - Dimensions meet threshold (if applicable)
|
|
448
|
+
* - Contract fulfilled (if applicable)
|
|
449
|
+
*
|
|
450
|
+
* PRODUCTION_READY criteria (in addition to SHADOW_COMPLETE):
|
|
451
|
+
* - CODE_EVIDENCE includes evidence_scope: both (cross-repo verification)
|
|
452
|
+
* - All scoring dimensions >= 4 (not just meeting threshold)
|
|
453
|
+
* - No OPEN_RISKS or OPEN_RISKS explicitly marked as "acceptable"
|
|
454
|
+
* - MACRO_ANSWERS all satisfied with concrete evidence references
|
|
455
|
+
*
|
|
456
|
+
* @param {Object} validation - Stage reports validation result
|
|
457
|
+
* @param {Object} metrics - Stage metrics from decideStage
|
|
458
|
+
* @param {Object} options - Additional options
|
|
459
|
+
* @param {number} options.productionThreshold - Minimum dimension score for production_ready (default: 4)
|
|
460
|
+
* @returns {{quality: string, reasons: string[]}}
|
|
461
|
+
*/
|
|
462
|
+
export function determineOutputQuality(validation, metrics, options = {}) {
|
|
463
|
+
const productionThreshold = options.productionThreshold ?? 4;
|
|
464
|
+
const reasons = [];
|
|
465
|
+
|
|
466
|
+
// Check basic contract validation
|
|
467
|
+
if (!validation.valid) {
|
|
468
|
+
reasons.push('Reports do not satisfy contract validation');
|
|
469
|
+
return { quality: OUTPUT_QUALITY.NEEDS_WORK, reasons };
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// Check approval status
|
|
473
|
+
if (metrics.approvalCount < (metrics.requiredApprovals ?? 2)) {
|
|
474
|
+
reasons.push(`Insufficient approvals: ${metrics.approvalCount}/${metrics.requiredApprovals ?? 2}`);
|
|
475
|
+
return { quality: OUTPUT_QUALITY.NEEDS_WORK, reasons };
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// Check blockers
|
|
479
|
+
if (metrics.blockerCount > 0) {
|
|
480
|
+
reasons.push(`Unresolved blockers: ${metrics.blockerCount}`);
|
|
481
|
+
return { quality: OUTPUT_QUALITY.NEEDS_WORK, reasons };
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// Check dimension failures
|
|
485
|
+
if (metrics.dimensionFailures && metrics.dimensionFailures.length > 0) {
|
|
486
|
+
reasons.push(`Dimension failures: ${metrics.dimensionFailures.join('; ')}`);
|
|
487
|
+
return { quality: OUTPUT_QUALITY.NEEDS_WORK, reasons };
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// Check contract fulfillment
|
|
491
|
+
if (metrics.requiredDeliverables && metrics.requiredDeliverables.length > 0) {
|
|
492
|
+
if (!metrics.contractCheck || !metrics.contractCheck.allDone) {
|
|
493
|
+
reasons.push('Contract not fulfilled');
|
|
494
|
+
return { quality: OUTPUT_QUALITY.NEEDS_WORK, reasons };
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// Check global reviewer requirements
|
|
499
|
+
if (metrics.globalReviewerRequired) {
|
|
500
|
+
if (!metrics.macroAnswersAllSatisfied) {
|
|
501
|
+
reasons.push('Macro answers not satisfied');
|
|
502
|
+
return { quality: OUTPUT_QUALITY.NEEDS_WORK, reasons };
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// === SHADOW_COMPLETE threshold reached ===
|
|
507
|
+
// Now check for PRODUCTION_READY
|
|
508
|
+
|
|
509
|
+
const productionBlockers = [];
|
|
510
|
+
|
|
511
|
+
// Check CODE_EVIDENCE scope (production requires cross-repo verification)
|
|
512
|
+
// Must explicitly have evidence_scope: both for production ready
|
|
513
|
+
const producerScope = metrics.producerCodeEvidence?.evidenceScope;
|
|
514
|
+
if (!producerScope) {
|
|
515
|
+
productionBlockers.push('Producer CODE_EVIDENCE missing evidence_scope field (required: "both")');
|
|
516
|
+
} else if (producerScope !== 'both') {
|
|
517
|
+
productionBlockers.push(`Producer CODE_EVIDENCE scope is "${producerScope}", not "both"`);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// Check dimension scores for production threshold
|
|
521
|
+
// Check each reviewer separately - production requires ALL reviewers to score >= threshold
|
|
522
|
+
if (metrics.scoringDimensions && metrics.scoringDimensions.length > 0) {
|
|
523
|
+
for (const dim of metrics.scoringDimensions) {
|
|
524
|
+
const scoreA = metrics.reviewerADimensions?.[dim];
|
|
525
|
+
const scoreB = metrics.reviewerBDimensions?.[dim];
|
|
526
|
+
if (scoreA !== undefined && scoreA < productionThreshold) {
|
|
527
|
+
productionBlockers.push(`Dimension "${dim}" reviewer A scored ${scoreA}/5, below production threshold ${productionThreshold}`);
|
|
528
|
+
}
|
|
529
|
+
if (scoreB !== undefined && scoreB < productionThreshold) {
|
|
530
|
+
productionBlockers.push(`Dimension "${dim}" reviewer B scored ${scoreB}/5, below production threshold ${productionThreshold}`);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// If no production blockers, it's production ready
|
|
536
|
+
if (productionBlockers.length === 0) {
|
|
537
|
+
return { quality: OUTPUT_QUALITY.PRODUCTION_READY, reasons: [] };
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// Otherwise it's shadow complete
|
|
541
|
+
return {
|
|
542
|
+
quality: OUTPUT_QUALITY.SHADOW_COMPLETE,
|
|
543
|
+
reasons: productionBlockers,
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// ============================================================================
|
|
548
|
+
// Next Run Recommendation System
|
|
549
|
+
// ============================================================================
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* Next Run Types
|
|
553
|
+
*/
|
|
554
|
+
export const NEXT_RUN_TYPE = {
|
|
555
|
+
NONE: 'none', // No follow-up run needed (production_ready)
|
|
556
|
+
CONTINUATION: 'continuation', // Continue work in same stage/spec
|
|
557
|
+
VERIFY: 'verify', // Verify the output meets higher standard
|
|
558
|
+
HANDOFF: 'handoff', // Hand off to different spec/team
|
|
559
|
+
};
|
|
560
|
+
|
|
561
|
+
/**
|
|
562
|
+
* Determine the recommended next run based on output quality and spec configuration.
|
|
563
|
+
*
|
|
564
|
+
* This is a GENERIC recommendation system, not PR2-specific.
|
|
565
|
+
* The spec can define:
|
|
566
|
+
* - verificationSpec: A separate spec to run for verification
|
|
567
|
+
* - continuationSpec: A separate spec to run for continuation
|
|
568
|
+
* - requireVerify: Whether shadow_complete requires verification
|
|
569
|
+
*
|
|
570
|
+
* SEMANTICS:
|
|
571
|
+
* - NEEDS_WORK + outcome=revise → CONTINUATION (continue current work)
|
|
572
|
+
* - NEEDS_WORK + outcome=halt → CONTINUATION or HANDOFF (recover from failure)
|
|
573
|
+
* - SHADOW_COMPLETE + spec.verificationSpec → VERIFY (run verification spec)
|
|
574
|
+
* - SHADOW_COMPLETE + no verificationSpec → CONTINUATION (improve to production_ready)
|
|
575
|
+
* - PRODUCTION_READY → NONE (no follow-up needed)
|
|
576
|
+
*
|
|
577
|
+
* @param {string} outputQuality - The output quality level
|
|
578
|
+
* @param {string} outcome - The stage outcome (advance/revise/halt)
|
|
579
|
+
* @param {Object} spec - The task spec
|
|
580
|
+
* @param {Object} options - Additional options
|
|
581
|
+
* @param {string[]} options.qualityReasons - Reasons for the quality level
|
|
582
|
+
* @returns {{type: string, spec: string|null, reasons: string[]}}
|
|
583
|
+
*/
|
|
584
|
+
export function determineNextRunRecommendation(outputQuality, outcome, spec = {}, options = {}) {
|
|
585
|
+
const { qualityReasons = [] } = options;
|
|
586
|
+
|
|
587
|
+
// PRODUCTION_READY: No follow-up needed
|
|
588
|
+
if (outputQuality === OUTPUT_QUALITY.PRODUCTION_READY) {
|
|
589
|
+
return {
|
|
590
|
+
type: NEXT_RUN_TYPE.NONE,
|
|
591
|
+
spec: null,
|
|
592
|
+
reasons: ['Output is production-ready. No follow-up run needed.'],
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// NEEDS_WORK: Requires continuation
|
|
597
|
+
if (outputQuality === OUTPUT_QUALITY.NEEDS_WORK) {
|
|
598
|
+
// If halted, might need handoff or fresh start
|
|
599
|
+
if (outcome === 'halt') {
|
|
600
|
+
// Check if spec defines a recovery spec
|
|
601
|
+
if (spec?.recoverySpec) {
|
|
602
|
+
return {
|
|
603
|
+
type: NEXT_RUN_TYPE.HANDOFF,
|
|
604
|
+
spec: spec.recoverySpec,
|
|
605
|
+
reasons: [
|
|
606
|
+
'Stage halted without completion.',
|
|
607
|
+
...qualityReasons,
|
|
608
|
+
`Consider recovery spec: ${spec.recoverySpec}`,
|
|
609
|
+
],
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
// Otherwise recommend continuation to retry
|
|
613
|
+
return {
|
|
614
|
+
type: NEXT_RUN_TYPE.CONTINUATION,
|
|
615
|
+
spec: spec?.continuationSpec || null,
|
|
616
|
+
reasons: [
|
|
617
|
+
'Stage halted without completion.',
|
|
618
|
+
...qualityReasons,
|
|
619
|
+
'Recommend retry with fresh context or adjusted parameters.',
|
|
620
|
+
],
|
|
621
|
+
};
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// revise or other: continue current work
|
|
625
|
+
return {
|
|
626
|
+
type: NEXT_RUN_TYPE.CONTINUATION,
|
|
627
|
+
spec: spec?.continuationSpec || null,
|
|
628
|
+
reasons: [
|
|
629
|
+
'Output needs additional work.',
|
|
630
|
+
...qualityReasons,
|
|
631
|
+
spec?.continuationSpec
|
|
632
|
+
? `Recommend continuation spec: ${spec.continuationSpec}`
|
|
633
|
+
: 'Continue with current spec.',
|
|
634
|
+
],
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
// SHADOW_COMPLETE: Check if verification is required
|
|
639
|
+
if (outputQuality === OUTPUT_QUALITY.SHADOW_COMPLETE) {
|
|
640
|
+
// If spec defines a verification spec, recommend verify
|
|
641
|
+
if (spec?.verificationSpec) {
|
|
642
|
+
return {
|
|
643
|
+
type: NEXT_RUN_TYPE.VERIFY,
|
|
644
|
+
spec: spec.verificationSpec,
|
|
645
|
+
reasons: [
|
|
646
|
+
'Output is shadow-complete but not production-ready.',
|
|
647
|
+
...qualityReasons,
|
|
648
|
+
`Recommend verification spec: ${spec.verificationSpec}`,
|
|
649
|
+
],
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
// If spec explicitly requires verification for shadow_complete
|
|
654
|
+
if (spec?.requireVerify === true) {
|
|
655
|
+
return {
|
|
656
|
+
type: NEXT_RUN_TYPE.VERIFY,
|
|
657
|
+
spec: null, // Use same spec but in verify mode
|
|
658
|
+
reasons: [
|
|
659
|
+
'Output is shadow-complete. Verification required by spec.',
|
|
660
|
+
...qualityReasons,
|
|
661
|
+
],
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
// No verification defined: recommend continuation to reach production_ready
|
|
666
|
+
return {
|
|
667
|
+
type: NEXT_RUN_TYPE.CONTINUATION,
|
|
668
|
+
spec: spec?.continuationSpec || null,
|
|
669
|
+
reasons: [
|
|
670
|
+
'Output is shadow-complete but not production-ready.',
|
|
671
|
+
...qualityReasons,
|
|
672
|
+
'Recommend additional work to reach production-ready status.',
|
|
673
|
+
],
|
|
674
|
+
};
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
// Fallback (should not reach here)
|
|
678
|
+
return {
|
|
679
|
+
type: NEXT_RUN_TYPE.NONE,
|
|
680
|
+
spec: null,
|
|
681
|
+
reasons: ['Unknown output quality level.'],
|
|
682
|
+
};
|
|
683
|
+
}
|