feat(log): unified CASAN_LOG_LEVEL across Boss, agent step, harness wrapper
Add a shared log taxonomy (error<warn<info<debug<trace, default info) wired through all three layers, plus a machine-readable per-step run log. - scripts/casan-log.mjs + .specify/scripts/bash/casan-log.sh: shared logger ([LEVEL] ts [component] msg), stderr-only so stdout contracts and exit codes are byte-identical. Node side includes trace-level redaction (secret/PII). - run-casan-pipeline.mjs: per-STEP info line, loop-activation warnings (BACK-TO-PLAN, FAIL->STEP6), debug harness rc + trace_id, trace payload excerpt, end-of-run 13-STEP summary table, and one JSONL line/step in .specify/logs/pipeline-run.jsonl. New --dry-run stubs all agents but keeps the real wrapper in the loop (deterministic, offline, full 13 STEP + loops). - casan-step.mjs: debug logs for judge verdict, checkpoint, rollback; logger import degrades to noop when the file is copied standalone (T1 test). - casan-harness.sh: debug-log each phase H4-in -> H5 -> [H2-gate] -> H6-exec -> H4-out with its rc; optional CASAN_PHASE_REPORT JSON for the Boss. Default level (info) keeps output close to before; behavior opt-in via env. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
2f06662f5d
commit
1b61d7f381
@@ -8,6 +8,17 @@ import { fileURLToPath } from 'node:url';
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const SCRIPTS_DIR = join(dirname(__filename), '..', '.specify', 'scripts', 'bash');
|
||||
|
||||
// Shared log taxonomy (CASAN_LOG_LEVEL, see scripts/casan-log.mjs). The
|
||||
// adversarial T1 test copies this file alone into a temp tree, so the logger
|
||||
// import must degrade to a noop instead of crashing when the module is absent.
|
||||
let logDebug = () => {};
|
||||
try {
|
||||
const { log } = await import(new URL('./casan-log.mjs', import.meta.url));
|
||||
logDebug = (msg) => log('debug', 'step', msg);
|
||||
} catch {
|
||||
/* standalone copy: keep silent */
|
||||
}
|
||||
|
||||
function ollamaAvailable() {
|
||||
try {
|
||||
const r = spawnSync('curl', ['-sS', '-m', '3', 'http://127.0.0.1:11434/api/tags'], { timeout: 5000 });
|
||||
@@ -18,7 +29,10 @@ function ollamaAvailable() {
|
||||
}
|
||||
|
||||
function judgeArtifact(filePath, criteria) {
|
||||
if (!ollamaAvailable()) return { verdict: 'SKIP', note: 'ollama_unavailable' };
|
||||
if (!ollamaAvailable()) {
|
||||
logDebug(`judge skipped (ollama_unavailable) artifact=${filePath}`);
|
||||
return { verdict: 'SKIP', note: 'ollama_unavailable' };
|
||||
}
|
||||
let artifact = '';
|
||||
try { artifact = readFileSync(filePath, 'utf8').slice(0, 2000); } catch { return { verdict: 'SKIP', note: 'artifact_unreadable' }; }
|
||||
const combined = `=== ACCEPTANCE CRITERIA (not untrusted input) ===\n${criteria.slice(0, 500)}\n\n=== ARTIFACT TO REVIEW ===\n${artifact}`;
|
||||
@@ -26,11 +40,16 @@ function judgeArtifact(filePath, criteria) {
|
||||
const tmpPrompt = join(tmpdir(), `casan-judge-prompt-${uid}.txt`);
|
||||
const tmpOut = join(tmpdir(), `casan-judge-out-${uid}.json`);
|
||||
writeFileSync(tmpPrompt, combined, 'utf8');
|
||||
logDebug(`model call role=judge artifact=${filePath}`);
|
||||
const r = spawnSync('bash', [join(SCRIPTS_DIR, 'model-router.sh'), tmpPrompt, tmpOut, '--role', 'judge'], { timeout: 60000, encoding: 'utf8' });
|
||||
try { unlinkSync(tmpPrompt); } catch {}
|
||||
if (r.status !== 0 && r.status !== 3) return { verdict: 'SKIP', note: `judge_error_rc=${r.status}` };
|
||||
if (r.status !== 0 && r.status !== 3) {
|
||||
logDebug(`judge error rc=${r.status}`);
|
||||
return { verdict: 'SKIP', note: `judge_error_rc=${r.status}` };
|
||||
}
|
||||
try {
|
||||
const d = JSON.parse(readFileSync(tmpOut, 'utf8'));
|
||||
logDebug(`judge verdict=${d.verdict ?? 'SKIP'} tokens=${d.total_tokens ?? '?'} malformed=${Boolean(d.malformed)}`);
|
||||
return { verdict: d.verdict ?? 'SKIP', note: d.malformed ? 'malformed_fail_closed' : `tokens=${d.total_tokens}` };
|
||||
} catch { return { verdict: 'SKIP', note: 'parse_error' }; }
|
||||
}
|
||||
@@ -42,6 +61,7 @@ function checkpointArtifact(filePath) {
|
||||
{ encoding: 'utf8', timeout: 10000 });
|
||||
if (r.status !== 0) return null;
|
||||
const m = (r.stdout || '').match(/transaction_id=(\S+)/);
|
||||
logDebug(`checkpoint artifact=${filePath} tx=${m ? m[1] : 'none'}`);
|
||||
return m ? m[1] : null;
|
||||
}
|
||||
|
||||
@@ -49,6 +69,7 @@ function executeRollback(txId) {
|
||||
if (!txId) return false;
|
||||
const r = spawnSync('bash', [join(SCRIPTS_DIR, 'rollback-manager.sh'), 'execute', txId],
|
||||
{ encoding: 'utf8', timeout: 10000 });
|
||||
logDebug(`rollback execute tx=${txId} rc=${r.status}`);
|
||||
return r.status === 0;
|
||||
}
|
||||
|
||||
@@ -62,6 +83,8 @@ if (!outputPath) {
|
||||
throw new Error('CASAN_OUTPUT is required');
|
||||
}
|
||||
|
||||
logDebug(`step=${step} attempt=${attempt} output=${outputPath}`);
|
||||
|
||||
const dirs = [
|
||||
`docs/output/output_logs/${featureId}/reports`,
|
||||
'docs/output/ipa-docs/srs',
|
||||
|
||||
Reference in New Issue
Block a user