zouroboros-workflow 2.0.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/LICENSE +21 -0
- package/README.md +153 -0
- package/dist/autoloop/loop.d.ts +48 -0
- package/dist/autoloop/loop.js +145 -0
- package/dist/autoloop/parser.d.ts +12 -0
- package/dist/autoloop/parser.js +121 -0
- package/dist/autoloop/types.d.ts +54 -0
- package/dist/autoloop/types.js +4 -0
- package/dist/cli/autoloop.d.ts +7 -0
- package/dist/cli/autoloop.js +170 -0
- package/dist/cli/evaluate.d.ts +7 -0
- package/dist/cli/evaluate.js +166 -0
- package/dist/cli/interview.d.ts +7 -0
- package/dist/cli/interview.js +117 -0
- package/dist/cli/unstuck.d.ts +7 -0
- package/dist/cli/unstuck.js +150 -0
- package/dist/evaluate/mechanical.d.ts +8 -0
- package/dist/evaluate/mechanical.js +106 -0
- package/dist/evaluate/semantic.d.ts +13 -0
- package/dist/evaluate/semantic.js +167 -0
- package/dist/evaluate/types.d.ts +57 -0
- package/dist/evaluate/types.js +4 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +20 -0
- package/dist/interview/ambiguity.d.ts +9 -0
- package/dist/interview/ambiguity.js +53 -0
- package/dist/interview/seed.d.ts +12 -0
- package/dist/interview/seed.js +120 -0
- package/dist/interview/types.d.ts +49 -0
- package/dist/interview/types.js +4 -0
- package/dist/unstuck/strategies.d.ts +20 -0
- package/dist/unstuck/strategies.js +164 -0
- package/dist/unstuck/types.d.ts +23 -0
- package/dist/unstuck/types.js +4 -0
- package/package.json +51 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 marlandoj
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# zouroboros-workflow
|
|
2
|
+
|
|
3
|
+
> Spec-first development tools: interview, evaluation, unstuck, and autoloop
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Spec-First Interview** — Socratic interview process to clarify requirements before building
|
|
8
|
+
- **Three-Stage Evaluation** — Mechanical → Semantic → Consensus verification pipeline
|
|
9
|
+
- **Unstuck Lateral** — 5 lateral-thinking personas for breaking through stagnation
|
|
10
|
+
- **Autoloop** — Autonomous single-metric optimization with git-based improvement tracking
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install zouroboros-workflow
|
|
16
|
+
# or
|
|
17
|
+
pnpm add zouroboros-workflow
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## CLI Usage
|
|
21
|
+
|
|
22
|
+
### Spec-First Interview
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
# Score ambiguity of a request
|
|
26
|
+
zouroboros-interview score --request "Make the site faster"
|
|
27
|
+
|
|
28
|
+
# Generate seed specification
|
|
29
|
+
zouroboros-interview seed --topic "Build a webhook retry system"
|
|
30
|
+
|
|
31
|
+
# Interactive interview guide
|
|
32
|
+
zouroboros-interview --topic "Your topic here"
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Three-Stage Evaluation
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
# Run full evaluation pipeline
|
|
39
|
+
zouroboros-evaluate --seed seed.yaml --artifact ./src/
|
|
40
|
+
|
|
41
|
+
# Run only mechanical checks
|
|
42
|
+
zouroboros-evaluate --seed seed.yaml --artifact ./src/ --stage 1
|
|
43
|
+
|
|
44
|
+
# Self-test current workspace
|
|
45
|
+
zouroboros-evaluate --self-test
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Unstuck Lateral
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
# Auto-select persona based on problem
|
|
52
|
+
zouroboros-unstuck --problem "The API keeps returning 403 errors"
|
|
53
|
+
|
|
54
|
+
# Use specific persona
|
|
55
|
+
zouroboros-unstuck --problem "This refactor touches 20 files" --persona architect
|
|
56
|
+
|
|
57
|
+
# List all personas
|
|
58
|
+
zouroboros-unstuck --list
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Autoloop
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
# Validate program configuration
|
|
65
|
+
zouroboros-autoloop --program ./program.md --dry-run
|
|
66
|
+
|
|
67
|
+
# Run optimization loop
|
|
68
|
+
zouroboros-autoloop --program ./program.md
|
|
69
|
+
|
|
70
|
+
# Resume from existing branch
|
|
71
|
+
zouroboros-autoloop --program ./program.md --resume
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Programmatic API
|
|
75
|
+
|
|
76
|
+
### Spec-First Interview
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
import { scoreAmbiguity, generateSeedTemplate } from 'zouroboros-workflow';
|
|
80
|
+
|
|
81
|
+
// Score ambiguity before building
|
|
82
|
+
const score = scoreAmbiguity("Build a webhook retry system");
|
|
83
|
+
console.log(score.ambiguity); // 0.35 (needs clarification)
|
|
84
|
+
console.log(score.assessment); // "NEEDS CLARIFICATION"
|
|
85
|
+
|
|
86
|
+
// Generate seed after interview
|
|
87
|
+
const seed = generateSeedTemplate("Webhook Retry System", "./interview-notes.md");
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Three-Stage Evaluation
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
import { parseSeed, runMechanicalChecks, evaluateSemantic } from 'zouroboros-workflow';
|
|
94
|
+
|
|
95
|
+
// Parse seed specification
|
|
96
|
+
const seed = parseSeed('./seed.yaml');
|
|
97
|
+
|
|
98
|
+
// Stage 1: Mechanical verification
|
|
99
|
+
const checks = runMechanicalChecks('./src/');
|
|
100
|
+
// [ { name: 'TypeScript compile', passed: true, detail: 'No errors' }, ... ]
|
|
101
|
+
|
|
102
|
+
// Stage 2: Semantic evaluation
|
|
103
|
+
const result = evaluateSemantic(seed, './src/');
|
|
104
|
+
console.log(result.overallScore); // 0.85
|
|
105
|
+
console.log(result.decision); // 'APPROVED' | 'NEEDS_WORK'
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Unstuck Lateral
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
import { autoSelectPersona, getStrategy } from 'zouroboros-workflow';
|
|
112
|
+
|
|
113
|
+
// Auto-select best persona
|
|
114
|
+
const selection = autoSelectPersona("I'm stuck on this API error");
|
|
115
|
+
console.log(selection.persona); // 'hacker'
|
|
116
|
+
console.log(selection.confidence); // 0.85
|
|
117
|
+
|
|
118
|
+
// Get strategy details
|
|
119
|
+
const strategy = getStrategy('hacker');
|
|
120
|
+
console.log(strategy.philosophy);
|
|
121
|
+
console.log(strategy.approach);
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Autoloop
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
import { parseProgram, initState, shouldContinue, runExperiment } from 'zouroboros-workflow';
|
|
128
|
+
|
|
129
|
+
// Parse program configuration
|
|
130
|
+
const config = parseProgram('./program.md');
|
|
131
|
+
|
|
132
|
+
// Initialize loop
|
|
133
|
+
const state = initState(config, 'autoloop/my-optimization');
|
|
134
|
+
|
|
135
|
+
// Run loop
|
|
136
|
+
while (shouldContinue(state, config).continue) {
|
|
137
|
+
// Propose change via AI executor
|
|
138
|
+
// Apply change to target file
|
|
139
|
+
// Git commit
|
|
140
|
+
|
|
141
|
+
// Run experiment
|
|
142
|
+
const result = await runExperiment(config, './');
|
|
143
|
+
|
|
144
|
+
// Decide keep or revert
|
|
145
|
+
if (result.metric !== null) {
|
|
146
|
+
// Update state, commit or revert
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## License
|
|
152
|
+
|
|
153
|
+
MIT
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core autoloop optimization engine
|
|
3
|
+
*/
|
|
4
|
+
import type { ProgramConfig, LoopState, ExperimentRecord } from './types.js';
|
|
5
|
+
/**
|
|
6
|
+
* Initialize the loop state
|
|
7
|
+
*/
|
|
8
|
+
export declare function initState(config: ProgramConfig, branch: string): LoopState;
|
|
9
|
+
/**
|
|
10
|
+
* Check if the loop should continue
|
|
11
|
+
*/
|
|
12
|
+
export declare function shouldContinue(state: LoopState, config: ProgramConfig): {
|
|
13
|
+
continue: boolean;
|
|
14
|
+
reason?: string;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Check if a metric is better than the current best
|
|
18
|
+
*/
|
|
19
|
+
export declare function isBetter(metric: number, best: number, direction: 'lower_is_better' | 'higher_is_better'): boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Extract metric from command output
|
|
22
|
+
*/
|
|
23
|
+
export declare function extractMetric(output: string, extractPattern: string): number | null;
|
|
24
|
+
/**
|
|
25
|
+
* Run the experiment and extract metric
|
|
26
|
+
*/
|
|
27
|
+
export declare function runExperiment(config: ProgramConfig, workDir: string): Promise<{
|
|
28
|
+
metric: number | null;
|
|
29
|
+
output: string;
|
|
30
|
+
durationMs: number;
|
|
31
|
+
error?: string;
|
|
32
|
+
}>;
|
|
33
|
+
/**
|
|
34
|
+
* Format results as TSV
|
|
35
|
+
*/
|
|
36
|
+
export declare function formatResultsTSV(results: ExperimentRecord[]): string;
|
|
37
|
+
/**
|
|
38
|
+
* Save loop results to file
|
|
39
|
+
*/
|
|
40
|
+
export declare function saveResults(state: LoopState, config: ProgramConfig, outputPath: string): void;
|
|
41
|
+
/**
|
|
42
|
+
* Calculate stagnation level
|
|
43
|
+
*/
|
|
44
|
+
export declare function getStagnationLevel(state: LoopState, config: ProgramConfig): 0 | 1 | 2 | 3;
|
|
45
|
+
/**
|
|
46
|
+
* Get prompt modifier based on stagnation level
|
|
47
|
+
*/
|
|
48
|
+
export declare function getStagnationModifier(level: number): string;
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core autoloop optimization engine
|
|
3
|
+
*/
|
|
4
|
+
import { $ } from 'bun';
|
|
5
|
+
import { writeFileSync } from 'fs';
|
|
6
|
+
/**
|
|
7
|
+
* Initialize the loop state
|
|
8
|
+
*/
|
|
9
|
+
export function initState(config, branch) {
|
|
10
|
+
return {
|
|
11
|
+
bestMetric: config.metric.direction === 'lower_is_better' ? Infinity : -Infinity,
|
|
12
|
+
bestCommit: '',
|
|
13
|
+
experimentCount: 0,
|
|
14
|
+
stagnationCount: 0,
|
|
15
|
+
totalCostUSD: 0,
|
|
16
|
+
startTime: Date.now(),
|
|
17
|
+
results: [],
|
|
18
|
+
branch
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Check if the loop should continue
|
|
23
|
+
*/
|
|
24
|
+
export function shouldContinue(state, config) {
|
|
25
|
+
// Check experiment limit
|
|
26
|
+
if (state.experimentCount >= config.constraints.maxExperiments) {
|
|
27
|
+
return { continue: false, reason: `Reached max experiments (${config.constraints.maxExperiments})` };
|
|
28
|
+
}
|
|
29
|
+
// Check duration limit
|
|
30
|
+
const elapsedHours = (Date.now() - state.startTime) / (1000 * 60 * 60);
|
|
31
|
+
if (elapsedHours >= config.constraints.maxDurationHours) {
|
|
32
|
+
return { continue: false, reason: `Reached max duration (${config.constraints.maxDurationHours}h)` };
|
|
33
|
+
}
|
|
34
|
+
// Check cost limit
|
|
35
|
+
if (state.totalCostUSD >= config.constraints.maxCostUSD) {
|
|
36
|
+
return { continue: false, reason: `Reached max cost ($${config.constraints.maxCostUSD})` };
|
|
37
|
+
}
|
|
38
|
+
return { continue: true };
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Check if a metric is better than the current best
|
|
42
|
+
*/
|
|
43
|
+
export function isBetter(metric, best, direction) {
|
|
44
|
+
if (direction === 'lower_is_better') {
|
|
45
|
+
return metric < best;
|
|
46
|
+
}
|
|
47
|
+
return metric > best;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Extract metric from command output
|
|
51
|
+
*/
|
|
52
|
+
export function extractMetric(output, extractPattern) {
|
|
53
|
+
try {
|
|
54
|
+
// Try to parse as a number directly
|
|
55
|
+
const direct = parseFloat(output.trim());
|
|
56
|
+
if (!isNaN(direct))
|
|
57
|
+
return direct;
|
|
58
|
+
// Try regex pattern
|
|
59
|
+
const regex = new RegExp(extractPattern);
|
|
60
|
+
const match = output.match(regex);
|
|
61
|
+
if (match) {
|
|
62
|
+
const num = parseFloat(match[1] || match[0]);
|
|
63
|
+
if (!isNaN(num))
|
|
64
|
+
return num;
|
|
65
|
+
}
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Run the experiment and extract metric
|
|
74
|
+
*/
|
|
75
|
+
export async function runExperiment(config, workDir) {
|
|
76
|
+
const startTime = Date.now();
|
|
77
|
+
try {
|
|
78
|
+
// Run the command with timeout
|
|
79
|
+
const shell = $ `cd ${workDir} && ${config.runCommand}`.nothrow();
|
|
80
|
+
const result = await shell.timeout(config.constraints.timeBudgetSeconds * 1000);
|
|
81
|
+
const durationMs = Date.now() - startTime;
|
|
82
|
+
const output = result.stdout.toString() + result.stderr.toString();
|
|
83
|
+
// Extract metric
|
|
84
|
+
const metric = extractMetric(output, config.metric.extract);
|
|
85
|
+
return {
|
|
86
|
+
metric,
|
|
87
|
+
output,
|
|
88
|
+
durationMs,
|
|
89
|
+
error: result.exitCode !== 0 ? `Exit code ${result.exitCode}` : undefined
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
catch (e) {
|
|
93
|
+
const durationMs = Date.now() - startTime;
|
|
94
|
+
return {
|
|
95
|
+
metric: null,
|
|
96
|
+
output: '',
|
|
97
|
+
durationMs,
|
|
98
|
+
error: e.message
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Format results as TSV
|
|
104
|
+
*/
|
|
105
|
+
export function formatResultsTSV(results) {
|
|
106
|
+
const lines = ['commit\tmetric\tstatus\tdescription\ttimestamp\tduration_ms'];
|
|
107
|
+
for (const r of results) {
|
|
108
|
+
lines.push(`${r.commit}\t${r.metric}\t${r.status}\t"${r.description}"\t${r.timestamp}\t${r.durationMs}`);
|
|
109
|
+
}
|
|
110
|
+
return lines.join('\n');
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Save loop results to file
|
|
114
|
+
*/
|
|
115
|
+
export function saveResults(state, config, outputPath) {
|
|
116
|
+
const tsv = formatResultsTSV(state.results);
|
|
117
|
+
writeFileSync(outputPath, tsv);
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Calculate stagnation level
|
|
121
|
+
*/
|
|
122
|
+
export function getStagnationLevel(state, config) {
|
|
123
|
+
if (state.stagnationCount >= config.stagnation.tripleThreshold)
|
|
124
|
+
return 3;
|
|
125
|
+
if (state.stagnationCount >= config.stagnation.doubleThreshold)
|
|
126
|
+
return 2;
|
|
127
|
+
if (state.stagnationCount >= config.stagnation.threshold)
|
|
128
|
+
return 1;
|
|
129
|
+
return 0;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Get prompt modifier based on stagnation level
|
|
133
|
+
*/
|
|
134
|
+
export function getStagnationModifier(level) {
|
|
135
|
+
switch (level) {
|
|
136
|
+
case 1:
|
|
137
|
+
return '\n[Stagnation detected — try a more aggressive change or different approach]';
|
|
138
|
+
case 2:
|
|
139
|
+
return '\n[Significant stagnation — consider a completely different strategy]';
|
|
140
|
+
case 3:
|
|
141
|
+
return '\n[Critical stagnation — radical change required, explore very different solutions]';
|
|
142
|
+
default:
|
|
143
|
+
return '';
|
|
144
|
+
}
|
|
145
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse program.md files for autoloop
|
|
3
|
+
*/
|
|
4
|
+
import type { ProgramConfig } from './types.js';
|
|
5
|
+
/**
|
|
6
|
+
* Parse a program.md file into configuration
|
|
7
|
+
*/
|
|
8
|
+
export declare function parseProgram(path: string): ProgramConfig;
|
|
9
|
+
/**
|
|
10
|
+
* Validate a program configuration
|
|
11
|
+
*/
|
|
12
|
+
export declare function validateProgram(config: ProgramConfig): string[];
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse program.md files for autoloop
|
|
3
|
+
*/
|
|
4
|
+
import { readFileSync } from 'fs';
|
|
5
|
+
/**
|
|
6
|
+
* Extract a section from markdown by heading
|
|
7
|
+
*/
|
|
8
|
+
function getSection(content, heading) {
|
|
9
|
+
const re = new RegExp(`^##\\s+${heading}\\s*$`, 'im');
|
|
10
|
+
const match = content.match(re);
|
|
11
|
+
if (!match || match.index === undefined)
|
|
12
|
+
return '';
|
|
13
|
+
const start = match.index + match[0].length;
|
|
14
|
+
const nextHeading = content.slice(start).search(/^##\s+/m);
|
|
15
|
+
const end = nextHeading === -1 ? content.length : start + nextHeading;
|
|
16
|
+
return content.slice(start, end).trim();
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Extract a field from a section
|
|
20
|
+
*/
|
|
21
|
+
function getField(section, field) {
|
|
22
|
+
const re = new RegExp(`^-\\s+\\*\\*${field}\\*\\*:\\s*(.+)$`, 'im');
|
|
23
|
+
const match = section.match(re);
|
|
24
|
+
return match ? match[1].trim() : '';
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Parse a number from constraint text
|
|
28
|
+
*/
|
|
29
|
+
function parseConstraint(content, label, fallback) {
|
|
30
|
+
const re = new RegExp(`\\*\\*${label}\\*\\*:\\s*([\\d.]+)`, 'i');
|
|
31
|
+
const m = content.match(re);
|
|
32
|
+
return m ? parseFloat(m[1]) : fallback;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Parse a stagnation threshold
|
|
36
|
+
*/
|
|
37
|
+
function parseStagnation(content, label, fallback) {
|
|
38
|
+
const re = new RegExp(`\\*\\*${label}\\*\\*:\\s*(\\d+)`, 'i');
|
|
39
|
+
const m = content.match(re);
|
|
40
|
+
return m ? parseInt(m[1]) : fallback;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Parse a program.md file into configuration
|
|
44
|
+
*/
|
|
45
|
+
export function parseProgram(path) {
|
|
46
|
+
const raw = readFileSync(path, 'utf-8');
|
|
47
|
+
// Extract name from title
|
|
48
|
+
const nameMatch = raw.match(/^#\s+Program:\s*(.+)$/m);
|
|
49
|
+
const name = nameMatch ? nameMatch[1].trim() : 'unnamed';
|
|
50
|
+
// Extract metric section
|
|
51
|
+
const metricSection = getSection(raw, 'Metric');
|
|
52
|
+
const metricName = getField(metricSection, 'name');
|
|
53
|
+
const direction = getField(metricSection, 'direction');
|
|
54
|
+
const extract = getField(metricSection, 'extract').replace(/^`|`$/g, '');
|
|
55
|
+
// Extract constraints
|
|
56
|
+
const constraintsSection = getSection(raw, 'Constraints');
|
|
57
|
+
// Extract stagnation config
|
|
58
|
+
const stagnationSection = getSection(raw, 'Stagnation');
|
|
59
|
+
// Extract read-only files
|
|
60
|
+
const readOnlySection = getSection(raw, 'Read-Only Files');
|
|
61
|
+
const readOnlyFiles = readOnlySection
|
|
62
|
+
.split('\n')
|
|
63
|
+
.map(l => l.replace(/^-\s*/, '').trim())
|
|
64
|
+
.filter(Boolean);
|
|
65
|
+
// Extract setup code
|
|
66
|
+
const setupSection = getSection(raw, 'Setup');
|
|
67
|
+
const setupMatch = setupSection.match(/```(?:bash)?\n([\s\S]*?)```/);
|
|
68
|
+
// Extract run command
|
|
69
|
+
const runSection = getSection(raw, 'Run Command');
|
|
70
|
+
const runMatch = runSection.match(/```(?:bash)?\n([\s\S]*?)```/);
|
|
71
|
+
// Extract target file
|
|
72
|
+
const targetSection = getSection(raw, 'Target File');
|
|
73
|
+
const targetFile = targetSection.split('\n')[0].replace(/^`|`$/g, '').trim();
|
|
74
|
+
return {
|
|
75
|
+
name,
|
|
76
|
+
objective: getSection(raw, 'Objective'),
|
|
77
|
+
metric: {
|
|
78
|
+
name: metricName,
|
|
79
|
+
direction: direction || 'lower_is_better',
|
|
80
|
+
extract
|
|
81
|
+
},
|
|
82
|
+
setup: setupMatch ? setupMatch[1].trim() : setupSection,
|
|
83
|
+
targetFile,
|
|
84
|
+
runCommand: runMatch ? runMatch[1].trim() : runSection.split('\n')[0],
|
|
85
|
+
readOnlyFiles,
|
|
86
|
+
constraints: {
|
|
87
|
+
timeBudgetSeconds: parseConstraint(constraintsSection, 'Time budget per run', 300),
|
|
88
|
+
maxExperiments: parseConstraint(constraintsSection, 'Max experiments', 100),
|
|
89
|
+
maxDurationHours: parseConstraint(constraintsSection, 'Max duration', 8),
|
|
90
|
+
maxCostUSD: parseConstraint(constraintsSection, 'Max cost', 10)
|
|
91
|
+
},
|
|
92
|
+
stagnation: {
|
|
93
|
+
threshold: parseStagnation(stagnationSection, 'Threshold', 10),
|
|
94
|
+
doubleThreshold: parseStagnation(stagnationSection, 'Double threshold', 20),
|
|
95
|
+
tripleThreshold: parseStagnation(stagnationSection, 'Triple threshold', 30)
|
|
96
|
+
},
|
|
97
|
+
notes: getSection(raw, 'Notes')
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Validate a program configuration
|
|
102
|
+
*/
|
|
103
|
+
export function validateProgram(config) {
|
|
104
|
+
const errors = [];
|
|
105
|
+
if (!config.name || config.name === 'unnamed') {
|
|
106
|
+
errors.push('Program must have a name');
|
|
107
|
+
}
|
|
108
|
+
if (!config.metric.name) {
|
|
109
|
+
errors.push('Metric must have a name');
|
|
110
|
+
}
|
|
111
|
+
if (!config.metric.extract) {
|
|
112
|
+
errors.push('Metric must have an extract command/pattern');
|
|
113
|
+
}
|
|
114
|
+
if (!config.targetFile) {
|
|
115
|
+
errors.push('Target file must be specified');
|
|
116
|
+
}
|
|
117
|
+
if (!config.runCommand) {
|
|
118
|
+
errors.push('Run command must be specified');
|
|
119
|
+
}
|
|
120
|
+
return errors;
|
|
121
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types for autoloop optimization
|
|
3
|
+
*/
|
|
4
|
+
export interface MetricConfig {
|
|
5
|
+
name: string;
|
|
6
|
+
direction: 'lower_is_better' | 'higher_is_better';
|
|
7
|
+
extract: string;
|
|
8
|
+
}
|
|
9
|
+
export interface ConstraintConfig {
|
|
10
|
+
timeBudgetSeconds: number;
|
|
11
|
+
maxExperiments: number;
|
|
12
|
+
maxDurationHours: number;
|
|
13
|
+
maxCostUSD: number;
|
|
14
|
+
}
|
|
15
|
+
export interface StagnationConfig {
|
|
16
|
+
threshold: number;
|
|
17
|
+
doubleThreshold: number;
|
|
18
|
+
tripleThreshold: number;
|
|
19
|
+
}
|
|
20
|
+
export interface ProgramConfig {
|
|
21
|
+
name: string;
|
|
22
|
+
objective: string;
|
|
23
|
+
metric: MetricConfig;
|
|
24
|
+
setup: string;
|
|
25
|
+
targetFile: string;
|
|
26
|
+
runCommand: string;
|
|
27
|
+
readOnlyFiles: string[];
|
|
28
|
+
constraints: ConstraintConfig;
|
|
29
|
+
stagnation: StagnationConfig;
|
|
30
|
+
notes: string;
|
|
31
|
+
}
|
|
32
|
+
export interface ExperimentRecord {
|
|
33
|
+
commit: string;
|
|
34
|
+
metric: number;
|
|
35
|
+
status: 'keep' | 'discard' | 'crash';
|
|
36
|
+
description: string;
|
|
37
|
+
timestamp: string;
|
|
38
|
+
durationMs: number;
|
|
39
|
+
}
|
|
40
|
+
export interface LoopState {
|
|
41
|
+
bestMetric: number;
|
|
42
|
+
bestCommit: string;
|
|
43
|
+
experimentCount: number;
|
|
44
|
+
stagnationCount: number;
|
|
45
|
+
totalCostUSD: number;
|
|
46
|
+
startTime: number;
|
|
47
|
+
results: ExperimentRecord[];
|
|
48
|
+
branch: string;
|
|
49
|
+
}
|
|
50
|
+
export interface LoopStatus {
|
|
51
|
+
running: boolean;
|
|
52
|
+
state: LoopState | null;
|
|
53
|
+
config: ProgramConfig | null;
|
|
54
|
+
}
|