import { execFileSync, execSync } from 'node:child_process'; import { copyFileSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from 'node:fs'; import { basename, join } from 'node:path'; const featureId = '001-okr-web-app'; const root = process.cwd(); const logDir = `docs/output/output_logs/${featureId}`; const casanDir = `${logDir}/casan`; const reportsDir = `${logDir}/reports`; const contextPath = `${logDir}/pipeline-context.yaml`; const bossLog = `${logDir}/00-boss.log.md`; mkdirSync(casanDir, { recursive: true }); mkdirSync(reportsDir, { recursive: true }); writeFileSync( contextPath, `feature-id: ${featureId}\nmodule-id: MOD-01\nmodule-keyword: okr-management\ntech-stack: NestJS + Prisma + SQLite + React + Vite + Tailwind\nsteps:\n`, 'utf8', ); writeFileSync(bossLog, `# Boss Log ${featureId}\n\n`, 'utf8'); function appendBoss(line) { const ts = new Date().toISOString(); writeFileSync(bossLog, `${readFileSync(bossLog, 'utf8')}- ${ts} ${line}\n`, 'utf8'); } function latestAgentTrace(stepName) { const traceDir = '.specify/logs/trace'; const files = readdirSync(traceDir) .filter((file) => file.startsWith('agentops-') && file.endsWith('.json')) .map((file) => join(traceDir, file)) .sort((a, b) => statSync(b).mtimeMs - statSync(a).mtimeMs); for (const file of files) { const record = JSON.parse(readFileSync(file, 'utf8')); if (record.step === stepName) { return { traceId: record.trace_id, path: file, status: record.status }; } } throw new Error(`No trace found for ${stepName}`); } function verdictFromOutput(path) { const text = readFileSync(path, 'utf8'); const match = text.match(/verdict:\s*([A-Z_]+)/); return match?.[1] ?? 'UNKNOWN'; } function appendContext({ id, agent, output, trace }) { const verdict = verdictFromOutput(output); const existing = readFileSync(contextPath, 'utf8'); writeFileSync( contextPath, `${existing} - id: ${id}\n agent: ${agent}\n artifact: ${output}\n verdict: ${verdict}\n trace_id: ${trace.traceId}\n trace_file: ${trace.path}\n status: ${trace.status}\n`, 'utf8', ); } function runHarness({ id, agent, step, attempt = '1' }) { const input = `${casanDir}/${id}-input.txt`; const output = `${casanDir}/${id}-output.md`; const payload = `feature ${featureId}\nstep ${id}\nagent ${agent}\nattempt ${attempt}\nsource docs/input/okr-requirement.md\n`; writeFileSync(input, payload, 'utf8'); appendBoss(`START ${id} ${agent} attempt ${attempt}`); execFileSync( '.specify/scripts/bash/casan-harness.sh', [input, output, `agent_step_${id}`, '--', 'node', 'scripts/casan-step.mjs', step, attempt], { cwd: root, stdio: 'inherit', env: { ...process.env, CASAN_AGENT: agent, CASAN_AGENT_NAME: agent, CASAN_STEP_NAME: id, }, }, ); const trace = latestAgentTrace(id); appendContext({ id, agent, output, trace }); appendBoss(`END ${id} verdict ${verdictFromOutput(output)} trace ${trace.traceId}`); return { output, verdict: verdictFromOutput(output), trace }; } const sequence = [ { id: '01-srs', agent: 'okr.srs', step: '01-srs' }, { id: '02-bd', agent: 'okr.bd', step: '02-bd' }, { id: '03-spec', agent: 'speckit.specify', step: '03-spec' }, { id: '04-reviewspec', agent: 'okr.reviewspec', step: '04-reviewspec' }, { id: '05-plan-attempt-1', agent: 'speckit.plan', step: '05-plan', attempt: '1' }, { id: '06-reviewplan-attempt-1', agent: 'okr.reviewplan', step: '06-reviewplan', attempt: '1' }, ]; for (const item of sequence) { runHarness(item); } appendBoss('BACK-TO-PLAN triggered by reviewplan rejection; retrying plan with missing criteria fixed.'); runHarness({ id: '07-plan-attempt-2', agent: 'speckit.plan', step: '05-plan', attempt: '2' }); const fallbackOut = `${casanDir}/model-fallback-output.txt`; execFileSync( '.specify/scripts/bash/model-fallback.sh', [ fallbackOut, '--primary', 'bash -c "exit 9"', '--fallback', 'printf "Generate a safe OKR plan for employee ***MASKED_EMAIL***.\\nExpected sections:\\n- Objective\\n- Key Results\\n- Security gate\\n- Governance decision\\n- AgentOps metrics\\n"', ], { cwd: root, stdio: 'inherit' }, ); appendBoss(`Model fallback invoked; output ${fallbackOut}`); const driftCandidate = `${casanDir}/drift-plan-candidate.txt`; copyFileSync(fallbackOut, driftCandidate); execFileSync('.specify/scripts/bash/drift-detect.sh', [ '.specify/level5/golden-runs/okr-plan.golden.txt', driftCandidate, '.specify/logs/level5/okr-plan-drift-report.json', ], { cwd: root, stdio: 'inherit' }); appendBoss('Drift detection invoked after fixed plan.'); runHarness({ id: '08-reviewplan-attempt-2', agent: 'okr.reviewplan', step: '06-reviewplan', attempt: '2' }); runHarness({ id: '09-dd', agent: 'okr.dd', step: '07-dd' }); runHarness({ id: '10-testkit', agent: 'okr.testkit', step: '08-testkit' }); runHarness({ id: '11-tasks', agent: 'speckit.tasks', step: '09-tasks' }); runHarness({ id: '12-reviewcode', agent: 'okr.reviewcode', step: '10-reviewcode' }); const rollbackDir = 'docs/output/casan/app-evidence'; mkdirSync(rollbackDir, { recursive: true }); const rollbackTarget = `${rollbackDir}/rollback-target.txt`; const rollbackBackup = `${rollbackDir}/rollback-target.backup.txt`; writeFileSync(rollbackTarget, 'original pipeline rollback content\n', 'utf8'); copyFileSync(rollbackTarget, rollbackBackup); writeFileSync(`${rollbackDir}/rollback-before.txt`, readFileSync(rollbackTarget, 'utf8'), 'utf8'); writeFileSync(rollbackTarget, 'changed content that must be undone\n', 'utf8'); writeFileSync(`${rollbackDir}/rollback-changed.txt`, readFileSync(rollbackTarget, 'utf8'), 'utf8'); const record = execFileSync('.specify/scripts/bash/rollback-manager.sh', [ 'record', 'restore rollback target evidence file', `cp ${rollbackBackup} ${rollbackTarget}`, ], { cwd: root, encoding: 'utf8' }); writeFileSync(`${rollbackDir}/rollback-record.stdout`, record, 'utf8'); const tx = record.match(/transaction_id=([^\s]+)/)?.[1]; if (!tx) { throw new Error('rollback transaction id not found'); } const execute = execFileSync('.specify/scripts/bash/rollback-manager.sh', ['execute', tx], { cwd: root, encoding: 'utf8', }); writeFileSync(`${rollbackDir}/rollback-execute.stdout`, execute, 'utf8'); writeFileSync(`${rollbackDir}/rollback-after.txt`, readFileSync(rollbackTarget, 'utf8'), 'utf8'); appendBoss(`Rollback transaction ${tx} executed; before/changed/after evidence captured.`); const summary = `Pipeline complete. Context: ${contextPath}. Boss log: ${bossLog}. Last step artifacts under ${reportsDir}.\n`; writeFileSync(`${rollbackDir}/pipeline-summary.txt`, summary, 'utf8'); console.log(summary);