opencastle 0.10.7 → 0.11.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/README.md +4 -0
- package/bin/cli.mjs +4 -0
- package/dist/cli/dashboard.d.ts.map +1 -1
- package/dist/cli/dashboard.js +5 -1
- package/dist/cli/dashboard.js.map +1 -1
- package/dist/cli/init.test.js +1 -1
- package/dist/cli/init.test.js.map +1 -1
- package/dist/cli/lesson.d.ts +17 -0
- package/dist/cli/lesson.d.ts.map +1 -0
- package/dist/cli/lesson.js +294 -0
- package/dist/cli/lesson.js.map +1 -0
- package/dist/cli/log.d.ts +7 -0
- package/dist/cli/log.d.ts.map +1 -0
- package/dist/cli/log.js +131 -0
- package/dist/cli/log.js.map +1 -0
- package/dist/cli/run/executor.js.map +1 -1
- package/dist/cli/run/loop-executor.d.ts +3 -0
- package/dist/cli/run/loop-executor.d.ts.map +1 -0
- package/dist/cli/run/loop-executor.js +154 -0
- package/dist/cli/run/loop-executor.js.map +1 -0
- package/dist/cli/run/loop-reporter.d.ts +6 -0
- package/dist/cli/run/loop-reporter.d.ts.map +1 -0
- package/dist/cli/run/loop-reporter.js +112 -0
- package/dist/cli/run/loop-reporter.js.map +1 -0
- package/dist/cli/run/reporter.d.ts.map +1 -1
- package/dist/cli/run/reporter.js +28 -1
- package/dist/cli/run/reporter.js.map +1 -1
- package/dist/cli/run/schema.d.ts.map +1 -1
- package/dist/cli/run/schema.js +104 -52
- package/dist/cli/run/schema.js.map +1 -1
- package/dist/cli/run/schema.test.js +214 -0
- package/dist/cli/run/schema.test.js.map +1 -1
- package/dist/cli/run.d.ts.map +1 -1
- package/dist/cli/run.js +84 -3
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/types.d.ts +59 -1
- package/dist/cli/types.d.ts.map +1 -1
- package/dist/cli/update.d.ts.map +1 -1
- package/dist/cli/update.js +54 -1
- package/dist/cli/update.js.map +1 -1
- package/package.json +1 -1
- package/src/cli/dashboard.ts +5 -1
- package/src/cli/init.test.ts +1 -1
- package/src/cli/lesson.ts +312 -0
- package/src/cli/log.ts +133 -0
- package/src/cli/run/executor.ts +8 -8
- package/src/cli/run/loop-executor.ts +198 -0
- package/src/cli/run/loop-reporter.ts +125 -0
- package/src/cli/run/reporter.ts +30 -1
- package/src/cli/run/schema.test.ts +242 -2
- package/src/cli/run/schema.ts +115 -59
- package/src/cli/run.ts +82 -5
- package/src/cli/types.ts +67 -1
- package/src/cli/update.ts +62 -1
- package/src/dashboard/dist/index.html +14 -15
- package/src/dashboard/node_modules/.vite/deps/_metadata.json +6 -6
- package/src/dashboard/scripts/generate-seed-data.ts +23 -43
- package/src/dashboard/seed-data/events.ndjson +104 -0
- package/src/dashboard/src/pages/index.astro +14 -15
- package/src/orchestrator/agents/api-designer.agent.md +1 -1
- package/src/orchestrator/agents/architect.agent.md +1 -1
- package/src/orchestrator/agents/content-engineer.agent.md +1 -1
- package/src/orchestrator/agents/copywriter.agent.md +1 -1
- package/src/orchestrator/agents/data-expert.agent.md +1 -1
- package/src/orchestrator/agents/database-engineer.agent.md +1 -1
- package/src/orchestrator/agents/developer.agent.md +1 -1
- package/src/orchestrator/agents/devops-expert.agent.md +1 -1
- package/src/orchestrator/agents/documentation-writer.agent.md +1 -1
- package/src/orchestrator/agents/performance-expert.agent.md +1 -1
- package/src/orchestrator/agents/release-manager.agent.md +1 -1
- package/src/orchestrator/agents/security-expert.agent.md +1 -1
- package/src/orchestrator/agents/seo-specialist.agent.md +1 -1
- package/src/orchestrator/agents/session-guard.agent.md +9 -21
- package/src/orchestrator/agents/team-lead.agent.md +8 -34
- package/src/orchestrator/agents/testing-expert.agent.md +1 -1
- package/src/orchestrator/agents/ui-ux-expert.agent.md +1 -1
- package/src/orchestrator/customizations/AGENT-PERFORMANCE.md +11 -12
- package/src/orchestrator/customizations/DISPUTES.md +2 -2
- package/src/orchestrator/customizations/README.md +1 -3
- package/src/orchestrator/customizations/logs/README.md +66 -14
- package/src/orchestrator/instructions/ai-optimization.instructions.md +21 -132
- package/src/orchestrator/instructions/general.instructions.md +35 -181
- package/src/orchestrator/plugins/nx/SKILL.md +1 -1
- package/src/orchestrator/prompts/bootstrap-customizations.prompt.md +4 -8
- package/src/orchestrator/prompts/bug-fix.prompt.md +4 -4
- package/src/orchestrator/prompts/implement-feature.prompt.md +3 -3
- package/src/orchestrator/prompts/quick-refinement.prompt.md +3 -3
- package/src/orchestrator/prompts/resolve-pr-comments.prompt.md +1 -1
- package/src/orchestrator/skills/agent-hooks/SKILL.md +11 -11
- package/src/orchestrator/skills/decomposition/SKILL.md +1 -1
- package/src/orchestrator/skills/fast-review/SKILL.md +4 -19
- package/src/orchestrator/skills/git-workflow/SKILL.md +72 -0
- package/src/orchestrator/skills/memory-merger/SKILL.md +1 -1
- package/src/orchestrator/skills/observability-logging/SKILL.md +129 -0
- package/src/orchestrator/skills/orchestration-protocols/SKILL.md +2 -2
- package/src/orchestrator/skills/panel-majority-vote/SKILL.md +4 -7
- package/src/orchestrator/skills/self-improvement/SKILL.md +13 -26
- package/src/orchestrator/skills/team-lead-reference/SKILL.md +2 -2
- package/src/orchestrator/customizations/logs/delegations.ndjson +0 -1
- package/src/orchestrator/customizations/logs/panels.ndjson +0 -1
- package/src/orchestrator/customizations/logs/reviews.ndjson +0 -0
- package/src/orchestrator/customizations/logs/sessions.ndjson +0 -1
- /package/src/orchestrator/customizations/logs/{disputes.ndjson → events.ndjson} +0 -0
package/src/cli/run.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises'
|
|
1
2
|
import { resolve } from 'node:path'
|
|
2
3
|
import { parseTaskSpec } from './run/schema.js'
|
|
3
4
|
import { createExecutor, buildPhases } from './run/executor.js'
|
|
@@ -9,14 +10,17 @@ const HELP = `
|
|
|
9
10
|
opencastle run [options]
|
|
10
11
|
|
|
11
12
|
Process a task queue from a spec file, delegating to AI agents autonomously.
|
|
13
|
+
Supports two modes: tasks (default phase-based execution) and loop (iterative Ralph Loop).
|
|
12
14
|
|
|
13
15
|
Options:
|
|
14
16
|
--file, -f <path> Task spec file (default: opencastle.tasks.yml)
|
|
15
17
|
--dry-run Show execution plan without running
|
|
16
|
-
--concurrency, -c <n> Override max parallel tasks
|
|
18
|
+
--concurrency, -c <n> Override max parallel tasks (tasks mode)
|
|
17
19
|
--adapter, -a <name> Override agent runtime adapter
|
|
18
20
|
--report-dir <path> Where to write run reports (default: .opencastle/runs)
|
|
19
21
|
--verbose Show full agent output
|
|
22
|
+
--mode <name> Execution mode: tasks | loop
|
|
23
|
+
--max-iterations <n> Override max loop iterations (loop mode)
|
|
20
24
|
--help, -h Show this help
|
|
21
25
|
`
|
|
22
26
|
|
|
@@ -32,6 +36,8 @@ function parseArgs(args: string[]): RunOptions {
|
|
|
32
36
|
reportDir: null,
|
|
33
37
|
verbose: false,
|
|
34
38
|
help: false,
|
|
39
|
+
maxIterations: null,
|
|
40
|
+
mode: null,
|
|
35
41
|
}
|
|
36
42
|
|
|
37
43
|
for (let i = 0; i < args.length; i++) {
|
|
@@ -73,6 +79,26 @@ function parseArgs(args: string[]): RunOptions {
|
|
|
73
79
|
case '--verbose':
|
|
74
80
|
opts.verbose = true
|
|
75
81
|
break
|
|
82
|
+
case '--max-iterations': {
|
|
83
|
+
if (i + 1 >= args.length) { console.error(' \u2717 --max-iterations requires a number'); process.exit(1) }
|
|
84
|
+
const val = parseInt(args[++i], 10)
|
|
85
|
+
if (!Number.isFinite(val) || val < 1) {
|
|
86
|
+
console.error(` \u2717 --max-iterations must be an integer >= 1`)
|
|
87
|
+
process.exit(1)
|
|
88
|
+
}
|
|
89
|
+
opts.maxIterations = val
|
|
90
|
+
break
|
|
91
|
+
}
|
|
92
|
+
case '--mode': {
|
|
93
|
+
if (i + 1 >= args.length) { console.error(' \u2717 --mode requires a name'); process.exit(1) }
|
|
94
|
+
const modeVal = args[++i]
|
|
95
|
+
if (modeVal !== 'tasks' && modeVal !== 'loop') {
|
|
96
|
+
console.error(` \u2717 --mode must be one of: tasks, loop`)
|
|
97
|
+
process.exit(1)
|
|
98
|
+
}
|
|
99
|
+
opts.mode = modeVal
|
|
100
|
+
break
|
|
101
|
+
}
|
|
76
102
|
default:
|
|
77
103
|
console.error(` ✗ Unknown option: ${arg}`)
|
|
78
104
|
console.log(HELP)
|
|
@@ -102,6 +128,7 @@ export default async function run({ args }: CliContext): Promise<void> {
|
|
|
102
128
|
if (opts.concurrency !== null) spec.concurrency = opts.concurrency
|
|
103
129
|
if (opts.adapter !== null) spec.adapter = opts.adapter
|
|
104
130
|
if (opts.verbose) spec._verbose = true
|
|
131
|
+
if (opts.mode !== null) spec.mode = opts.mode as 'tasks' | 'loop'
|
|
105
132
|
|
|
106
133
|
// ── Auto-detect adapter if not specified ─────────────────────
|
|
107
134
|
let detectionFailed = false
|
|
@@ -117,9 +144,25 @@ export default async function run({ args }: CliContext): Promise<void> {
|
|
|
117
144
|
}
|
|
118
145
|
|
|
119
146
|
// ── Dry run ──────────────────────────────────────────────────
|
|
120
|
-
const phases = buildPhases(spec.tasks)
|
|
121
|
-
|
|
122
147
|
if (opts.dryRun) {
|
|
148
|
+
if (spec.mode === 'loop') {
|
|
149
|
+
const loop = spec.loop!
|
|
150
|
+
console.log(`\n \uD83C\uDFF0 Loop Plan: ${spec.name}`)
|
|
151
|
+
console.log(` Mode: loop`)
|
|
152
|
+
console.log(` Prompt: ${loop.prompt}`)
|
|
153
|
+
console.log(` Max iterations: ${loop.max_iterations}`)
|
|
154
|
+
console.log(` Timeout: ${loop.timeout}`)
|
|
155
|
+
if (loop.plan_file) console.log(` Plan file: ${loop.plan_file}`)
|
|
156
|
+
if (loop.model) console.log(` Model: ${loop.model}`)
|
|
157
|
+
if (loop.backpressure?.length) {
|
|
158
|
+
console.log(` Backpressure:`)
|
|
159
|
+
for (const cmd of loop.backpressure) {
|
|
160
|
+
console.log(` - ${cmd}`)
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return
|
|
164
|
+
}
|
|
165
|
+
const phases = buildPhases(spec.tasks!)
|
|
123
166
|
printExecutionPlan(spec, phases)
|
|
124
167
|
return
|
|
125
168
|
}
|
|
@@ -164,8 +207,42 @@ export default async function run({ args }: CliContext): Promise<void> {
|
|
|
164
207
|
}
|
|
165
208
|
|
|
166
209
|
// ── Execute ──────────────────────────────────────────────────
|
|
167
|
-
|
|
168
|
-
|
|
210
|
+
if (spec.mode === 'loop') {
|
|
211
|
+
const { createLoopExecutor } = await import('./run/loop-executor.js')
|
|
212
|
+
const { createLoopReporter } = await import('./run/loop-reporter.js')
|
|
213
|
+
|
|
214
|
+
if (opts.maxIterations !== null && spec.loop) {
|
|
215
|
+
spec.loop.max_iterations = opts.maxIterations
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const promptPath = resolve(process.cwd(), spec.loop!.prompt)
|
|
219
|
+
try {
|
|
220
|
+
await readFile(promptPath)
|
|
221
|
+
} catch {
|
|
222
|
+
console.error(` \u2717 Prompt file not found: ${spec.loop!.prompt}`)
|
|
223
|
+
process.exit(1)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
console.log(`\n \uD83C\uDFF0 OpenCastle Loop: ${spec.name}`)
|
|
227
|
+
console.log(` Adapter: ${adapter.name} | Max iterations: ${spec.loop!.max_iterations} | Timeout: ${spec.loop!.timeout}`)
|
|
228
|
+
if (spec.loop!.backpressure?.length) {
|
|
229
|
+
console.log(` Backpressure: ${spec.loop!.backpressure.join(', ')}`)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const loopReporter = createLoopReporter(spec.name, {
|
|
233
|
+
reportDir: opts.reportDir ? resolve(process.cwd(), opts.reportDir) : undefined,
|
|
234
|
+
verbose: opts.verbose,
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
const loopExecutor = createLoopExecutor(spec, adapter, loopReporter)
|
|
238
|
+
const loopReport = await loopExecutor.run()
|
|
239
|
+
|
|
240
|
+
const failed = loopReport.stoppedReason === 'error' || loopReport.stoppedReason === 'backpressure-fail'
|
|
241
|
+
process.exit(failed ? 1 : 0)
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
console.log(`\n \uD83C\uDFF0 OpenCastle Run: ${spec.name}`)
|
|
245
|
+
console.log(` Adapter: ${adapter.name} | Concurrency: ${spec.concurrency} | Tasks: ${spec.tasks!.length}`)
|
|
169
246
|
|
|
170
247
|
const reporter = createReporter(spec, {
|
|
171
248
|
reportDir: opts.reportDir
|
package/src/cli/types.ts
CHANGED
|
@@ -126,13 +126,31 @@ export const IDE_LABELS: Record<IdeChoice, string> = {
|
|
|
126
126
|
|
|
127
127
|
// ── Run command types ──────────────────────────────────────────
|
|
128
128
|
|
|
129
|
+
/** Loop execution configuration. */
|
|
130
|
+
export interface LoopConfig {
|
|
131
|
+
/** Maximum number of agent iterations (default 20). */
|
|
132
|
+
max_iterations: number;
|
|
133
|
+
/** Path to the prompt file read each iteration. */
|
|
134
|
+
prompt: string;
|
|
135
|
+
/** Path to the plan file (default 'IMPLEMENTATION_PLAN.md'). */
|
|
136
|
+
plan_file?: string;
|
|
137
|
+
/** Per-iteration timeout (default '10m'). */
|
|
138
|
+
timeout: string;
|
|
139
|
+
/** Model override for loop sessions. */
|
|
140
|
+
model?: string;
|
|
141
|
+
/** Shell commands that must exit 0 after each iteration. */
|
|
142
|
+
backpressure?: string[];
|
|
143
|
+
}
|
|
144
|
+
|
|
129
145
|
/** Validated task spec from YAML. */
|
|
130
146
|
export interface TaskSpec {
|
|
131
147
|
name: string;
|
|
132
148
|
concurrency: number;
|
|
133
149
|
on_failure: 'continue' | 'stop';
|
|
134
150
|
adapter: string;
|
|
135
|
-
tasks
|
|
151
|
+
tasks?: Task[];
|
|
152
|
+
mode?: 'tasks' | 'loop';
|
|
153
|
+
loop?: LoopConfig;
|
|
136
154
|
_verbose?: boolean;
|
|
137
155
|
}
|
|
138
156
|
|
|
@@ -231,6 +249,8 @@ export interface RunOptions {
|
|
|
231
249
|
reportDir: string | null;
|
|
232
250
|
verbose: boolean;
|
|
233
251
|
help: boolean;
|
|
252
|
+
maxIterations: number | null;
|
|
253
|
+
mode: string | null;
|
|
234
254
|
}
|
|
235
255
|
|
|
236
256
|
/** Validation result. */
|
|
@@ -250,3 +270,49 @@ export interface Executor {
|
|
|
250
270
|
run(): Promise<RunReport>;
|
|
251
271
|
getPhases(): Task[][];
|
|
252
272
|
}
|
|
273
|
+
|
|
274
|
+
// ── Loop executor types ────────────────────────────────────────
|
|
275
|
+
|
|
276
|
+
/** Result of a single backpressure command run. */
|
|
277
|
+
export interface BackpressureResult {
|
|
278
|
+
command: string;
|
|
279
|
+
exitCode: number;
|
|
280
|
+
output: string;
|
|
281
|
+
passed: boolean;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/** Result of a single loop iteration. */
|
|
285
|
+
export interface LoopIterationResult {
|
|
286
|
+
iteration: number;
|
|
287
|
+
status: 'done' | 'failed' | 'backpressure-fail';
|
|
288
|
+
duration: number;
|
|
289
|
+
output: string;
|
|
290
|
+
backpressureResults?: BackpressureResult[];
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/** Final report produced by the loop executor. */
|
|
294
|
+
export interface LoopRunReport {
|
|
295
|
+
name: string;
|
|
296
|
+
mode: 'loop';
|
|
297
|
+
startedAt: string;
|
|
298
|
+
completedAt: string;
|
|
299
|
+
duration: string;
|
|
300
|
+
totalIterations: number;
|
|
301
|
+
completedIterations: number;
|
|
302
|
+
stoppedReason: 'max-iterations' | 'plan-empty' | 'backpressure-fail' | 'user-abort' | 'error';
|
|
303
|
+
iterations: LoopIterationResult[];
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/** Reporter interface for loop execution progress. */
|
|
307
|
+
export interface LoopReporter {
|
|
308
|
+
onIterationStart(iteration: number, maxIterations: number): void;
|
|
309
|
+
onIterationDone(iteration: number, result: LoopIterationResult): void;
|
|
310
|
+
onBackpressureStart(command: string): void;
|
|
311
|
+
onBackpressureResult(result: BackpressureResult): void;
|
|
312
|
+
onComplete(report: LoopRunReport): Promise<void>;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/** Executor for loop-mode run specs. */
|
|
316
|
+
export interface LoopExecutor {
|
|
317
|
+
run(): Promise<LoopRunReport>;
|
|
318
|
+
}
|
package/src/cli/update.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { resolve } from 'node:path'
|
|
2
|
-
import {
|
|
2
|
+
import { existsSync } from 'node:fs'
|
|
3
|
+
import { readFile, appendFile, rename } from 'node:fs/promises'
|
|
3
4
|
import { readManifest, writeManifest } from './manifest.js'
|
|
4
5
|
import { multiselect, confirm, closePrompts, c } from './prompt.js'
|
|
5
6
|
import { isLegacyStack, migrateStackConfig, IDE_LABELS } from './types.js'
|
|
@@ -247,6 +248,9 @@ export default async function update({
|
|
|
247
248
|
}
|
|
248
249
|
}
|
|
249
250
|
|
|
251
|
+
// ── Migrate legacy log files ────────────────────────────────────
|
|
252
|
+
await migrateLegacyLogs(projectRoot)
|
|
253
|
+
|
|
250
254
|
// ── Update manifest ─────────────────────────────────────────────
|
|
251
255
|
if (hasVersionUpdate) manifest.version = pkg.version
|
|
252
256
|
manifest.ides = ides
|
|
@@ -322,6 +326,63 @@ export default async function update({
|
|
|
322
326
|
closePrompts()
|
|
323
327
|
}
|
|
324
328
|
|
|
329
|
+
async function migrateLegacyLogs(projectRoot: string): Promise<void> {
|
|
330
|
+
const logsDir = resolve(projectRoot, '.github', 'customizations', 'logs')
|
|
331
|
+
if (!existsSync(logsDir)) return
|
|
332
|
+
|
|
333
|
+
const typeMap: Record<string, string> = {
|
|
334
|
+
'sessions.ndjson': 'session',
|
|
335
|
+
'delegations.ndjson': 'delegation',
|
|
336
|
+
'reviews.ndjson': 'review',
|
|
337
|
+
'panels.ndjson': 'panel',
|
|
338
|
+
'disputes.ndjson': 'dispute',
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const eventsFile = resolve(logsDir, 'events.ndjson')
|
|
342
|
+
let totalMigrated = 0
|
|
343
|
+
|
|
344
|
+
for (const [filename, type] of Object.entries(typeMap)) {
|
|
345
|
+
const filePath = resolve(logsDir, filename)
|
|
346
|
+
if (!existsSync(filePath)) continue
|
|
347
|
+
|
|
348
|
+
let content: string
|
|
349
|
+
try {
|
|
350
|
+
content = await readFile(filePath, 'utf8')
|
|
351
|
+
} catch {
|
|
352
|
+
continue
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const lines = content.split('\n').filter((line) => line.trim() !== '')
|
|
356
|
+
if (lines.length === 0) continue
|
|
357
|
+
|
|
358
|
+
const migratedLines: string[] = []
|
|
359
|
+
for (const line of lines) {
|
|
360
|
+
try {
|
|
361
|
+
const record = JSON.parse(line) as Record<string, unknown>
|
|
362
|
+
if (!record['type']) {
|
|
363
|
+
record['type'] = type
|
|
364
|
+
}
|
|
365
|
+
migratedLines.push(JSON.stringify(record))
|
|
366
|
+
} catch {
|
|
367
|
+
console.warn(` ${c.yellow('⚠')} Skipping malformed JSON line in ${filename}`)
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
if (migratedLines.length > 0) {
|
|
372
|
+
await appendFile(eventsFile, migratedLines.join('\n') + '\n', 'utf8')
|
|
373
|
+
totalMigrated += migratedLines.length
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
await rename(filePath, filePath + '.migrated')
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
if (totalMigrated > 0) {
|
|
380
|
+
console.log(
|
|
381
|
+
` ${c.green('✓')} Migrated ${c.bold(String(totalMigrated))} records from legacy log files to events.ndjson`
|
|
382
|
+
)
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
325
386
|
function sameSet(a: Set<string>, b: Set<string>): boolean {
|
|
326
387
|
if (a.size !== b.size) return false
|
|
327
388
|
for (const item of a) {
|
|
@@ -1059,29 +1059,28 @@ Export
|
|
|
1059
1059
|
// ── Export ─────────────────────────────────────────────────
|
|
1060
1060
|
|
|
1061
1061
|
function exportData() {
|
|
1062
|
-
const
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
};
|
|
1069
|
-
const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
|
|
1062
|
+
const events = [
|
|
1063
|
+
...rawSessions,
|
|
1064
|
+
...rawDelegations,
|
|
1065
|
+
...rawPanels,
|
|
1066
|
+
...rawReviews,
|
|
1067
|
+
].sort((a, b) => a.timestamp.localeCompare(b.timestamp));
|
|
1068
|
+
const blob = new Blob([events.map((e) => JSON.stringify(e)).join('\n') + '\n'], { type: 'application/x-ndjson' });
|
|
1070
1069
|
const url = URL.createObjectURL(blob);
|
|
1071
1070
|
const a = document.createElement('a');
|
|
1072
1071
|
a.href = url;
|
|
1073
|
-
a.download = 'opencastle-
|
|
1072
|
+
a.download = 'opencastle-events-' + new Date().toISOString().slice(0, 10) + '.ndjson';
|
|
1074
1073
|
a.click();
|
|
1075
1074
|
URL.revokeObjectURL(url);
|
|
1076
1075
|
}
|
|
1077
1076
|
|
|
1078
1077
|
async function main() {
|
|
1079
|
-
const
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1078
|
+
const events = await loadNdjson(base + 'data/events.ndjson');
|
|
1079
|
+
|
|
1080
|
+
const sessions = events.filter((e) => e.type === 'session');
|
|
1081
|
+
const delegations = events.filter((e) => e.type === 'delegation');
|
|
1082
|
+
const panels = events.filter((e) => e.type === 'panel');
|
|
1083
|
+
const reviews = events.filter((e) => e.type === 'review');
|
|
1085
1084
|
|
|
1086
1085
|
rawSessions = sessions;
|
|
1087
1086
|
rawDelegations = delegations;
|
|
@@ -1,25 +1,25 @@
|
|
|
1
1
|
{
|
|
2
|
-
"hash": "
|
|
2
|
+
"hash": "87401706",
|
|
3
3
|
"configHash": "30f8ea04",
|
|
4
|
-
"lockfileHash": "
|
|
5
|
-
"browserHash": "
|
|
4
|
+
"lockfileHash": "56be8082",
|
|
5
|
+
"browserHash": "8ccc6e37",
|
|
6
6
|
"optimized": {
|
|
7
7
|
"astro > cssesc": {
|
|
8
8
|
"src": "../../../../../node_modules/cssesc/cssesc.js",
|
|
9
9
|
"file": "astro___cssesc.js",
|
|
10
|
-
"fileHash": "
|
|
10
|
+
"fileHash": "7e157996",
|
|
11
11
|
"needsInterop": true
|
|
12
12
|
},
|
|
13
13
|
"astro > aria-query": {
|
|
14
14
|
"src": "../../../../../node_modules/aria-query/lib/index.js",
|
|
15
15
|
"file": "astro___aria-query.js",
|
|
16
|
-
"fileHash": "
|
|
16
|
+
"fileHash": "021f979d",
|
|
17
17
|
"needsInterop": true
|
|
18
18
|
},
|
|
19
19
|
"astro > axobject-query": {
|
|
20
20
|
"src": "../../../../../node_modules/axobject-query/lib/index.js",
|
|
21
21
|
"file": "astro___axobject-query.js",
|
|
22
|
-
"fileHash": "
|
|
22
|
+
"fileHash": "f1a04609",
|
|
23
23
|
"needsInterop": true
|
|
24
24
|
}
|
|
25
25
|
},
|
|
@@ -2,15 +2,13 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Generate realistic seed data for the Agent Dashboard.
|
|
4
4
|
*
|
|
5
|
-
* Writes NDJSON
|
|
6
|
-
* -
|
|
7
|
-
* - delegations.ndjson (35 records)
|
|
8
|
-
* - panels.ndjson (12 records + preserves existing)
|
|
5
|
+
* Writes a single NDJSON file to the configured logs directory:
|
|
6
|
+
* - events.ndjson (50 session + 35 delegation + 12 panel records, sorted by timestamp)
|
|
9
7
|
*
|
|
10
8
|
* Usage: npx tsx opencastle/src/dashboard/scripts/generate-seed-data.ts
|
|
11
9
|
*/
|
|
12
10
|
|
|
13
|
-
import {
|
|
11
|
+
import { writeFileSync, mkdirSync } from 'fs';
|
|
14
12
|
import { join } from 'path';
|
|
15
13
|
|
|
16
14
|
const REPO_ROOT = join(__dirname, '..', '..', '..', '..');
|
|
@@ -156,6 +154,7 @@ const END_DATE = new Date('2026-02-25T18:00:00Z');
|
|
|
156
154
|
// --- Generate Sessions ---
|
|
157
155
|
|
|
158
156
|
interface SessionRecord {
|
|
157
|
+
type: 'session';
|
|
159
158
|
timestamp: string;
|
|
160
159
|
agent: string;
|
|
161
160
|
model: string;
|
|
@@ -180,6 +179,7 @@ function generateSessions(count: number): SessionRecord[] {
|
|
|
180
179
|
const discoveries = rng.next() > 0.7 ? [rng.pick(TASK_DESCRIPTIONS)] : [];
|
|
181
180
|
|
|
182
181
|
records.push({
|
|
182
|
+
type: 'session',
|
|
183
183
|
timestamp: generateTimestamp(i, count, START_DATE, END_DATE),
|
|
184
184
|
agent: rng.pick(AGENTS),
|
|
185
185
|
model: rng.pick(MODELS),
|
|
@@ -199,6 +199,7 @@ function generateSessions(count: number): SessionRecord[] {
|
|
|
199
199
|
// --- Generate Delegations ---
|
|
200
200
|
|
|
201
201
|
interface DelegationRecord {
|
|
202
|
+
type: 'delegation';
|
|
202
203
|
timestamp: string;
|
|
203
204
|
session_id: string;
|
|
204
205
|
agent: string;
|
|
@@ -220,9 +221,9 @@ function generateDelegations(count: number): DelegationRecord[] {
|
|
|
220
221
|
const outcome = outcomeRoll < 0.75 ? 'success' : outcomeRoll < 0.9 ? 'partial' : 'failed';
|
|
221
222
|
|
|
222
223
|
records.push({
|
|
224
|
+
type: 'delegation',
|
|
223
225
|
timestamp: generateTimestamp(i, count, START_DATE, END_DATE),
|
|
224
|
-
session_id: `feat/${issue.toLowerCase()}`,
|
|
225
|
-
agent: rng.pick(AGENTS),
|
|
226
|
+
session_id: `feat/${issue.toLowerCase()}`, agent: rng.pick(AGENTS),
|
|
226
227
|
model: rng.pick(MODELS),
|
|
227
228
|
tier: rng.weighted(TIERS),
|
|
228
229
|
mechanism: rng.next() < 0.6 ? 'sub-agent' : 'background',
|
|
@@ -239,6 +240,7 @@ function generateDelegations(count: number): DelegationRecord[] {
|
|
|
239
240
|
// --- Generate Panels ---
|
|
240
241
|
|
|
241
242
|
interface PanelRecord {
|
|
243
|
+
type: 'panel';
|
|
242
244
|
timestamp: string;
|
|
243
245
|
panel_key: string;
|
|
244
246
|
verdict: string;
|
|
@@ -264,6 +266,7 @@ function generatePanels(count: number): PanelRecord[] {
|
|
|
264
266
|
const issue = rng.pick(TRACKER_ISSUES);
|
|
265
267
|
|
|
266
268
|
records.push({
|
|
269
|
+
type: 'panel',
|
|
267
270
|
timestamp: generateTimestamp(i, count, START_DATE, END_DATE),
|
|
268
271
|
panel_key: panelKey,
|
|
269
272
|
verdict: isPass ? 'pass' : 'block',
|
|
@@ -287,50 +290,27 @@ function generatePanels(count: number): PanelRecord[] {
|
|
|
287
290
|
function main() {
|
|
288
291
|
mkdirSync(LOGS_DIR, { recursive: true });
|
|
289
292
|
|
|
290
|
-
// Read existing panel records to preserve them
|
|
291
|
-
const panelsPath = join(LOGS_DIR, 'panels.ndjson');
|
|
292
|
-
let existingPanels: string[] = [];
|
|
293
|
-
try {
|
|
294
|
-
const content = readFileSync(panelsPath, 'utf-8').trim();
|
|
295
|
-
if (content) {
|
|
296
|
-
existingPanels = content.split('\n').filter(Boolean);
|
|
297
|
-
}
|
|
298
|
-
} catch {
|
|
299
|
-
// File doesn't exist yet
|
|
300
|
-
}
|
|
301
|
-
|
|
302
293
|
// Generate data
|
|
303
294
|
const sessions = generateSessions(50);
|
|
304
295
|
const delegations = generateDelegations(35);
|
|
305
296
|
const panels = generatePanels(12);
|
|
306
297
|
|
|
307
|
-
//
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
// Write
|
|
313
|
-
const
|
|
314
|
-
writeFileSync(
|
|
315
|
-
console.log(`Wrote ${
|
|
316
|
-
|
|
317
|
-
// Write panels (preserve existing + add new, sorted by timestamp)
|
|
318
|
-
const allPanelLines = [
|
|
319
|
-
...existingPanels,
|
|
320
|
-
...panels.map((r) => JSON.stringify(r)),
|
|
321
|
-
];
|
|
322
|
-
// Parse and sort all panel records by timestamp
|
|
323
|
-
const allPanelRecords = allPanelLines
|
|
324
|
-
.map((line) => JSON.parse(line))
|
|
325
|
-
.sort((a: { timestamp: string }, b: { timestamp: string }) => a.timestamp.localeCompare(b.timestamp));
|
|
326
|
-
writeFileSync(panelsPath, allPanelRecords.map((r: unknown) => JSON.stringify(r)).join('\n') + '\n');
|
|
327
|
-
console.log(`Wrote ${allPanelRecords.length} panel records to ${panelsPath} (${existingPanels.length} existing + ${panels.length} new)`);
|
|
298
|
+
// Merge and sort all events by timestamp
|
|
299
|
+
type AnyRecord = SessionRecord | DelegationRecord | PanelRecord;
|
|
300
|
+
const allEvents: AnyRecord[] = [...sessions, ...delegations, ...panels]
|
|
301
|
+
.sort((a, b) => a.timestamp.localeCompare(b.timestamp));
|
|
302
|
+
|
|
303
|
+
// Write single events.ndjson
|
|
304
|
+
const eventsPath = join(LOGS_DIR, 'events.ndjson');
|
|
305
|
+
writeFileSync(eventsPath, allEvents.map((r) => JSON.stringify(r)).join('\n') + '\n');
|
|
306
|
+
console.log(`Wrote ${allEvents.length} event records to ${eventsPath}`);
|
|
328
307
|
|
|
329
308
|
// Summary
|
|
330
309
|
console.log('\n--- Seed Data Summary ---');
|
|
331
310
|
console.log(`Sessions: ${sessions.length}`);
|
|
332
311
|
console.log(`Delegations: ${delegations.length}`);
|
|
333
|
-
console.log(`Panels: ${
|
|
312
|
+
console.log(`Panels: ${panels.length}`);
|
|
313
|
+
console.log(`Total: ${allEvents.length}`);
|
|
334
314
|
|
|
335
315
|
// Outcome distribution
|
|
336
316
|
const sessionOutcomes = sessions.reduce<Record<string, number>>((acc, s) => {
|
|
@@ -345,10 +325,10 @@ function main() {
|
|
|
345
325
|
}, {});
|
|
346
326
|
console.log(`Delegation tiers: ${JSON.stringify(tierDist)}`);
|
|
347
327
|
|
|
348
|
-
const panelVerdicts =
|
|
328
|
+
const panelVerdicts = panels.reduce<Record<string, number>>((acc, p) => {
|
|
349
329
|
acc[p.verdict] = (acc[p.verdict] || 0) + 1;
|
|
350
330
|
return acc;
|
|
351
|
-
}, {}
|
|
331
|
+
}, {});
|
|
352
332
|
console.log(`Panel verdicts: ${JSON.stringify(panelVerdicts)}`);
|
|
353
333
|
}
|
|
354
334
|
|