@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.
- package/CHANGELOG.md +10 -0
- package/README.md +328 -0
- package/bin/build-check-context.mjs +67 -0
- package/bin/build-execution-ledger.mjs +54 -0
- package/bin/estimate-llm-input.mjs +160 -0
- package/bin/guard-task.mjs +384 -0
- package/bin/hash-task-artifacts.mjs +44 -0
- package/bin/init-project.mjs +49 -0
- package/bin/intake-execution-feedback.mjs +207 -0
- package/bin/intake-feedback.test.mjs +73 -0
- package/bin/learning-loop.mjs +658 -0
- package/bin/learning-loop.test.mjs +175 -0
- package/bin/lib/bootstrap-utils.mjs +542 -0
- package/bin/lib/bootstrap-utils.test.mjs +156 -0
- package/bin/lib/check-context-utils.mjs +1448 -0
- package/bin/lib/check-context-utils.test.mjs +497 -0
- package/bin/lib/execution-ledger-utils.mjs +162 -0
- package/bin/lib/execution-ledger-utils.test.mjs +74 -0
- package/bin/lib/llm-input-pack-utils.mjs +663 -0
- package/bin/lib/llm-input-pack-utils.test.mjs +262 -0
- package/bin/lib/project-config.mjs +229 -0
- package/bin/lib/project-config.test.mjs +102 -0
- package/bin/lib/task-manifest-utils.mjs +512 -0
- package/bin/lib/task-manifest-utils.test.mjs +218 -0
- package/bin/lib/task-metrics-utils.mjs +63 -0
- package/bin/lib/task-metrics-utils.test.mjs +40 -0
- package/bin/lib/test-setup.mjs +37 -0
- package/bin/new-task.mjs +42 -0
- package/bin/ops-agent.mjs +81 -0
- package/bin/preflight.mjs +56 -0
- package/bin/providers/external-cli-checker.mjs +190 -0
- package/bin/providers/openai-checker.mjs +62 -0
- package/bin/quality-gates.mjs +92 -0
- package/bin/run-check.mjs +559 -0
- package/bin/run-plan-check-loop.mjs +392 -0
- package/bin/run-verify.mjs +627 -0
- package/bin/self-lint.mjs +88 -0
- package/bin/supervisor-turn.mjs +146 -0
- package/bin/supervisor-turn.test.mjs +72 -0
- package/bin/task-manifest.mjs +57 -0
- package/bin/task-metrics.mjs +48 -0
- package/bin/transition.mjs +94 -0
- package/bin/validate-check-artifacts.mjs +418 -0
- package/config/default-agents.json +100 -0
- package/package.json +28 -0
- package/playbooks/checker-context.md +9 -0
- package/playbooks/complexity-performance.md +13 -0
- package/playbooks/production-rollout.md +9 -0
- package/playbooks/source-sync-provider.md +9 -0
- package/playbooks/ui-acceptance.md +9 -0
- package/prompts/checker.md +170 -0
- package/prompts/executor.md +54 -0
- package/prompts/planner.md +128 -0
- package/prompts/researcher.md +44 -0
- package/prompts/supervisor.md +337 -0
- package/prompts/verifier.md +128 -0
- package/templates/brief.md +15 -0
- package/templates/check-resolution.md +69 -0
- package/templates/check-result.json +32 -0
- package/templates/check.md +46 -0
- package/templates/execution-feedback.md +25 -0
- package/templates/execution.md +101 -0
- package/templates/human-gate-summary.md +49 -0
- package/templates/orchestration-log.md +8 -0
- package/templates/plan.md +86 -0
- package/templates/research.md +13 -0
- package/templates/retrospective.md +48 -0
- package/templates/status.md +53 -0
- package/templates/verify-result.json +19 -0
- package/templates/verify.md +41 -0
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { afterEach, describe, expect, it } from 'vitest';
|
|
5
|
+
import {
|
|
6
|
+
buildTaskManifest,
|
|
7
|
+
preflightTask,
|
|
8
|
+
transitionTaskManifest,
|
|
9
|
+
updateLoopDetector,
|
|
10
|
+
writeTaskManifest,
|
|
11
|
+
} from './task-manifest-utils.mjs';
|
|
12
|
+
|
|
13
|
+
const tempDirs = [];
|
|
14
|
+
|
|
15
|
+
afterEach(() => {
|
|
16
|
+
for (const dir of tempDirs.splice(0)) {
|
|
17
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
describe('task manifest utilities', () => {
|
|
22
|
+
it('creates a manifest from a minimal task folder', () => {
|
|
23
|
+
const taskDir = createTask({
|
|
24
|
+
plan: [
|
|
25
|
+
'# Plan',
|
|
26
|
+
'',
|
|
27
|
+
'## Risk tier and execution budget',
|
|
28
|
+
'',
|
|
29
|
+
'- Speed mode: `Fast`',
|
|
30
|
+
'',
|
|
31
|
+
'## Затронутые модули и файлы',
|
|
32
|
+
'',
|
|
33
|
+
'- `docs/example.md`',
|
|
34
|
+
].join('\n'),
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const manifest = buildTaskManifest({ taskDir, now: '2026-05-21T00:00:00.000Z' });
|
|
38
|
+
|
|
39
|
+
expect(manifest.taskId).toMatch(/^TASK-/);
|
|
40
|
+
expect(manifest.mode).toBe('fast');
|
|
41
|
+
expect(manifest.phase).toBe('plan');
|
|
42
|
+
expect(manifest.context.riskProfile).toBe('low');
|
|
43
|
+
expect(manifest.gates.uiAcceptance.required).toBe(false);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('updates phase timestamps without losing existing manual fields', () => {
|
|
47
|
+
const taskDir = createTask();
|
|
48
|
+
writeTaskManifest(taskDir, {
|
|
49
|
+
schemaVersion: 1,
|
|
50
|
+
taskId: path.basename(taskDir),
|
|
51
|
+
mode: 'standard',
|
|
52
|
+
phase: 'plan',
|
|
53
|
+
timestamps: {
|
|
54
|
+
createdAt: '2026-05-20T00:00:00.000Z',
|
|
55
|
+
updatedAt: '2026-05-20T00:00:00.000Z',
|
|
56
|
+
phaseChangedAt: '2026-05-20T00:00:00.000Z',
|
|
57
|
+
},
|
|
58
|
+
manualNote: 'keep me',
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const result = transitionTaskManifest({
|
|
62
|
+
taskDir,
|
|
63
|
+
targetPhase: 'check',
|
|
64
|
+
now: '2026-05-21T00:00:00.000Z',
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
expect(result.ok).toBe(true);
|
|
68
|
+
expect(result.manifest.phase).toBe('check');
|
|
69
|
+
expect(result.manifest.timestamps.createdAt).toBe('2026-05-20T00:00:00.000Z');
|
|
70
|
+
expect(result.manifest.timestamps.phaseChangedAt).toBe('2026-05-21T00:00:00.000Z');
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('blocks execute transition when required UI gate is incomplete', () => {
|
|
74
|
+
const taskDir = createTask({
|
|
75
|
+
plan: [
|
|
76
|
+
'# Plan',
|
|
77
|
+
'',
|
|
78
|
+
'## Затронутые модули и файлы',
|
|
79
|
+
'',
|
|
80
|
+
'- `web/app/src/features/runs/page.tsx`',
|
|
81
|
+
].join('\n'),
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const result = transitionTaskManifest({ taskDir, targetPhase: 'execute' });
|
|
85
|
+
|
|
86
|
+
expect(result.ok).toBe(false);
|
|
87
|
+
expect(result.issues.map((issue) => issue.category)).toContain('plan_quality_gate');
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('allows fast mode preflight when no applicable gates are triggered', () => {
|
|
91
|
+
const taskDir = createTask({
|
|
92
|
+
plan: [
|
|
93
|
+
'# Plan',
|
|
94
|
+
'',
|
|
95
|
+
'## Risk tier and execution budget',
|
|
96
|
+
'',
|
|
97
|
+
'- Speed mode: `Fast`',
|
|
98
|
+
'',
|
|
99
|
+
'## Затронутые модули и файлы',
|
|
100
|
+
'',
|
|
101
|
+
'- `docs/example.md`',
|
|
102
|
+
].join('\n'),
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const result = preflightTask({ taskDir, targetPhase: 'check' });
|
|
106
|
+
|
|
107
|
+
expect(result.ok).toBe(true);
|
|
108
|
+
expect(result.manifest.mode).toBe('fast');
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('blocks verify preflight when required execution evidence is missing', () => {
|
|
112
|
+
const taskDir = createTask({
|
|
113
|
+
plan: [
|
|
114
|
+
'# Plan',
|
|
115
|
+
'',
|
|
116
|
+
'## Complexity / Performance Budget',
|
|
117
|
+
'',
|
|
118
|
+
'- Hot paths: read model.',
|
|
119
|
+
'- Expected data size / row counts: 10000 rows.',
|
|
120
|
+
'- Complexity risks: N+1.',
|
|
121
|
+
'- Budget / stop rule: < 2 seconds.',
|
|
122
|
+
].join('\n'),
|
|
123
|
+
execution: '# Execution\n\n## Краткое summary\n\nImplemented.',
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
const result = preflightTask({ taskDir, targetPhase: 'verify' });
|
|
127
|
+
|
|
128
|
+
expect(result.ok).toBe(false);
|
|
129
|
+
expect(result.issues.some((issue) => issue.message.includes('Complexity / Performance Evidence'))).toBe(true);
|
|
130
|
+
expect(result.issues.map((issue) => issue.category)).toContain('execution_ledger_missing');
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('blocks verify preflight when execution ledger changed files are not in execution evidence', () => {
|
|
134
|
+
const taskDir = createTask({
|
|
135
|
+
execution: [
|
|
136
|
+
'# Execution',
|
|
137
|
+
'',
|
|
138
|
+
'## Измененные файлы',
|
|
139
|
+
'',
|
|
140
|
+
'| File | Change summary | Planned item / reason |',
|
|
141
|
+
'| --- | --- | --- |',
|
|
142
|
+
'| `docs/example.md` | docs | planned |',
|
|
143
|
+
].join('\n'),
|
|
144
|
+
});
|
|
145
|
+
fs.writeFileSync(path.join(taskDir, 'execution-ledger.json'), JSON.stringify({
|
|
146
|
+
schemaVersion: 1,
|
|
147
|
+
git: {
|
|
148
|
+
changedFiles: [
|
|
149
|
+
{ path: 'docs/example.md', isTaskArtifact: false, isOpsFrameworkFile: false },
|
|
150
|
+
{ path: 'web/app/src/features/runs/page.tsx', isTaskArtifact: false, isOpsFrameworkFile: false },
|
|
151
|
+
],
|
|
152
|
+
unrelatedDirtyFiles: [],
|
|
153
|
+
},
|
|
154
|
+
}, null, 2));
|
|
155
|
+
|
|
156
|
+
const result = preflightTask({ taskDir, targetPhase: 'verify' });
|
|
157
|
+
|
|
158
|
+
expect(result.ok).toBe(false);
|
|
159
|
+
expect(result.issues).toContainEqual({
|
|
160
|
+
category: 'dirty_evidence_mismatch',
|
|
161
|
+
message: 'execution.md does not mention changed files from execution-ledger.json: web/app/src/features/runs/page.tsx.',
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('detects repeated checker/verifier return reasons for consolidated remediation', () => {
|
|
166
|
+
const first = updateLoopDetector({
|
|
167
|
+
results: [
|
|
168
|
+
{
|
|
169
|
+
source: 'checker',
|
|
170
|
+
value: {
|
|
171
|
+
verdict: 'return_to_plan',
|
|
172
|
+
createdAt: '2026-05-21T00:00:00.000Z',
|
|
173
|
+
findings: [{ claim: 'Plan misses UI Acceptance Evidence for UI-001.' }],
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
],
|
|
177
|
+
});
|
|
178
|
+
const second = updateLoopDetector({
|
|
179
|
+
existing: first,
|
|
180
|
+
results: [
|
|
181
|
+
{
|
|
182
|
+
source: 'verifier',
|
|
183
|
+
value: {
|
|
184
|
+
verdict: 'return_to_execute',
|
|
185
|
+
createdAt: '2026-05-21T01:00:00.000Z',
|
|
186
|
+
findings: [{ claim: 'Plan misses UI Acceptance Evidence for UI-002.' }],
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
],
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
expect(second.requiresConsolidatedRemediation).toBe(true);
|
|
193
|
+
expect(second.repeatedReasons[0].count).toBe(2);
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
function createTask({ plan = defaultPlan(), execution = '' } = {}) {
|
|
198
|
+
const taskDir = fs.mkdtempSync(path.join(os.tmpdir(), 'TASK-999-manifest-'));
|
|
199
|
+
tempDirs.push(taskDir);
|
|
200
|
+
fs.writeFileSync(path.join(taskDir, 'brief.md'), '# Brief\n\nTest task.\n');
|
|
201
|
+
fs.writeFileSync(path.join(taskDir, 'research.md'), '# Research\n\n## Findings\n\n- `docs/example.md`\n');
|
|
202
|
+
fs.writeFileSync(path.join(taskDir, 'plan.md'), `${plan.trim()}\n`);
|
|
203
|
+
fs.writeFileSync(path.join(taskDir, 'status.md'), '# Status\n\n## Текущий этап\n\nplan\n');
|
|
204
|
+
if (execution) {
|
|
205
|
+
fs.writeFileSync(path.join(taskDir, 'execution.md'), `${execution.trim()}\n`);
|
|
206
|
+
}
|
|
207
|
+
return taskDir;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function defaultPlan() {
|
|
211
|
+
return [
|
|
212
|
+
'# Plan',
|
|
213
|
+
'',
|
|
214
|
+
'## Затронутые модули и файлы',
|
|
215
|
+
'',
|
|
216
|
+
'- `docs/example.md`',
|
|
217
|
+
].join('\n');
|
|
218
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export function computeTaskMetricsFromLog(logContent) {
|
|
2
|
+
const lines = String(logContent || '').split('\n');
|
|
3
|
+
const events = lines
|
|
4
|
+
.map(parseOrchestrationEvent)
|
|
5
|
+
.filter(Boolean)
|
|
6
|
+
.sort((a, b) => a.at.getTime() - b.at.getTime());
|
|
7
|
+
const text = lines.join('\n');
|
|
8
|
+
const externalVerifierRuns = events.filter((event) => /external CLI verifier/i.test(event.body));
|
|
9
|
+
const returnToExecute = countMatches(text, /return_to_execute/g);
|
|
10
|
+
const returnToPlan = countMatches(text, /return_to_plan/g);
|
|
11
|
+
const processBreaks = countMatches(text, /process break|stage discipline issue/gi);
|
|
12
|
+
const deterministicPreverifyReturns = countMatches(text, /deterministic pre-verify evidence gate returned return_to_execute/gi);
|
|
13
|
+
const spanMs = events.length > 1 ? events.at(-1).at.getTime() - events[0].at.getTime() : 0;
|
|
14
|
+
|
|
15
|
+
return {
|
|
16
|
+
events: events.length,
|
|
17
|
+
firstEventAt: events[0]?.at.toISOString() || null,
|
|
18
|
+
lastEventAt: events.at(-1)?.at.toISOString() || null,
|
|
19
|
+
spanHours: Number((spanMs / 36e5).toFixed(2)),
|
|
20
|
+
externalVerifierRuns: externalVerifierRuns.length,
|
|
21
|
+
returnToExecute,
|
|
22
|
+
returnToPlan,
|
|
23
|
+
processBreaks,
|
|
24
|
+
deterministicPreverifyReturns,
|
|
25
|
+
loopSignals: returnToExecute + returnToPlan + processBreaks,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function parseOrchestrationEvent(line) {
|
|
30
|
+
const match = /^-\s*`([^`]+)`\s*—\s*(.*)$/.exec(line || '');
|
|
31
|
+
if (!match) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
const at = parseTaskTimestamp(match[1]);
|
|
35
|
+
if (!at) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
at,
|
|
40
|
+
body: match[2],
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function parseTaskTimestamp(value) {
|
|
45
|
+
let normalized = String(value || '').trim();
|
|
46
|
+
if (!normalized) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
normalized = normalized.replace(' WITA', '+08:00');
|
|
50
|
+
if (/^\d{4}-\d{2}-\d{2}$/.test(normalized)) {
|
|
51
|
+
normalized += 'T00:00:00+08:00';
|
|
52
|
+
} else if (/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}$/.test(normalized)) {
|
|
53
|
+
normalized = `${normalized.replace(' ', 'T')}:00+08:00`;
|
|
54
|
+
} else if (/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/.test(normalized)) {
|
|
55
|
+
normalized = `${normalized.replace(' ', 'T')}+08:00`;
|
|
56
|
+
}
|
|
57
|
+
const date = new Date(normalized);
|
|
58
|
+
return Number.isNaN(date.getTime()) ? null : date;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function countMatches(value, pattern) {
|
|
62
|
+
return (String(value || '').match(pattern) || []).length;
|
|
63
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
computeTaskMetricsFromLog,
|
|
4
|
+
parseTaskTimestamp,
|
|
5
|
+
} from './task-metrics-utils.mjs';
|
|
6
|
+
|
|
7
|
+
describe('task metrics utils', () => {
|
|
8
|
+
it('parses WITA and ISO timestamps', () => {
|
|
9
|
+
expect(parseTaskTimestamp('2026-05-15 13:40 WITA')?.toISOString()).toBe('2026-05-15T05:40:00.000Z');
|
|
10
|
+
expect(parseTaskTimestamp('2026-05-15T07:08:33.731Z')?.toISOString()).toBe('2026-05-15T07:08:33.731Z');
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('computes loop and verifier metrics from orchestration log', () => {
|
|
14
|
+
const log = [
|
|
15
|
+
'# Orchestration Log',
|
|
16
|
+
'',
|
|
17
|
+
'## Entries',
|
|
18
|
+
'',
|
|
19
|
+
'- `2026-05-15 13:40 WITA` — started',
|
|
20
|
+
'- `2026-05-15T07:08:33.731Z` — external CLI verifier completed via codex-cli; verdict=return_to_execute',
|
|
21
|
+
'- `2026-05-15T07:14:51.887Z` — external CLI verifier completed via codex-cli; verdict=pass_with_notes',
|
|
22
|
+
'- `2026-05-15T07:16:00.000Z` — deterministic pre-verify evidence gate returned return_to_execute; findings=1',
|
|
23
|
+
'- `2026-05-15T07:20:00.000Z` — Checker returned return_to_plan',
|
|
24
|
+
'- `2026-05-15T07:30:00.000Z` — process break corrected',
|
|
25
|
+
].join('\n');
|
|
26
|
+
|
|
27
|
+
expect(computeTaskMetricsFromLog(log)).toEqual({
|
|
28
|
+
events: 6,
|
|
29
|
+
firstEventAt: '2026-05-15T05:40:00.000Z',
|
|
30
|
+
lastEventAt: '2026-05-15T07:30:00.000Z',
|
|
31
|
+
spanHours: 1.83,
|
|
32
|
+
externalVerifierRuns: 2,
|
|
33
|
+
returnToExecute: 2,
|
|
34
|
+
returnToPlan: 1,
|
|
35
|
+
processBreaks: 1,
|
|
36
|
+
deterministicPreverifyReturns: 1,
|
|
37
|
+
loopSignals: 4,
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'ops-framework-test-project-'));
|
|
6
|
+
const memoryRoot = path.join(root, 'ops', 'agent-pipeline', 'memory');
|
|
7
|
+
fs.mkdirSync(path.join(root, 'ops'), { recursive: true });
|
|
8
|
+
fs.mkdirSync(memoryRoot, { recursive: true });
|
|
9
|
+
fs.writeFileSync(path.join(root, 'ops', 'project.ops.yaml'), `
|
|
10
|
+
name: TestProject
|
|
11
|
+
ops:
|
|
12
|
+
legacyPipelineDir: ops/agent-pipeline
|
|
13
|
+
tasksDir: ops/agent-pipeline/tasks
|
|
14
|
+
memoryDir: ops/agent-pipeline/memory
|
|
15
|
+
cacheDir: ops/agent-pipeline/cache
|
|
16
|
+
playbooksDir: ops/agent-pipeline/playbooks
|
|
17
|
+
risk:
|
|
18
|
+
uiRoots:
|
|
19
|
+
- web/app
|
|
20
|
+
backendRoots:
|
|
21
|
+
- services/api
|
|
22
|
+
workerRoots:
|
|
23
|
+
- workers/backend
|
|
24
|
+
`);
|
|
25
|
+
|
|
26
|
+
for (const fileName of [
|
|
27
|
+
'project-context-digest.md',
|
|
28
|
+
'module-boundaries.md',
|
|
29
|
+
'standards-digest.md',
|
|
30
|
+
'recurring-failures.md',
|
|
31
|
+
'domain-glossary.md',
|
|
32
|
+
'checker-rubric.md',
|
|
33
|
+
]) {
|
|
34
|
+
fs.writeFileSync(path.join(memoryRoot, fileName), `# ${fileName}\n\nTest memory.\n`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
process.chdir(root);
|
package/bin/new-task.mjs
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getFlag,
|
|
3
|
+
parseCliArgs,
|
|
4
|
+
} from './lib/check-context-utils.mjs';
|
|
5
|
+
import {
|
|
6
|
+
createTask,
|
|
7
|
+
summarizeChanges,
|
|
8
|
+
} from './lib/bootstrap-utils.mjs';
|
|
9
|
+
|
|
10
|
+
function main() {
|
|
11
|
+
const args = parseCliArgs(process.argv.slice(2));
|
|
12
|
+
const taskId = args.positional[0];
|
|
13
|
+
if (!taskId) {
|
|
14
|
+
fail('Usage: ops-agent new-task <TASK-000-slug> --title "Task title" [--owner Supervisor] [--force]');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
const result = createTask({
|
|
19
|
+
projectRoot: process.cwd(),
|
|
20
|
+
taskId,
|
|
21
|
+
title: getFlag(args, 'title', null),
|
|
22
|
+
owner: getFlag(args, 'owner', 'Supervisor'),
|
|
23
|
+
force: args.flags.has('force'),
|
|
24
|
+
});
|
|
25
|
+
const summary = summarizeChanges(result.changes);
|
|
26
|
+
|
|
27
|
+
console.log(`Ops task created: ${result.taskId}`);
|
|
28
|
+
console.log(`- taskDir: ${result.taskDir}`);
|
|
29
|
+
console.log(`- created: ${summary.created}`);
|
|
30
|
+
console.log(`- existing: ${summary.existing}`);
|
|
31
|
+
console.log(`- overwritten: ${summary.overwritten}`);
|
|
32
|
+
} catch (error) {
|
|
33
|
+
fail(error.message);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function fail(message) {
|
|
38
|
+
console.error(`Error: ${message}`);
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
main();
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawnSync } from 'node:child_process';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
|
|
6
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
7
|
+
const binRoot = path.dirname(__filename);
|
|
8
|
+
|
|
9
|
+
const COMMANDS = new Map([
|
|
10
|
+
['init', 'init-project.mjs'],
|
|
11
|
+
['new-task', 'new-task.mjs'],
|
|
12
|
+
['manifest', 'task-manifest.mjs'],
|
|
13
|
+
['preflight', 'preflight.mjs'],
|
|
14
|
+
['transition', 'transition.mjs'],
|
|
15
|
+
['build-check-context', 'build-check-context.mjs'],
|
|
16
|
+
['validate-check-artifacts', 'validate-check-artifacts.mjs'],
|
|
17
|
+
['estimate-llm-input', 'estimate-llm-input.mjs'],
|
|
18
|
+
['quality-gates', 'quality-gates.mjs'],
|
|
19
|
+
['run-check', 'run-check.mjs'],
|
|
20
|
+
['run-verify', 'run-verify.mjs'],
|
|
21
|
+
['hash-task-artifacts', 'hash-task-artifacts.mjs'],
|
|
22
|
+
['build-execution-ledger', 'build-execution-ledger.mjs'],
|
|
23
|
+
['task-metrics', 'task-metrics.mjs'],
|
|
24
|
+
['run-plan-check-loop', 'run-plan-check-loop.mjs'],
|
|
25
|
+
['supervisor-turn', 'supervisor-turn.mjs'],
|
|
26
|
+
['intake-feedback', 'intake-execution-feedback.mjs'],
|
|
27
|
+
['intake-execution-feedback', 'intake-execution-feedback.mjs'],
|
|
28
|
+
['guard-task', 'guard-task.mjs'],
|
|
29
|
+
['memory-candidates', 'learning-loop.mjs'],
|
|
30
|
+
['learning-index', 'learning-loop.mjs'],
|
|
31
|
+
['learning-review', 'learning-loop.mjs'],
|
|
32
|
+
['update-memory', 'learning-loop.mjs'],
|
|
33
|
+
['learning-audit', 'learning-loop.mjs'],
|
|
34
|
+
['learning-report', 'learning-loop.mjs'],
|
|
35
|
+
['test/self-test', null],
|
|
36
|
+
]);
|
|
37
|
+
|
|
38
|
+
function main() {
|
|
39
|
+
const [command, ...args] = process.argv.slice(2);
|
|
40
|
+
if (!command || command === '--help' || command === '-h') {
|
|
41
|
+
printHelp();
|
|
42
|
+
process.exit(command ? 0 : 1);
|
|
43
|
+
}
|
|
44
|
+
if (command === '--version' || command === '-v') {
|
|
45
|
+
console.log('0.1.0');
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (!COMMANDS.has(command)) {
|
|
49
|
+
console.error(`Unknown ops-agent command: ${command}`);
|
|
50
|
+
printHelp();
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
if (command === 'test/self-test') {
|
|
54
|
+
console.log('ops-agent self-test: ok');
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const script = path.join(binRoot, COMMANDS.get(command));
|
|
59
|
+
const scriptArgs = COMMANDS.get(command) === 'learning-loop.mjs' ? [command, ...args] : args;
|
|
60
|
+
const result = spawnSync(process.execPath, [script, ...scriptArgs], {
|
|
61
|
+
cwd: process.cwd(),
|
|
62
|
+
env: process.env,
|
|
63
|
+
stdio: 'inherit',
|
|
64
|
+
});
|
|
65
|
+
if (result.error) {
|
|
66
|
+
console.error(`Failed to run ${command}: ${result.error.message}`);
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
process.exit(result.status ?? 1);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function printHelp() {
|
|
73
|
+
console.log('Usage: ops-agent <command> [args]');
|
|
74
|
+
console.log('');
|
|
75
|
+
console.log('Commands:');
|
|
76
|
+
for (const name of COMMANDS.keys()) {
|
|
77
|
+
console.log(` ${name}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
main();
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import {
|
|
3
|
+
parseCliArgs,
|
|
4
|
+
resolveTaskDir,
|
|
5
|
+
} from './lib/check-context-utils.mjs';
|
|
6
|
+
import {
|
|
7
|
+
normalizePhase,
|
|
8
|
+
preflightTask,
|
|
9
|
+
} from './lib/task-manifest-utils.mjs';
|
|
10
|
+
|
|
11
|
+
function main() {
|
|
12
|
+
const args = parseCliArgs(process.argv.slice(2));
|
|
13
|
+
const taskArg = args.positional[0];
|
|
14
|
+
if (!taskArg) {
|
|
15
|
+
fail('Usage: node ops/agent-pipeline/bin/preflight.mjs <TASK-id-or-task-path> [--target execute|verify|human_gate] [--json]');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
const taskDir = resolveTaskDir(taskArg);
|
|
20
|
+
const taskId = path.basename(taskDir);
|
|
21
|
+
const targetPhase = normalizePhase(args.flags.get('target') || args.flags.get('phase') || 'execute');
|
|
22
|
+
const result = preflightTask({ taskDir, targetPhase });
|
|
23
|
+
|
|
24
|
+
if (args.flags.has('json')) {
|
|
25
|
+
console.log(JSON.stringify(result, null, 2));
|
|
26
|
+
} else {
|
|
27
|
+
console.log(`Preflight for ${taskId} -> ${targetPhase}: ${result.ok ? 'ok' : 'blocked'}`);
|
|
28
|
+
console.log(`- mode: ${result.manifest.mode}`);
|
|
29
|
+
console.log(`- current phase: ${result.manifest.phase}`);
|
|
30
|
+
console.log(`- check context current: ${result.manifest.context.checkContextCurrent ? 'yes' : 'no'}`);
|
|
31
|
+
if (result.warnings?.length > 0) {
|
|
32
|
+
console.log('Warnings:');
|
|
33
|
+
for (const warning of result.warnings) {
|
|
34
|
+
console.log(`- ${warning}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (result.issues.length > 0) {
|
|
38
|
+
console.log('Issues:');
|
|
39
|
+
for (const issue of result.issues) {
|
|
40
|
+
console.log(`- ${issue.category}: ${issue.message}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
process.exit(result.ok ? 0 : 1);
|
|
46
|
+
} catch (error) {
|
|
47
|
+
fail(error.message);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function fail(message) {
|
|
52
|
+
console.error(`Error: ${message}`);
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
main();
|