fix template, remove okr, use casan.*
This commit is contained in:
@@ -2,24 +2,37 @@ import { execFileSync } from 'node:child_process';
|
||||
import { existsSync, appendFileSync, copyFileSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from 'node:fs';
|
||||
import { dirname, join } from 'node:path';
|
||||
import { enabled, log, redact, LOG_LEVEL } from './casan-log.mjs';
|
||||
import { loadProjectManifest, projectArtifactPaths } from './casan-project.mjs';
|
||||
|
||||
// --dry-run: walk the FULL 13-STEP diagram with stub agents. Every stub still
|
||||
// goes through casan-harness.sh (H4-in -> H5 -> H6 -> exec -> H4-out), so log
|
||||
// levels and pipeline-run.jsonl can be demonstrated deterministically offline.
|
||||
const dryRun = process.argv.includes('--dry-run');
|
||||
|
||||
const featureId = '001-okr-web-app';
|
||||
const root = process.cwd();
|
||||
const optionValue = (name) => {
|
||||
const index = process.argv.indexOf(name);
|
||||
return index >= 0 ? process.argv[index + 1] : undefined;
|
||||
};
|
||||
const project = loadProjectManifest({
|
||||
root,
|
||||
manifestPath: optionValue('--manifest'),
|
||||
projectId: optionValue('--project'),
|
||||
});
|
||||
const featureId = project.feature.id;
|
||||
const artifactPaths = projectArtifactPaths(project);
|
||||
// Plan-01: harness relocated to packages/casan-harness/, domain to apps/okr/domain/;
|
||||
// fall back to the pre-move paths so this runner works from either layout.
|
||||
const HARNESS_BASH = existsSync(join(root, 'packages/casan-harness/scripts/bash'))
|
||||
? 'packages/casan-harness/scripts/bash'
|
||||
: '.specify/scripts/bash';
|
||||
const HARNESS = `${HARNESS_BASH}/casan-harness.sh`;
|
||||
const GOLDEN_PLAN = existsSync(join(root, 'apps/okr/domain/golden-runs/okr-plan.golden.txt'))
|
||||
? 'apps/okr/domain/golden-runs/okr-plan.golden.txt'
|
||||
: '.specify/level5/golden-runs/okr-plan.golden.txt';
|
||||
const logDir = `docs/output/output_logs/${featureId}`;
|
||||
const goldenCandidates = [
|
||||
`${project.domain_root}/golden-runs/plan.golden.txt`,
|
||||
`${project.domain_root}/golden-runs/${project.project_id}-plan.golden.txt`,
|
||||
`${project.domain_root}/golden-runs/okr-plan.golden.txt`,
|
||||
];
|
||||
const GOLDEN_PLAN = goldenCandidates.find((candidate) => existsSync(join(root, candidate)));
|
||||
const logDir = artifactPaths.logDir;
|
||||
const casanDir = `${logDir}/casan`;
|
||||
const reportsDir = `${logDir}/reports`;
|
||||
const contextPath = dryRun ? `${logDir}/pipeline-context.dryrun.yaml` : `${logDir}/pipeline-context.yaml`;
|
||||
@@ -47,19 +60,19 @@ const DIAGRAM_STEP = {
|
||||
};
|
||||
|
||||
const FULL_DIAGRAM = [
|
||||
['STEP1', 'okr.srs'],
|
||||
['STEP2', 'okr.bd'],
|
||||
['STEP1', 'casan.srs'],
|
||||
['STEP2', 'casan.bd'],
|
||||
['STEP3', 'speckit.specify'],
|
||||
['STEP4', 'speckit.clarify'],
|
||||
['STEP5', 'okr.reviewspec'],
|
||||
['STEP5', 'casan.reviewspec'],
|
||||
['STEP6', 'speckit.plan'],
|
||||
['STEP7', 'okr.reviewplan'],
|
||||
['STEP8', 'okr.dd'],
|
||||
['STEP8b', 'okr.testkit'],
|
||||
['STEP7', 'casan.reviewplan'],
|
||||
['STEP8', 'casan.dd'],
|
||||
['STEP8b', 'casan.testkit'],
|
||||
['STEP9', 'speckit.tasks'],
|
||||
['STEP10', 'speckit.implement'],
|
||||
['STEP11', 'okr.reviewcode'],
|
||||
['STEP12', 'okr.testkit run-tests'],
|
||||
['STEP11', 'casan.reviewcode'],
|
||||
['STEP12', 'casan.testkit run-tests'],
|
||||
['STEP13', 'deploy'],
|
||||
];
|
||||
|
||||
@@ -70,7 +83,7 @@ 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`,
|
||||
`feature-id: ${featureId}\nproject-id: ${project.project_id}\nmanifest: ${project.manifest_path}\nmodule-id: ${project.feature.module_id}\nmodule-keyword: ${project.feature.slug}\ntech-stack: ${project.tech_stack || 'defined by architecture'}\nsteps:\n`,
|
||||
'utf8',
|
||||
);
|
||||
writeFileSync(bossLog, `# Boss Log ${featureId}\n\n`, 'utf8');
|
||||
@@ -176,7 +189,7 @@ function runHarness({ id, agent, step, attempt = '1', extraEnv = {} }) {
|
||||
const input = `${casanDir}/${id}-input.txt`;
|
||||
const output = `${casanDir}/${id}-output.md`;
|
||||
const phaseReport = `${casanDir}/${id}-phases.json`;
|
||||
const payload = `feature ${featureId}\nstep ${id}\nagent ${agent}\nattempt ${attempt}\nsource docs/input/okr-requirement.md\n`;
|
||||
const payload = `project ${project.project_id}\nfeature ${featureId}\nstep ${id}\nagent ${agent}\nattempt ${attempt}\nsource ${project.requirements}\nmanifest ${project.manifest_path}\n`;
|
||||
writeFileSync(input, payload, 'utf8');
|
||||
appendBoss(`START ${id} ${agent} attempt ${attempt}`);
|
||||
log('debug', 'boss', `${diagram} start agent=${agent} attempt=${attempt} action=agent_step_${id}`);
|
||||
@@ -195,6 +208,9 @@ function runHarness({ id, agent, step, attempt = '1', extraEnv = {} }) {
|
||||
CASAN_AGENT_NAME: agent,
|
||||
CASAN_STEP_NAME: id,
|
||||
CASAN_PHASE_REPORT: phaseReport,
|
||||
CASAN_PROJECT_MANIFEST: project.manifest_path,
|
||||
CASAN_PROJECT_ID: project.project_id,
|
||||
CASAN_DOMAIN_ROOT: join(root, project.domain_root),
|
||||
...extraEnv,
|
||||
},
|
||||
},
|
||||
@@ -269,6 +285,9 @@ function runDryStep({ diagram, agent, attempt = '1', verdict = 'APPROVED', extra
|
||||
CASAN_AGENT_NAME: agent,
|
||||
CASAN_STEP_NAME: id,
|
||||
CASAN_PHASE_REPORT: phaseReport,
|
||||
CASAN_PROJECT_MANIFEST: project.manifest_path,
|
||||
CASAN_PROJECT_ID: project.project_id,
|
||||
CASAN_DOMAIN_ROOT: join(root, project.domain_root),
|
||||
...extraEnv,
|
||||
},
|
||||
},
|
||||
@@ -322,8 +341,8 @@ if (dryRun) {
|
||||
|
||||
// ───────────────────────────── real pipeline ─────────────────────────────
|
||||
const sequence = [
|
||||
{ id: '01-srs', agent: 'okr.srs', step: '01-srs' },
|
||||
{ id: '02-bd', agent: 'okr.bd', step: '02-bd' },
|
||||
{ id: '01-srs', agent: 'casan.srs', step: '01-srs' },
|
||||
{ id: '02-bd', agent: 'casan.bd', step: '02-bd' },
|
||||
{ id: '03-spec', agent: 'speckit.specify', step: '03-spec' },
|
||||
];
|
||||
|
||||
@@ -331,7 +350,7 @@ for (const item of sequence) {
|
||||
runHarness(item);
|
||||
}
|
||||
|
||||
let reviewSpec = runHarness({ id: '04-reviewspec', agent: 'okr.reviewspec', step: '04-reviewspec' });
|
||||
let reviewSpec = runHarness({ id: '04-reviewspec', agent: 'casan.reviewspec', step: '04-reviewspec' });
|
||||
if (reviewSpec.verdict === 'REJECTED') {
|
||||
logLoop('STEP5', reviewSpec.verdict, 'STEP3', 'review-spec rejected; retrying spec with template fallback');
|
||||
runHarness({
|
||||
@@ -343,7 +362,7 @@ if (reviewSpec.verdict === 'REJECTED') {
|
||||
});
|
||||
reviewSpec = runHarness({
|
||||
id: '04c-reviewspec-attempt-2',
|
||||
agent: 'okr.reviewspec',
|
||||
agent: 'casan.reviewspec',
|
||||
step: '04-reviewspec',
|
||||
attempt: '2',
|
||||
extraEnv: { CASAN_MODEL_PRIMARY: process.env.CASAN_REVIEW_ESCALATE_MODEL || 'openai:gpt-4o-mini' },
|
||||
@@ -356,7 +375,7 @@ if (reviewSpec.verdict === 'REJECTED') {
|
||||
|
||||
runHarness({ id: '05-plan-attempt-1', agent: 'speckit.plan', step: '05-plan', attempt: '1' });
|
||||
|
||||
const reviewPlan1 = runHarness({ id: '06-reviewplan-attempt-1', agent: 'okr.reviewplan', step: '06-reviewplan', attempt: '1' });
|
||||
const reviewPlan1 = runHarness({ id: '06-reviewplan-attempt-1', agent: 'casan.reviewplan', step: '06-reviewplan', attempt: '1' });
|
||||
if (reviewPlan1.verdict === 'REJECTED') {
|
||||
logLoop('STEP7', reviewPlan1.verdict, 'STEP6', 'BACK-TO-PLAN: retrying plan with missing criteria fixed');
|
||||
}
|
||||
@@ -375,7 +394,9 @@ execFileSync(
|
||||
'--primary',
|
||||
'cat /nonexistent/casan/primary-model-endpoint',
|
||||
'--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"',
|
||||
project.project_id === 'okr'
|
||||
? '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"'
|
||||
: 'printf "Generate a safe project plan.\\nExpected sections:\\n- Requirements traceability\\n- Architecture constraints\\n- Security gate\\n- Governance decision\\n- Build and test evidence\\n- Rollback strategy\\n"',
|
||||
],
|
||||
{ cwd: root, stdio: 'inherit' },
|
||||
);
|
||||
@@ -387,17 +408,20 @@ appendBoss(`Model fallback invoked; output ${fallbackOut}`);
|
||||
// independently in adversarial-harness-tests.sh (H7 drift, two different files).
|
||||
const driftCandidate = `${casanDir}/drift-plan-candidate.txt`;
|
||||
copyFileSync(fallbackOut, driftCandidate);
|
||||
if (!GOLDEN_PLAN) {
|
||||
throw new Error(`CASAN_GOLDEN_PLAN_MISSING: ${project.domain_root}/golden-runs`);
|
||||
}
|
||||
log('debug', 'boss', 'drift-detect: fallback output vs golden baseline');
|
||||
execFileSync(`${HARNESS_BASH}/drift-detect.sh`, [
|
||||
GOLDEN_PLAN,
|
||||
driftCandidate,
|
||||
'.specify/logs/level5/okr-plan-drift-report.json',
|
||||
`.specify/logs/level5/${project.project_id}-plan-drift-report.json`,
|
||||
], { cwd: root, stdio: 'inherit' });
|
||||
appendBoss('Drift detection invoked: fallback output vs golden baseline.');
|
||||
|
||||
const reviewPlan2 = runHarness({
|
||||
id: '08-reviewplan-attempt-2',
|
||||
agent: 'okr.reviewplan',
|
||||
agent: 'casan.reviewplan',
|
||||
step: '06-reviewplan',
|
||||
attempt: '2',
|
||||
extraEnv: { CASAN_MODEL_PRIMARY: process.env.CASAN_REVIEW_ESCALATE_MODEL || 'openai:gpt-4o-mini' },
|
||||
@@ -406,17 +430,17 @@ if (reviewPlan2.verdict === 'REJECTED') {
|
||||
logLoop('STEP7', reviewPlan2.verdict, 'STEP6', 'BACK-TO-PLAN attempt 2 still rejected');
|
||||
throw new Error('review-plan rejected after retry');
|
||||
}
|
||||
runHarness({ id: '09-dd', agent: 'okr.dd', step: '07-dd' });
|
||||
runHarness({ id: '10-testkit', agent: 'okr.testkit', step: '08-testkit' });
|
||||
runHarness({ id: '09-dd', agent: 'casan.dd', step: '07-dd' });
|
||||
runHarness({ id: '10-testkit', agent: 'casan.testkit', step: '08-testkit' });
|
||||
runHarness({ id: '11-tasks', agent: 'speckit.tasks', step: '09-tasks' });
|
||||
runHarness({ id: '12-implement', agent: 'speckit.implement', step: '10-implement' });
|
||||
const reviewCode = runHarness({ id: '13-reviewcode', agent: 'okr.reviewcode', step: '11-reviewcode' });
|
||||
const reviewCode = runHarness({ id: '13-reviewcode', agent: 'casan.reviewcode', step: '11-reviewcode' });
|
||||
if (reviewCode.verdict === 'REJECTED') {
|
||||
logLoop('STEP11', reviewCode.verdict, 'STEP10', 'review-code rejected; implement step must be re-run');
|
||||
runHarness({ id: '13b-implement-attempt-2', agent: 'speckit.implement', step: '10-implement', attempt: '2' });
|
||||
const reviewCode2 = runHarness({
|
||||
id: '13c-reviewcode-attempt-2',
|
||||
agent: 'okr.reviewcode',
|
||||
agent: 'casan.reviewcode',
|
||||
step: '11-reviewcode',
|
||||
attempt: '2',
|
||||
extraEnv: { CASAN_MODEL_PRIMARY: process.env.CASAN_REVIEW_ESCALATE_MODEL || 'openai:gpt-4o-mini' },
|
||||
@@ -426,12 +450,12 @@ if (reviewCode.verdict === 'REJECTED') {
|
||||
throw new Error('review-code rejected after implement retry');
|
||||
}
|
||||
}
|
||||
const runTests = runHarness({ id: '14-runtests', agent: 'okr.testkit run-tests', step: '12-runtests' });
|
||||
const runTests = runHarness({ id: '14-runtests', agent: 'casan.testkit run-tests', step: '12-runtests' });
|
||||
if (runTests.verdict === 'FAIL') {
|
||||
logLoop('STEP12', runTests.verdict, 'STEP6', 'tests FAIL: re-plan/re-implement before acceptance');
|
||||
}
|
||||
|
||||
const rollbackDir = 'docs/output/casan/app-evidence';
|
||||
const rollbackDir = `${project.artifacts_root}/casan/${project.project_id}/app-evidence`;
|
||||
mkdirSync(rollbackDir, { recursive: true });
|
||||
const rollbackTarget = `${rollbackDir}/rollback-target.txt`;
|
||||
writeFileSync(rollbackTarget, 'original pipeline rollback content\n', 'utf8');
|
||||
|
||||
Reference in New Issue
Block a user