@besales/ops-framework 0.1.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 (70) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +328 -0
  3. package/bin/build-check-context.mjs +67 -0
  4. package/bin/build-execution-ledger.mjs +54 -0
  5. package/bin/estimate-llm-input.mjs +160 -0
  6. package/bin/guard-task.mjs +384 -0
  7. package/bin/hash-task-artifacts.mjs +44 -0
  8. package/bin/init-project.mjs +49 -0
  9. package/bin/intake-execution-feedback.mjs +207 -0
  10. package/bin/intake-feedback.test.mjs +73 -0
  11. package/bin/learning-loop.mjs +658 -0
  12. package/bin/learning-loop.test.mjs +175 -0
  13. package/bin/lib/bootstrap-utils.mjs +542 -0
  14. package/bin/lib/bootstrap-utils.test.mjs +156 -0
  15. package/bin/lib/check-context-utils.mjs +1448 -0
  16. package/bin/lib/check-context-utils.test.mjs +497 -0
  17. package/bin/lib/execution-ledger-utils.mjs +162 -0
  18. package/bin/lib/execution-ledger-utils.test.mjs +74 -0
  19. package/bin/lib/llm-input-pack-utils.mjs +663 -0
  20. package/bin/lib/llm-input-pack-utils.test.mjs +262 -0
  21. package/bin/lib/project-config.mjs +229 -0
  22. package/bin/lib/project-config.test.mjs +102 -0
  23. package/bin/lib/task-manifest-utils.mjs +512 -0
  24. package/bin/lib/task-manifest-utils.test.mjs +218 -0
  25. package/bin/lib/task-metrics-utils.mjs +63 -0
  26. package/bin/lib/task-metrics-utils.test.mjs +40 -0
  27. package/bin/lib/test-setup.mjs +37 -0
  28. package/bin/new-task.mjs +42 -0
  29. package/bin/ops-agent.mjs +81 -0
  30. package/bin/preflight.mjs +56 -0
  31. package/bin/providers/external-cli-checker.mjs +190 -0
  32. package/bin/providers/openai-checker.mjs +62 -0
  33. package/bin/quality-gates.mjs +92 -0
  34. package/bin/run-check.mjs +559 -0
  35. package/bin/run-plan-check-loop.mjs +392 -0
  36. package/bin/run-verify.mjs +627 -0
  37. package/bin/self-lint.mjs +88 -0
  38. package/bin/supervisor-turn.mjs +146 -0
  39. package/bin/supervisor-turn.test.mjs +72 -0
  40. package/bin/task-manifest.mjs +57 -0
  41. package/bin/task-metrics.mjs +48 -0
  42. package/bin/transition.mjs +94 -0
  43. package/bin/validate-check-artifacts.mjs +418 -0
  44. package/config/default-agents.json +100 -0
  45. package/package.json +28 -0
  46. package/playbooks/checker-context.md +9 -0
  47. package/playbooks/complexity-performance.md +13 -0
  48. package/playbooks/production-rollout.md +9 -0
  49. package/playbooks/source-sync-provider.md +9 -0
  50. package/playbooks/ui-acceptance.md +9 -0
  51. package/prompts/checker.md +170 -0
  52. package/prompts/executor.md +54 -0
  53. package/prompts/planner.md +128 -0
  54. package/prompts/researcher.md +44 -0
  55. package/prompts/supervisor.md +337 -0
  56. package/prompts/verifier.md +128 -0
  57. package/templates/brief.md +15 -0
  58. package/templates/check-resolution.md +69 -0
  59. package/templates/check-result.json +32 -0
  60. package/templates/check.md +46 -0
  61. package/templates/execution-feedback.md +25 -0
  62. package/templates/execution.md +101 -0
  63. package/templates/human-gate-summary.md +49 -0
  64. package/templates/orchestration-log.md +8 -0
  65. package/templates/plan.md +86 -0
  66. package/templates/research.md +13 -0
  67. package/templates/retrospective.md +48 -0
  68. package/templates/status.md +53 -0
  69. package/templates/verify-result.json +19 -0
  70. package/templates/verify.md +41 -0
@@ -0,0 +1,418 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import {
4
+ ALLOWED_CLAIM_CATEGORIES,
5
+ ALLOWED_FAILURE_REASONS,
6
+ ALLOWED_REF_TYPES,
7
+ ALLOWED_RESOLUTION_DECISIONS,
8
+ ALLOWED_RISK_PROFILES,
9
+ ALLOWED_RISK_TRIGGERS,
10
+ ALLOWED_SEVERITIES,
11
+ ALLOWED_VERDICTS,
12
+ RISK_CONFIG,
13
+ buildCheckContext,
14
+ computeTaskContextInputs,
15
+ readJsonFile,
16
+ resolveTaskDir,
17
+ } from './lib/check-context-utils.mjs';
18
+
19
+ const INVALID_JSON = Symbol('invalid-json');
20
+
21
+ function main() {
22
+ const taskArg = process.argv[2];
23
+ if (!taskArg) {
24
+ fail('Usage: node ops/agent-pipeline/bin/validate-check-artifacts.mjs <TASK-id-or-task-path> [--skip-resolution]');
25
+ }
26
+ const skipResolution = process.argv.includes('--skip-resolution');
27
+
28
+ const errors = [];
29
+ const warnings = [];
30
+
31
+ try {
32
+ const taskDir = resolveTaskDir(taskArg);
33
+ const taskId = path.basename(taskDir);
34
+ const inputs = computeTaskContextInputs(taskDir);
35
+ const expectedContext = buildCheckContext({
36
+ taskId,
37
+ inputs,
38
+ createdAt: null,
39
+ });
40
+
41
+ validateCheckContext({
42
+ taskDir,
43
+ expectedContext,
44
+ errors,
45
+ });
46
+
47
+ const result = readOptionalJson(path.join(taskDir, 'check.result.json'), errors);
48
+ if (result === null) {
49
+ warnings.push('Check incomplete: check.result.json is missing.');
50
+ } else if (result !== INVALID_JSON) {
51
+ validateCheckResult({
52
+ result,
53
+ expectedContext,
54
+ qualityGates: inputs.qualityGates,
55
+ errors,
56
+ });
57
+
58
+ if (result.verdict === 'return_to_plan' && !skipResolution) {
59
+ validateCheckResolution({
60
+ taskDir,
61
+ result,
62
+ errors,
63
+ });
64
+ }
65
+ }
66
+
67
+ printResult({ taskId, errors, warnings });
68
+ } catch (error) {
69
+ errors.push(error.message);
70
+ printResult({ taskId: 'unknown', errors, warnings });
71
+ }
72
+ }
73
+
74
+ function validateCheckContext({ taskDir, expectedContext, errors }) {
75
+ const contextPath = path.join(taskDir, 'check-context.json');
76
+ if (!fs.existsSync(contextPath)) {
77
+ errors.push('Missing check-context.json. Run agent:build-check-context first.');
78
+ return;
79
+ }
80
+
81
+ let context;
82
+ try {
83
+ context = readJsonFile(contextPath);
84
+ } catch (error) {
85
+ errors.push(`Invalid check-context.json: ${error.message}`);
86
+ return;
87
+ }
88
+
89
+ requireString(context, 'taskId', errors);
90
+ expectEqual(context.taskId, expectedContext.taskId, 'check-context.taskId', errors);
91
+ expectEqual(context.stage, 'Check', 'check-context.stage', errors);
92
+ expectEqual(context.planSha, expectedContext.planSha, 'check-context.planSha', errors);
93
+ expectEqual(context.planFingerprintVersion, expectedContext.planFingerprintVersion, 'check-context.planFingerprintVersion', errors);
94
+ expectEqual(context.memorySha, expectedContext.memorySha, 'check-context.memorySha', errors);
95
+ expectArrayEqual(context.riskTriggers, expectedContext.riskTriggers, 'check-context.riskTriggers', errors);
96
+ expectEqual(context.riskProfile, expectedContext.riskProfile, 'check-context.riskProfile', errors);
97
+ expectEqual(context.memoryDeliveryMode, 'inline', 'check-context.memoryDeliveryMode', errors);
98
+ expectEqual(context.contextBudgetTokens, RISK_CONFIG[expectedContext.riskProfile].contextBudgetTokens, 'check-context.contextBudgetTokens', errors);
99
+ expectEqual(context.maxRepoReads, RISK_CONFIG[expectedContext.riskProfile].maxRepoReads, 'check-context.maxRepoReads', errors);
100
+ expectEqual(context.evidenceFile, 'check-evidence.md', 'check-context.evidenceFile', errors);
101
+ expectEqual(context.checkerContextPackFile, expectedContext.checkerContextPackFile, 'check-context.checkerContextPackFile', errors);
102
+ expectEqual(context.checkerContextPackSha, expectedContext.checkerContextPackSha, 'check-context.checkerContextPackSha', errors);
103
+ expectArrayEqual(context.referencedFiles, expectedContext.referencedFiles, 'check-context.referencedFiles', errors);
104
+
105
+ validateRiskProfile(context.riskProfile, errors, 'check-context.riskProfile');
106
+ validateRiskTriggers(context.riskTriggers, errors, 'check-context.riskTriggers');
107
+ expectObjectArrayEqual(context.memoryFiles, expectedContext.memoryFiles, 'check-context.memoryFiles', errors);
108
+ expectObjectArrayEqual(context.taskArtifacts, expectedContext.taskArtifacts, 'check-context.taskArtifacts', errors);
109
+ validateSha(context.planSha, errors, 'check-context.planSha');
110
+ validateSha(context.memorySha, errors, 'check-context.memorySha');
111
+ validateSha(context.checkerContextPackSha, errors, 'check-context.checkerContextPackSha');
112
+ if (context.checkerContextPackFile && !fs.existsSync(path.join(taskDir, context.checkerContextPackFile))) {
113
+ errors.push(`Missing ${context.checkerContextPackFile}. Run agent:build-check-context first.`);
114
+ }
115
+ }
116
+
117
+ function validateCheckResult({ result, expectedContext, qualityGates, errors }) {
118
+ requireString(result, 'checkerProvider', errors, 'check.result');
119
+ requireString(result, 'checkerModel', errors, 'check.result');
120
+ requireString(result, 'createdAt', errors, 'check.result');
121
+ expectEqual(result.stage, 'Check', 'check.result.stage', errors);
122
+ expectEqual(result.taskId, expectedContext.taskId, 'check.result.taskId', errors);
123
+ expectEqual(result.planSha, expectedContext.planSha, 'check.result.planSha', errors);
124
+ expectEqual(result.memorySha, expectedContext.memorySha, 'check.result.memorySha', errors);
125
+ expectEqual(result.riskProfile, expectedContext.riskProfile, 'check.result.riskProfile', errors);
126
+ validateSha(result.planSha, errors, 'check.result.planSha');
127
+ validateSha(result.memorySha, errors, 'check.result.memorySha');
128
+
129
+ if (!ALLOWED_VERDICTS.includes(result.verdict)) {
130
+ errors.push(`check.result.verdict is not allowed: ${formatValue(result.verdict)}`);
131
+ }
132
+
133
+ if (result.verdict === 'checker_failed') {
134
+ if (!ALLOWED_FAILURE_REASONS.includes(result.failureReason)) {
135
+ errors.push(`check.result.failureReason is required and must be allowed when verdict=checker_failed: ${formatValue(result.failureReason)}`);
136
+ }
137
+ } else if (result.verdict === 'context_insufficient') {
138
+ if (result.failureReason !== null && result.failureReason !== 'context_insufficient') {
139
+ errors.push('check.result.failureReason must be null or context_insufficient when verdict=context_insufficient.');
140
+ }
141
+ } else if (result.failureReason !== null) {
142
+ errors.push('check.result.failureReason must be null unless verdict=checker_failed.');
143
+ }
144
+
145
+ if (!Array.isArray(result.findings)) {
146
+ errors.push('check.result.findings must be an array.');
147
+ return;
148
+ }
149
+
150
+ const seenIds = new Set();
151
+ result.findings.forEach((finding, index) => {
152
+ validateFinding(finding, index, seenIds, errors);
153
+ });
154
+
155
+ const blockingFindings = result.findings.filter((finding) => finding.severity === 'blocking').length;
156
+ const nonBlockingFindings = result.findings.filter((finding) => finding.severity === 'non_blocking').length;
157
+ const humanQuestions = result.findings.filter((finding) => finding.severity === 'question').length;
158
+ expectEqual(result.blockingFindings, blockingFindings, 'check.result.blockingFindings', errors);
159
+ expectEqual(result.nonBlockingFindings, nonBlockingFindings, 'check.result.nonBlockingFindings', errors);
160
+ expectEqual(result.humanQuestions, humanQuestions, 'check.result.humanQuestions', errors);
161
+
162
+ const expectedReady = result.verdict === 'ready_for_human_gate';
163
+ expectEqual(result.readyForHumanGate, expectedReady, 'check.result.readyForHumanGate', errors);
164
+
165
+ if (result.verdict === 'ready_for_human_gate' && blockingFindings > 0) {
166
+ errors.push('check.result.verdict cannot be ready_for_human_gate while blocking findings are present.');
167
+ }
168
+ if (result.verdict === 'ready_for_human_gate' && qualityGates?.missingSignals?.length > 0) {
169
+ for (const signal of qualityGates.missingSignals) {
170
+ errors.push(`check.result.verdict cannot be ready_for_human_gate while deterministic quality gate is missing: ${signal}`);
171
+ }
172
+ }
173
+ if (result.verdict === 'return_to_plan' && blockingFindings === 0) {
174
+ errors.push('check.result.verdict return_to_plan requires at least one blocking finding.');
175
+ }
176
+ }
177
+
178
+ function validateFinding(finding, index, seenIds, errors) {
179
+ const prefix = `check.result.findings[${index}]`;
180
+ if (!finding || typeof finding !== 'object' || Array.isArray(finding)) {
181
+ errors.push(`${prefix} must be an object.`);
182
+ return;
183
+ }
184
+
185
+ const expectedId = `F-${String(index + 1).padStart(3, '0')}`;
186
+ if (!/^F-\d{3}$/.test(finding.id ?? '')) {
187
+ errors.push(`${prefix}.id must match F-001 format.`);
188
+ } else if (finding.id !== expectedId) {
189
+ errors.push(`${prefix}.id must be consecutive; expected ${expectedId}, got ${finding.id}.`);
190
+ } else if (seenIds.has(finding.id)) {
191
+ errors.push(`${prefix}.id duplicates ${finding.id}.`);
192
+ } else {
193
+ seenIds.add(finding.id);
194
+ }
195
+
196
+ if (!ALLOWED_SEVERITIES.includes(finding.severity)) {
197
+ errors.push(`${prefix}.severity is not allowed: ${formatValue(finding.severity)}`);
198
+ }
199
+ if (!ALLOWED_CLAIM_CATEGORIES.includes(finding.claimCategory)) {
200
+ errors.push(`${prefix}.claimCategory is not allowed: ${formatValue(finding.claimCategory)}`);
201
+ }
202
+ requireString(finding, 'claim', errors, prefix);
203
+ requireString(finding, 'expectedCorrection', errors, prefix);
204
+ validateRefs(finding.evidenceRefs, `${prefix}.evidenceRefs`, errors, {
205
+ allowEmpty: finding.severity === 'blocking' && finding.claimCategory === 'missing_evidence',
206
+ });
207
+ if (!Array.isArray(finding.affectedPlanSections) || finding.affectedPlanSections.some((value) => typeof value !== 'string' || !value.trim())) {
208
+ errors.push(`${prefix}.affectedPlanSections must be a non-empty string array.`);
209
+ }
210
+ }
211
+
212
+ function validateCheckResolution({ taskDir, result, errors }) {
213
+ const resolutionPath = path.join(taskDir, 'check-resolution.md');
214
+ if (!fs.existsSync(resolutionPath)) {
215
+ errors.push('check.result verdict is return_to_plan but check-resolution.md is missing.');
216
+ return;
217
+ }
218
+
219
+ const resolutionJson = extractStructuredResolution(fs.readFileSync(resolutionPath, 'utf8'));
220
+ if (!resolutionJson) {
221
+ errors.push('check-resolution.md must contain a fenced JSON block after "Structured Resolution".');
222
+ return;
223
+ }
224
+
225
+ let resolution;
226
+ try {
227
+ resolution = JSON.parse(resolutionJson);
228
+ } catch (error) {
229
+ errors.push(`Invalid Structured Resolution JSON: ${error.message}`);
230
+ return;
231
+ }
232
+
233
+ expectEqual(resolution.taskId, result.taskId, 'check-resolution.taskId', errors);
234
+ expectEqual(resolution.checkerPlanSha, result.planSha, 'check-resolution.checkerPlanSha', errors);
235
+ validateSha(resolution.checkerPlanSha, errors, 'check-resolution.checkerPlanSha');
236
+
237
+ if (!Array.isArray(resolution.responses)) {
238
+ errors.push('check-resolution Structured Resolution responses must be an array.');
239
+ return;
240
+ }
241
+
242
+ const blockingFindingIds = new Set(result.findings.filter((item) => item.severity === 'blocking').map((finding) => finding.id));
243
+ const responsesByFindingId = new Map();
244
+ resolution.responses.forEach((response, index) => {
245
+ validateResolutionResponse(response, index, responsesByFindingId, errors);
246
+ if (response?.findingId && !blockingFindingIds.has(response.findingId)) {
247
+ errors.push(`check-resolution response references non-blocking or unknown finding ${response.findingId}.`);
248
+ }
249
+ });
250
+
251
+ for (const findingId of blockingFindingIds) {
252
+ if (!responsesByFindingId.has(findingId)) {
253
+ errors.push(`Missing check-resolution response for blocking finding ${findingId}.`);
254
+ }
255
+ }
256
+ }
257
+
258
+ function validateResolutionResponse(response, index, responsesByFindingId, errors) {
259
+ const prefix = `check-resolution.responses[${index}]`;
260
+ if (!response || typeof response !== 'object' || Array.isArray(response)) {
261
+ errors.push(`${prefix} must be an object.`);
262
+ return;
263
+ }
264
+
265
+ if (!/^F-\d{3}$/.test(response.findingId ?? '')) {
266
+ errors.push(`${prefix}.findingId must match F-001 format.`);
267
+ } else if (responsesByFindingId.has(response.findingId)) {
268
+ errors.push(`${prefix}.findingId duplicates ${response.findingId}.`);
269
+ } else {
270
+ responsesByFindingId.set(response.findingId, response);
271
+ }
272
+
273
+ if (!ALLOWED_RESOLUTION_DECISIONS.includes(response.decision)) {
274
+ errors.push(`${prefix}.decision is not allowed: ${formatValue(response.decision)}`);
275
+ }
276
+
277
+ requireString(response, 'rationale', errors, prefix);
278
+
279
+ if (response.decision === 'rejected') {
280
+ validateRefs(response.evidenceRefs, `${prefix}.evidenceRefs`, errors);
281
+ }
282
+ if (response.decision === 'accepted') {
283
+ validateRefs(response.artifactChangeRefs, `${prefix}.artifactChangeRefs`, errors);
284
+ }
285
+ if (response.decision === 'partially_accepted') {
286
+ validateRefs(response.artifactChangeRefs, `${prefix}.artifactChangeRefs`, errors);
287
+ validateRefs(response.evidenceRefs, `${prefix}.evidenceRefs`, errors);
288
+ }
289
+ if ((response.decision === 'needs_research' || response.decision === 'needs_human_decision') && !String(response.rationale ?? '').trim()) {
290
+ errors.push(`${prefix}.rationale is required for ${response.decision}.`);
291
+ }
292
+ }
293
+
294
+ function validateRefs(refs, label, errors, options = {}) {
295
+ const allowEmpty = options.allowEmpty === true;
296
+ if (!Array.isArray(refs)) {
297
+ errors.push(`${label} must be an array.`);
298
+ return;
299
+ }
300
+ if (!allowEmpty && refs.length === 0) {
301
+ errors.push(`${label} must not be empty.`);
302
+ return;
303
+ }
304
+
305
+ refs.forEach((ref, index) => {
306
+ const prefix = `${label}[${index}]`;
307
+ if (!ref || typeof ref !== 'object' || Array.isArray(ref)) {
308
+ errors.push(`${prefix} must be an object.`);
309
+ return;
310
+ }
311
+ if (!ALLOWED_REF_TYPES.includes(ref.type)) {
312
+ errors.push(`${prefix}.type is not allowed: ${formatValue(ref.type)}`);
313
+ }
314
+ requireString(ref, 'ref', errors, prefix);
315
+ });
316
+ }
317
+
318
+ function extractStructuredResolution(markdown) {
319
+ const markerIndex = markdown.indexOf('## Structured Resolution');
320
+ if (markerIndex === -1) {
321
+ return null;
322
+ }
323
+
324
+ const afterMarker = markdown.slice(markerIndex);
325
+ const match = /```json\s*([\s\S]*?)```/.exec(afterMarker);
326
+ return match?.[1] ?? null;
327
+ }
328
+
329
+ function readOptionalJson(filePath, errors) {
330
+ if (!fs.existsSync(filePath)) {
331
+ return null;
332
+ }
333
+ try {
334
+ return readJsonFile(filePath);
335
+ } catch (error) {
336
+ errors.push(`Invalid ${path.basename(filePath)}: ${error.message}`);
337
+ return INVALID_JSON;
338
+ }
339
+ }
340
+
341
+ function validateRiskProfile(value, errors, label) {
342
+ if (!ALLOWED_RISK_PROFILES.includes(value)) {
343
+ errors.push(`${label} is not allowed: ${formatValue(value)}`);
344
+ }
345
+ }
346
+
347
+ function validateRiskTriggers(value, errors, label) {
348
+ if (!Array.isArray(value)) {
349
+ errors.push(`${label} must be an array.`);
350
+ return;
351
+ }
352
+ for (const trigger of value) {
353
+ if (!ALLOWED_RISK_TRIGGERS.includes(trigger)) {
354
+ errors.push(`${label} contains unknown trigger: ${formatValue(trigger)}`);
355
+ }
356
+ }
357
+ }
358
+
359
+ function requireString(object, key, errors, prefix = '') {
360
+ const label = prefix ? `${prefix}.${key}` : key;
361
+ if (typeof object?.[key] !== 'string' || !object[key].trim()) {
362
+ errors.push(`${label} must be a non-empty string.`);
363
+ }
364
+ }
365
+
366
+ function validateSha(value, errors, label) {
367
+ if (typeof value !== 'string' || !/^sha256:[a-f0-9]{64}$/.test(value)) {
368
+ errors.push(`${label} must match sha256:<64 lowercase hex chars>.`);
369
+ }
370
+ }
371
+
372
+ function expectEqual(actual, expected, label, errors) {
373
+ if (actual !== expected) {
374
+ errors.push(`${label} mismatch: expected ${formatValue(expected)}, got ${formatValue(actual)}.`);
375
+ }
376
+ }
377
+
378
+ function expectArrayEqual(actual, expected, label, errors) {
379
+ if (!Array.isArray(actual)) {
380
+ errors.push(`${label} must be an array.`);
381
+ return;
382
+ }
383
+
384
+ if (JSON.stringify(actual) !== JSON.stringify(expected)) {
385
+ errors.push(`${label} mismatch.`);
386
+ }
387
+ }
388
+
389
+ function expectObjectArrayEqual(actual, expected, label, errors) {
390
+ expectArrayEqual(actual, expected, label, errors);
391
+ }
392
+
393
+ function formatValue(value) {
394
+ return JSON.stringify(value);
395
+ }
396
+
397
+ function printResult({ taskId, errors, warnings }) {
398
+ for (const warning of warnings) {
399
+ console.warn(`Warning: ${warning}`);
400
+ }
401
+
402
+ if (errors.length > 0) {
403
+ console.error(`Check artifact validation failed for ${taskId}:`);
404
+ for (const error of errors) {
405
+ console.error(`- ${error}`);
406
+ }
407
+ process.exit(1);
408
+ }
409
+
410
+ console.log(`Check artifact validation ok for ${taskId}`);
411
+ }
412
+
413
+ function fail(message) {
414
+ console.error(`Error: ${message}`);
415
+ process.exit(1);
416
+ }
417
+
418
+ main();
@@ -0,0 +1,100 @@
1
+ {
2
+ "planner": {
3
+ "provider": "openai",
4
+ "model": "${PLANNER_MODEL}"
5
+ },
6
+ "checker": {
7
+ "provider": "codex-cli",
8
+ "model": "gpt-5.5",
9
+ "reasoningEffort": "medium",
10
+ "isolatedContext": true,
11
+ "readOnly": true
12
+ },
13
+ "verifier": {
14
+ "mode": "internal_supervisor",
15
+ "provider": "codex-cli",
16
+ "model": "gpt-5.5",
17
+ "reasoningEffort": "medium",
18
+ "isolatedContext": true,
19
+ "readOnly": true
20
+ },
21
+ "checkerProviders": {
22
+ "codex-cli": {
23
+ "command": "${CODEX_CLI_COMMAND}",
24
+ "model": "gpt-5.5",
25
+ "reasoningEffort": "medium",
26
+ "args": [
27
+ "exec",
28
+ "--sandbox",
29
+ "read-only",
30
+ "-c",
31
+ "model_reasoning_effort=\"$REASONING_EFFORT\"",
32
+ "--output-last-message",
33
+ "$OUTPUT_FILE",
34
+ "--model",
35
+ "$MODEL",
36
+ "-"
37
+ ],
38
+ "input": "stdin",
39
+ "output": "file"
40
+ },
41
+ "claude-cli": {
42
+ "command": "${CLAUDE_CLI_COMMAND}",
43
+ "model": "claude-opus-4-7",
44
+ "reasoningEffort": "medium",
45
+ "args": [
46
+ "-p",
47
+ "--no-session-persistence",
48
+ "--tools",
49
+ "",
50
+ "--model",
51
+ "$MODEL",
52
+ "--effort",
53
+ "$REASONING_EFFORT"
54
+ ],
55
+ "input": "stdin",
56
+ "output": "stdout"
57
+ },
58
+ "cloud-cli": {
59
+ "command": "${CLOUD_CLI_COMMAND}",
60
+ "model": "claude-opus-4-7",
61
+ "reasoningEffort": "medium",
62
+ "args": [
63
+ "-p",
64
+ "--no-session-persistence",
65
+ "--tools",
66
+ "",
67
+ "--model",
68
+ "$MODEL",
69
+ "--effort",
70
+ "$REASONING_EFFORT"
71
+ ],
72
+ "input": "stdin",
73
+ "output": "stdout"
74
+ },
75
+ "custom-cli": {
76
+ "command": "${CHECKER_CLI_COMMAND}",
77
+ "args": [],
78
+ "input": "stdin",
79
+ "output": "stdout"
80
+ }
81
+ },
82
+ "planCheckLoop": {
83
+ "maxIterations": {
84
+ "low": 2,
85
+ "medium": 3,
86
+ "high": 5
87
+ },
88
+ "defaultRiskProfile": "medium",
89
+ "contextBudgetTokens": {
90
+ "low": 6000,
91
+ "medium": 15000,
92
+ "high": 30000
93
+ },
94
+ "maxRepoReads": {
95
+ "low": 5,
96
+ "medium": 12,
97
+ "high": 25
98
+ }
99
+ }
100
+ }
package/package.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "@besales/ops-framework",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "bin": {
6
+ "ops-agent": "bin/ops-agent.mjs"
7
+ },
8
+ "publishConfig": {
9
+ "access": "public"
10
+ },
11
+ "files": [
12
+ "bin",
13
+ "config",
14
+ "playbooks",
15
+ "prompts",
16
+ "templates",
17
+ "README.md",
18
+ "CHANGELOG.md"
19
+ ],
20
+ "scripts": {
21
+ "test": "vitest run --config vitest.config.ts",
22
+ "lint": "node bin/self-lint.mjs",
23
+ "lint:fix": "node bin/self-lint.mjs --fix"
24
+ },
25
+ "devDependencies": {
26
+ "vitest": "^3.1.1"
27
+ }
28
+ }
@@ -0,0 +1,9 @@
1
+ # Checker Context Playbook
2
+
3
+ Use this to keep independent checks cheap and accurate.
4
+
5
+ - `checker-context-pack.md` is the compact decision packet for Checker.
6
+ - It must name decision, active slice, risk triggers, required repo files, prior failures and exact questions.
7
+ - Checker should read only the listed files first, then expand context only when a claim cannot be judged.
8
+ - If the pack is stale, regenerate it before Check or transition.
9
+ - `task-manifest.json` records mode, phase, gates and loop detector status for automation.
@@ -0,0 +1,13 @@
1
+ # Complexity / Performance Playbook
2
+
3
+ Use this for read models, materializers, workers, table/dashboard UI, replay/backfill and production hot paths.
4
+
5
+ - Plan must include `## Complexity / Performance Budget`.
6
+ - Name hot paths, expected data size or row count, complexity risks, mitigation and budget/stop rule.
7
+ - O2/O3 hot-path work must also include `## Optimization Strategy`.
8
+ - O0: none; O1: quick checklist; O2: focused optimizer review on touched hot paths; O3: measured review with timing/rows/EXPLAIN/benchmark.
9
+ - Keep optimization bounded: one focused pass for O2, one focused pass plus one representative measurement for O3; defer nice-to-have ideas.
10
+ - Prefer bounded queries, maps/sets, batching and indexed access over repeated scans or N+1 calls.
11
+ - Execution must include `## Complexity / Performance Evidence`.
12
+ - Execution must include `## Optimization Review Evidence` when O2/O3 strategy is present.
13
+ - Acceptable evidence includes timing, row counts, EXPLAIN, no-N+1 review, hot-path code review or representative benchmark.
@@ -0,0 +1,9 @@
1
+ # Production Rollout Playbook
2
+
3
+ Use this for migrations, env vars, cron/workers, billing/auth, external APIs, deploy/runtime behavior and production-readiness claims.
4
+
5
+ - Plan must include `## Production Rollout Gate`.
6
+ - Name impact/blast radius, environment or deploy variables, rollback/disable path and post-deploy evidence.
7
+ - Do not hide production risk behind a generic smoke test.
8
+ - Execution must include `## Production Rollout Evidence`.
9
+ - Acceptable evidence includes deploy/env facts, logs, metrics, smoke results, monitor links or explicit rollback verification.
@@ -0,0 +1,9 @@
1
+ # Source Sync / Provider Playbook
2
+
3
+ Use this for sync/import/provider pipelines, raw records, retries, pagination, rate limits, idempotency, replay/backfill and partial failure recovery.
4
+
5
+ - Plan must include `## Source Sync / Provider Gate`.
6
+ - Name scope/provider window, idempotency or duplicate handling, retry/failure boundaries and coverage/parity evidence.
7
+ - Keep provider credentials, queue ownership and affected windows explicit.
8
+ - Execution must include `## Source Sync / Provider Evidence`.
9
+ - Acceptable evidence includes raw-record samples, counts/parity, replay/audit output, retry/idempotency checks and partial-failure recovery proof.
@@ -0,0 +1,9 @@
1
+ # UI Acceptance Playbook
2
+
3
+ Use this when a task touches configured frontend/UI roots or any user-visible API contract.
4
+
5
+ - Plan must include `## UI Acceptance Scenarios`.
6
+ - Each scenario needs `UI-xxx`, user intent, setup/data, steps, expected visible result and must-catch regression.
7
+ - Page-load-only smoke is not enough.
8
+ - Execution must include `## UI Acceptance Evidence` with scenario id, pass/fail/blocked/human-owned result and observed screenshot/payload/rendered evidence.
9
+ - If browser/auth/env is blocked, record API/payload/rendered fallback or mark the scenario human-owned.