fix template, remove okr, use casan.*

This commit is contained in:
thanhnv
2026-07-18 16:45:31 +07:00
parent 0dfd1742d3
commit 13fae3e6c3
249 changed files with 4881 additions and 5702 deletions
+54 -30
View File
@@ -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');