cclaw-cli 0.48.4 → 0.48.5
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/dist/content/observe.js +1 -1
- package/dist/eval/agents/workflow.js +22 -2
- package/dist/gate-evidence.js +30 -1
- package/package.json +1 -1
package/dist/content/observe.js
CHANGED
|
@@ -1289,7 +1289,7 @@ export function codexHooksJsonWithObservation() {
|
|
|
1289
1289
|
command: hookDispatcherCommand("workflow-guard.sh")
|
|
1290
1290
|
}, {
|
|
1291
1291
|
type: "command",
|
|
1292
|
-
command: "bash -lc 'if ! command -v cclaw >/dev/null 2>&1; then echo \"[cclaw] codex hook: cclaw binary is required for verify-current-state\" >&2; exit 1; fi; cclaw internal verify-current-state --quiet >/dev/null || true'"
|
|
1292
|
+
command: "bash -lc 'if ! command -v cclaw >/dev/null 2>&1; then echo \"[cclaw] codex hook: cclaw binary is required for verify-current-state\" >&2; exit 1; fi; MODE=\"${CCLAW_WORKFLOW_GUARD_MODE:-advisory}\"; if [ \"$MODE\" = \"strict\" ]; then cclaw internal verify-current-state --quiet >/dev/null; else cclaw internal verify-current-state --quiet >/dev/null || true; fi'"
|
|
1293
1293
|
}]
|
|
1294
1294
|
}],
|
|
1295
1295
|
PreToolUse: [{
|
|
@@ -29,9 +29,17 @@ import fs from "node:fs/promises";
|
|
|
29
29
|
import path from "node:path";
|
|
30
30
|
import { createSandbox } from "../sandbox.js";
|
|
31
31
|
import { loadStageSkill } from "./single-shot.js";
|
|
32
|
-
import { runWithTools } from "./with-tools.js";
|
|
32
|
+
import { MaxTurnsExceededError, runWithTools } from "./with-tools.js";
|
|
33
33
|
const STAGES_SUBDIR = "stages";
|
|
34
34
|
const ARTIFACT_CANDIDATES = ["artifact.md", "artifact.txt", "ARTIFACT.md"];
|
|
35
|
+
const DEFAULT_WORKFLOW_MAX_TOTAL_TURNS = 40;
|
|
36
|
+
const DEFAULT_STAGE_TURN_CAP = 8;
|
|
37
|
+
function clampPositive(value, fallback) {
|
|
38
|
+
if (!Number.isInteger(value) || value < 1) {
|
|
39
|
+
return fallback;
|
|
40
|
+
}
|
|
41
|
+
return value;
|
|
42
|
+
}
|
|
35
43
|
export async function runWorkflow(input) {
|
|
36
44
|
const { workflow, config, projectRoot, client } = input;
|
|
37
45
|
const sandboxFactory = input.createSandboxFn ?? createSandbox;
|
|
@@ -43,9 +51,17 @@ export async function runWorkflow(input) {
|
|
|
43
51
|
const artifacts = new Map();
|
|
44
52
|
let totalUsageUsd = 0;
|
|
45
53
|
let totalDurationMs = 0;
|
|
54
|
+
let totalTurns = 0;
|
|
55
|
+
const workflowTurnBudget = clampPositive(config.workflowMaxTotalTurns, DEFAULT_WORKFLOW_MAX_TOTAL_TURNS);
|
|
46
56
|
try {
|
|
47
57
|
await fs.mkdir(await sandbox.resolve(STAGES_SUBDIR, { allowMissing: true }), { recursive: true });
|
|
48
58
|
for (const step of workflow.stages) {
|
|
59
|
+
const remainingWorkflowTurns = workflowTurnBudget - totalTurns;
|
|
60
|
+
if (remainingWorkflowTurns < 1) {
|
|
61
|
+
throw new MaxTurnsExceededError(workflowTurnBudget);
|
|
62
|
+
}
|
|
63
|
+
const perStageTurnCap = clampPositive(config.toolMaxTurns, DEFAULT_STAGE_TURN_CAP);
|
|
64
|
+
const stageTurnBudget = Math.min(perStageTurnCap, remainingWorkflowTurns);
|
|
49
65
|
input.onStageStart?.(step.name);
|
|
50
66
|
await clearArtifactFile(sandbox);
|
|
51
67
|
const priorStages = stageResults.map((r) => r.stage);
|
|
@@ -58,7 +74,10 @@ export async function runWorkflow(input) {
|
|
|
58
74
|
};
|
|
59
75
|
const result = await runWithTools({
|
|
60
76
|
caseEntry,
|
|
61
|
-
config
|
|
77
|
+
config: {
|
|
78
|
+
...config,
|
|
79
|
+
toolMaxTurns: stageTurnBudget
|
|
80
|
+
},
|
|
62
81
|
projectRoot,
|
|
63
82
|
client,
|
|
64
83
|
...(input.tools ? { tools: input.tools } : {}),
|
|
@@ -87,6 +106,7 @@ export async function runWorkflow(input) {
|
|
|
87
106
|
input.onStageEnd?.(step.name, stageResult);
|
|
88
107
|
totalUsageUsd += result.usageUsd;
|
|
89
108
|
totalDurationMs += result.durationMs;
|
|
109
|
+
totalTurns += result.toolUse.turns;
|
|
90
110
|
}
|
|
91
111
|
return {
|
|
92
112
|
caseId: workflow.id,
|
package/dist/gate-evidence.js
CHANGED
|
@@ -7,6 +7,7 @@ import { readDelegationLedger } from "./delegation.js";
|
|
|
7
7
|
import { ensureDir, exists, writeFileSafe } from "./fs-utils.js";
|
|
8
8
|
import { detectPublicApiChanges } from "./internal/detect-public-api-changes.js";
|
|
9
9
|
import { readFlowState, writeFlowState } from "./runs.js";
|
|
10
|
+
import { parseTddCycleLog, validateTddCycleOrder } from "./tdd-cycle.js";
|
|
10
11
|
import { buildTraceMatrix } from "./trace-matrix.js";
|
|
11
12
|
import { FLOW_STAGES } from "./types.js";
|
|
12
13
|
async function currentStageArtifactExists(projectRoot, stage, track) {
|
|
@@ -200,6 +201,7 @@ export async function verifyCurrentStageGateEvidence(projectRoot, flowState) {
|
|
|
200
201
|
issues.push(`stale triggered conditional gate "${gateId}" found in stageGateCatalog.triggered for stage "${stage}" (conditional gate DSL removed).`);
|
|
201
202
|
}
|
|
202
203
|
const blockedSet = new Set(catalog.blocked);
|
|
204
|
+
const passedSet = new Set(catalog.passed);
|
|
203
205
|
for (const gateId of catalog.passed) {
|
|
204
206
|
if (!allowedSet.has(gateId)) {
|
|
205
207
|
issues.push(`passed gate "${gateId}" is not defined for stage "${stage}".`);
|
|
@@ -239,6 +241,13 @@ export async function verifyCurrentStageGateEvidence(projectRoot, flowState) {
|
|
|
239
241
|
if (!verdictConsistency.ok) {
|
|
240
242
|
issues.push(`review verdict inconsistency: ${verdictConsistency.errors.join("; ")}`);
|
|
241
243
|
}
|
|
244
|
+
const reviewCriticalsClaimedResolved = passedSet.has("review_criticals_resolved") || flowState.completedStages.includes("review");
|
|
245
|
+
const unresolvedCriticals = verdictConsistency.openCriticalCount > 0 || verdictConsistency.shipBlockerCount > 0;
|
|
246
|
+
if (reviewCriticalsClaimedResolved && unresolvedCriticals) {
|
|
247
|
+
issues.push(`review criticals gate blocked (review_criticals_resolved): review-army still reports ` +
|
|
248
|
+
`${verdictConsistency.openCriticalCount} open critical(s) and ` +
|
|
249
|
+
`${verdictConsistency.shipBlockerCount} ship blocker(s).`);
|
|
250
|
+
}
|
|
242
251
|
const securityAttestation = await checkReviewSecurityNoChangeAttestation(projectRoot);
|
|
243
252
|
if (!securityAttestation.ok) {
|
|
244
253
|
issues.push(`review security attestation failed: ${securityAttestation.errors.join("; ")}`);
|
|
@@ -309,9 +318,29 @@ export async function verifyCurrentStageGateEvidence(projectRoot, flowState) {
|
|
|
309
318
|
issues.push(`tdd docs drift gate blocked (tdd_docs_drift_check): public surface changes detected (${docsDriftDetection.changedFiles.join(", ")}) but no completed doc-updater delegation exists for the active run.`);
|
|
310
319
|
}
|
|
311
320
|
}
|
|
321
|
+
const tddLogPath = path.join(projectRoot, RUNTIME_ROOT, "state", "tdd-cycle-log.jsonl");
|
|
322
|
+
if (await exists(tddLogPath)) {
|
|
323
|
+
try {
|
|
324
|
+
const tddLogRaw = await fs.readFile(tddLogPath, "utf8");
|
|
325
|
+
const parsedCycles = parseTddCycleLog(tddLogRaw);
|
|
326
|
+
const tddOrderValidation = validateTddCycleOrder(parsedCycles, {
|
|
327
|
+
runId: flowState.activeRunId
|
|
328
|
+
});
|
|
329
|
+
if (!tddOrderValidation.ok) {
|
|
330
|
+
const details = [...tddOrderValidation.issues];
|
|
331
|
+
if (tddOrderValidation.openRedSlices.length > 0) {
|
|
332
|
+
details.push(`open red slices: ${tddOrderValidation.openRedSlices.join(", ")}`);
|
|
333
|
+
}
|
|
334
|
+
issues.push(`tdd cycle order gate blocked: ${details.join("; ")}`);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
catch (err) {
|
|
338
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
339
|
+
issues.push(`tdd cycle order gate blocked: unable to read tdd-cycle-log.jsonl (${reason}).`);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
312
342
|
}
|
|
313
343
|
}
|
|
314
|
-
const passedSet = new Set(catalog.passed);
|
|
315
344
|
const missingRequired = required.filter((gateId) => !passedSet.has(gateId));
|
|
316
345
|
const missingRecommended = recommended.filter((gateId) => !passedSet.has(gateId));
|
|
317
346
|
const missingTriggeredConditional = [];
|