intentdna 1.7.5 → 1.8.1
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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/LICENSE +201 -0
- package/README.md +143 -194
- package/dist/assets/candidates.d.ts +124 -0
- package/dist/assets/candidates.js +1249 -0
- package/dist/assets/catalog.d.ts +101 -0
- package/dist/assets/catalog.js +311 -0
- package/dist/assets/evidence.d.ts +93 -0
- package/dist/assets/evidence.js +391 -0
- package/dist/assets/index.d.ts +4 -0
- package/dist/assets/index.js +4 -0
- package/dist/assets/metadata.d.ts +19 -0
- package/dist/assets/metadata.js +227 -0
- package/dist/cli/commands/assets.d.ts +65 -0
- package/dist/cli/commands/assets.js +2871 -0
- package/dist/cli/commands/auth.d.ts +322 -0
- package/dist/cli/commands/auth.js +1592 -0
- package/dist/cli/commands/cloud-auth-guidance.d.ts +2 -0
- package/dist/cli/commands/cloud-auth-guidance.js +4 -0
- package/dist/cli/commands/cloud.d.ts +1359 -0
- package/dist/cli/commands/cloud.js +8364 -0
- package/dist/cli/commands/evolve.js +18 -2
- package/dist/cli/commands/generate.js +25 -2
- package/dist/cli/commands/guard.d.ts +1 -1
- package/dist/cli/commands/guard.js +1 -1
- package/dist/cli/commands/guide.d.ts +15 -0
- package/dist/cli/commands/guide.js +1747 -0
- package/dist/cli/commands/init.d.ts +12 -0
- package/dist/cli/commands/init.js +95 -60
- package/dist/cli/commands/org.d.ts +16 -0
- package/dist/cli/commands/org.js +2393 -0
- package/dist/cli/commands/release.d.ts +59 -0
- package/dist/cli/commands/release.js +716 -0
- package/dist/cli/commands/report.d.ts +82 -0
- package/dist/cli/commands/report.js +1516 -0
- package/dist/cli/commands/run.d.ts +8 -1
- package/dist/cli/commands/run.js +129 -57
- package/dist/cli/commands/sync.d.ts +8 -1
- package/dist/cli/commands/sync.js +436 -141
- package/dist/cli/commands/templates.d.ts +64 -0
- package/dist/cli/commands/templates.js +2151 -1
- package/dist/cli/commands/workflow.d.ts +26 -0
- package/dist/cli/commands/workflow.js +3053 -0
- package/dist/cli/index.js +703 -36
- package/dist/compiler/cascade.js +4 -9
- package/dist/compiler/compile.js +1 -0
- package/dist/governance/evidence-capture-attribution.d.ts +12 -0
- package/dist/governance/evidence-capture-attribution.js +46 -0
- package/dist/governance/index.d.ts +2 -0
- package/dist/governance/index.js +1 -0
- package/dist/hooks/cli.d.ts +9 -0
- package/dist/hooks/cli.js +170 -411
- package/dist/hooks/enforce.d.ts +1 -0
- package/dist/hooks/protocol.d.ts +1 -1
- package/dist/hooks/protocol.js +1 -1
- package/dist/hooks/state.d.ts +41 -0
- package/dist/hooks/state.js +126 -2
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/mcp/tools-state.js +2 -0
- package/dist/organization/index.d.ts +121 -0
- package/dist/organization/index.js +413 -0
- package/dist/runtime/claude-sync-target.d.ts +49 -0
- package/dist/runtime/claude-sync-target.js +280 -0
- package/dist/runtime/codex-adapter.d.ts +254 -0
- package/dist/runtime/codex-adapter.js +1204 -0
- package/dist/runtime/codex-sync-target.d.ts +33 -0
- package/dist/runtime/codex-sync-target.js +241 -0
- package/dist/runtime/index.d.ts +8 -0
- package/dist/runtime/index.js +4 -0
- package/dist/runtime/output-artifact-registry.d.ts +1 -1
- package/dist/runtime/output-artifact-registry.js +2 -0
- package/dist/runtime/settings-adapter.d.ts +1 -1
- package/dist/runtime/settings-adapter.js +1 -1
- package/dist/runtime/skill-adapter.d.ts +5 -1
- package/dist/runtime/skill-adapter.js +164 -39
- package/dist/runtime/sync-artifact-registry.d.ts +99 -0
- package/dist/runtime/sync-artifact-registry.js +194 -0
- package/dist/runtime/sync-target-plan.d.ts +45 -0
- package/dist/runtime/sync-target-plan.js +19 -0
- package/dist/runtime/verifier.d.ts +51 -0
- package/dist/runtime/verifier.js +462 -0
- package/dist/schema/controller-registry.d.ts +8 -1
- package/dist/schema/controller-registry.js +8 -0
- package/dist/schema/types.d.ts +35 -23
- package/dist/schema/types.js +10 -1
- package/dist/schema/validate.js +22 -22
- package/dist/schema/validators/controllers.js +38 -20
- package/dist/schema/workflow-authoring-contract.d.ts +29 -0
- package/dist/schema/workflow-authoring-contract.js +109 -0
- package/dist/schema/yaml-parser.js +18 -0
- package/dist/templates/api-backend.dna.yaml +19 -0
- package/dist/templates/brainstorming-first.dna.yaml +19 -0
- package/dist/templates/code-cleanup.dna.yaml +19 -0
- package/dist/templates/code-review-pipeline.dna.yaml +18 -0
- package/dist/templates/documentation-writer.dna.yaml +19 -0
- package/dist/templates/flutter-behavior-lock.dna.yaml +19 -0
- package/dist/templates/flutter-refactoring-rescue.dna.yaml +19 -0
- package/dist/templates/flutter-rewrite-diagnosis-review.dna.yaml +210 -0
- package/dist/templates/flutter-rewrite-fix-review-loop.dna.yaml +260 -0
- package/dist/templates/flutter-rewrite-new.dna.yaml +506 -0
- package/dist/templates/flutter-rewrite.dna.yaml +100 -4
- package/dist/templates/frontend-quality.dna.yaml +19 -0
- package/dist/templates/full-pipeline.dna.yaml +20 -0
- package/dist/templates/marketplace.d.ts +92 -0
- package/dist/templates/marketplace.js +502 -0
- package/dist/templates/metadata.d.ts +25 -0
- package/dist/templates/metadata.js +136 -3
- package/dist/templates/mobile-dev.dna.yaml +19 -0
- package/dist/templates/multi-agent-handoff-coordination.dna.yaml +156 -0
- package/dist/templates/multi-perspective-review.dna.yaml +19 -0
- package/dist/templates/persistent-executor.dna.yaml +19 -0
- package/dist/templates/qa-loop.dna.yaml +18 -0
- package/dist/templates/release-evidence-gate.dna.yaml +131 -0
- package/dist/templates/requirements-gate.dna.yaml +19 -0
- package/dist/templates/research-orchestration.dna.yaml +19 -0
- package/dist/templates/root-cause-analysis.dna.yaml +19 -0
- package/dist/templates/safe-refactoring.dna.yaml +321 -0
- package/dist/templates/secure-dev.dna.yaml +19 -0
- package/dist/templates/subagent-parallel.dna.yaml +19 -0
- package/dist/templates/systematic-debugging.dna.yaml +19 -0
- package/dist/templates/tdd-strict.dna.yaml +19 -0
- package/dist/templates/ui-visual-regression-qa.dna.yaml +133 -0
- package/dist/templates/workflow-author.dna.yaml +302 -0
- package/dist/templates/workflow-blocked-scope-triage.dna.yaml +242 -0
- package/dist/templates/workflow-completion-audit.dna.yaml +254 -0
- package/dist/templates/workflow-cross-boundary-resource-approval.dna.yaml +249 -0
- package/dist/templates/workflow-evidence-review.dna.yaml +198 -0
- package/dist/templates/workflow-module-inventory.dna.yaml +257 -0
- package/dist/templates/workflow-runtime-smoke-review.dna.yaml +252 -0
- package/dist/templates/workflow-verification-command-routing.dna.yaml +278 -0
- package/dist/workflow/authoring-packet.d.ts +67 -0
- package/dist/workflow/authoring-packet.js +218 -0
- package/dist/workflow/draft.d.ts +159 -0
- package/dist/workflow/draft.js +800 -0
- package/dist/workflow/index.d.ts +3 -0
- package/dist/workflow/index.js +3 -0
- package/dist/workflow/suggestion-review.d.ts +65 -0
- package/dist/workflow/suggestion-review.js +245 -0
- package/package.json +1 -1
- package/spec/README.md +15 -48
- package/spec/codex-adapter-contract.md +176 -0
- package/spec/sync-target-plan.md +158 -0
- package/spec/workflow-capability-ir.md +153 -0
- package/spec/agent-dispatch-fix.md +0 -116
- package/spec/control-plane-convergence-handoff-2026-04-24.md +0 -432
- package/spec/flutter-rewrite-template-optimization.md +0 -301
- package/spec/foundation-hardening.md +0 -178
- package/spec/hooks-infra-harness-hardening.md +0 -741
- package/spec/multi-config.md +0 -172
- package/spec/parallel-isolation.md +0 -268
- package/spec/template-version-namespace-fix.md +0 -653
- package/spec/workflow-pipeline.md +0 -101
package/dist/hooks/cli.js
CHANGED
|
@@ -23,10 +23,12 @@ import { fileURLToPath } from "node:url";
|
|
|
23
23
|
import { readStdin, writeOutput, silentOutput, allowOutput, blockOutput, stopOutput } from "./protocol.js";
|
|
24
24
|
import { validateHookInput } from "./schema.js";
|
|
25
25
|
import { enforcePreToolUse, enforcePostToolUse, enforceUserPromptSubmit, enforceSubagentStop, enforcePreCompact, enforceNotification, enforceSessionStart, enforceStop, extractBashWritePaths, checkReflectionLimit, checkContextReadiness, checkWorkflowBoundary, } from "./enforce.js";
|
|
26
|
-
import { appendAudit, readWorkflowState, appendTrace, appendRuntimeDecisionEvent, rotateTraces, readTraces, cleanStaleState, readSurgeonAttempts, writeSurgeonAttempts, appendSessionRead, readSessionReads, appendVerifierResult, appendCompletedArtifact, artifactIdentity, buildArtifactKey, readArtifactManifest, resolveArtifactTemplate, safePathComponent, writeArtifactManifest } from "./state.js";
|
|
26
|
+
import { appendAudit, readWorkflowState, appendTrace, appendRuntimeDecisionEvent, appendEvidenceCaptureEvent, rotateTraces, readTraces, cleanStaleState, readSurgeonAttempts, writeSurgeonAttempts, appendSessionRead, readSessionReads, appendVerifierResult, appendCompletedArtifact, artifactIdentity, buildArtifactKey, readArtifactManifest, resolveArtifactTemplate, safePathComponent, writeArtifactManifest, EVIDENCE_CAPTURE_SCHEMA_VERSION } from "./state.js";
|
|
27
27
|
import { hookEventsForSurface } from "./event-registry.js";
|
|
28
28
|
import { writeAuditEvent } from "../audit/index.js";
|
|
29
|
-
import { RUNTIME_DECISION_EVENT_SCHEMA_VERSION } from "../governance/index.js";
|
|
29
|
+
import { RUNTIME_DECISION_EVENT_SCHEMA_VERSION, trustedEvidenceCaptureAttribution } from "../governance/index.js";
|
|
30
|
+
import { runCodexHook } from "../runtime/codex-adapter.js";
|
|
31
|
+
import { runCheckpointVerifier as runSharedCheckpointVerifier, runCompletionVerifier, toProjectRelativePath, } from "../runtime/verifier.js";
|
|
30
32
|
// ── Constants ──────────────────────────────────────────────
|
|
31
33
|
const DEFAULT_IR_PATH = ".dna/compiled/ir.json";
|
|
32
34
|
const VALID_EVENTS = new Set(hookEventsForSurface("cli"));
|
|
@@ -150,6 +152,74 @@ function buildRuntimeDecisionEvent(params) {
|
|
|
150
152
|
evidence_refs: [{ type: "trace", ref: params.traceId }],
|
|
151
153
|
};
|
|
152
154
|
}
|
|
155
|
+
function captureResultForHook(rawInput, decision) {
|
|
156
|
+
if (decision === "block")
|
|
157
|
+
return "blocked";
|
|
158
|
+
if (typeof rawInput.result === "string" && ["success", "failure", "blocked", "skipped", "unknown", "unsupported"].includes(rawInput.result)) {
|
|
159
|
+
return rawInput.result;
|
|
160
|
+
}
|
|
161
|
+
if (typeof rawInput.exit_code === "number")
|
|
162
|
+
return rawInput.exit_code === 0 ? "success" : "failure";
|
|
163
|
+
return "unknown";
|
|
164
|
+
}
|
|
165
|
+
function captureFactForHook(rawInput, decision, event) {
|
|
166
|
+
if (decision === "block")
|
|
167
|
+
return "blocker";
|
|
168
|
+
if (event === "PostToolUse" || event === "Stop" || rawInput.result || rawInput.exit_code !== undefined)
|
|
169
|
+
return "result";
|
|
170
|
+
const toolInput = typeof rawInput.tool_input === "object" && rawInput.tool_input !== null
|
|
171
|
+
? rawInput.tool_input
|
|
172
|
+
: {};
|
|
173
|
+
if (typeof rawInput.command === "string" || typeof toolInput.command === "string")
|
|
174
|
+
return "command";
|
|
175
|
+
return "result";
|
|
176
|
+
}
|
|
177
|
+
function commandForHook(rawInput) {
|
|
178
|
+
const toolInput = typeof rawInput.tool_input === "object" && rawInput.tool_input !== null
|
|
179
|
+
? rawInput.tool_input
|
|
180
|
+
: {};
|
|
181
|
+
const command = rawInput.command ?? toolInput.command;
|
|
182
|
+
return typeof command === "string" && command.trim().length > 0 ? command : undefined;
|
|
183
|
+
}
|
|
184
|
+
function buildClaudeEvidenceCaptureEvent(params) {
|
|
185
|
+
const fact = captureFactForHook(params.rawInput, params.decision, params.event);
|
|
186
|
+
const result = captureResultForHook(params.rawInput, params.decision);
|
|
187
|
+
const command = commandForHook(params.rawInput);
|
|
188
|
+
const toolName = typeof params.rawInput.tool_name === "string" ? params.rawInput.tool_name : "unknown tool";
|
|
189
|
+
const subject = command ?? `${toolName} ${params.event}`;
|
|
190
|
+
const attribution = trustedEvidenceCaptureAttribution(params.ir, {
|
|
191
|
+
workflowAsset: params.wfState?.workflow_asset,
|
|
192
|
+
workflowId: params.wfState?.workflow,
|
|
193
|
+
stepId: params.wfState?.current_step ?? params.runtimeEvent.step_id,
|
|
194
|
+
});
|
|
195
|
+
return {
|
|
196
|
+
schema_version: EVIDENCE_CAPTURE_SCHEMA_VERSION,
|
|
197
|
+
event_id: `c5:${params.runtimeEvent.event_id}`,
|
|
198
|
+
captured_at: params.runtimeEvent.timestamp,
|
|
199
|
+
project_root: params.projectDir,
|
|
200
|
+
source: "claude_code",
|
|
201
|
+
workflow_asset: attribution.workflowAsset,
|
|
202
|
+
workflow_id: attribution.workflowId,
|
|
203
|
+
run_id: params.runtimeEvent.run_id || params.runtimeEvent.session_id || "unknown",
|
|
204
|
+
step_id: attribution.stepId === "unknown" && !params.wfState
|
|
205
|
+
? `${params.event}:direct`
|
|
206
|
+
: attribution.stepId,
|
|
207
|
+
fact,
|
|
208
|
+
summary: `Claude hook observed ${fact} for ${subject}: ${result}`,
|
|
209
|
+
command,
|
|
210
|
+
result,
|
|
211
|
+
exit_code: typeof params.rawInput.exit_code === "number" ? params.rawInput.exit_code : undefined,
|
|
212
|
+
duration_ms: typeof params.rawInput.duration_ms === "number" ? params.rawInput.duration_ms : undefined,
|
|
213
|
+
manual_intervention: fact === "manual_intervention"
|
|
214
|
+
? { required: true, reason: params.reason, actor: "human" }
|
|
215
|
+
: undefined,
|
|
216
|
+
evidence_refs: [
|
|
217
|
+
{ type: "runtime_decision_event", ref: params.runtimeEvent.event_id },
|
|
218
|
+
...params.runtimeEvent.evidence_refs.map((ref) => ({ type: ref.type, ref: ref.ref })),
|
|
219
|
+
],
|
|
220
|
+
redaction: "intentdna.c5.claude_hook_secret_redaction.v1",
|
|
221
|
+
};
|
|
222
|
+
}
|
|
153
223
|
export function computeSummary(traces) {
|
|
154
224
|
let blocks = 0, warns = 0;
|
|
155
225
|
const toolCounts = new Map();
|
|
@@ -193,7 +263,12 @@ export function formatSummary(s) {
|
|
|
193
263
|
async function main() {
|
|
194
264
|
// Parse args
|
|
195
265
|
const args = process.argv.slice(2);
|
|
196
|
-
const
|
|
266
|
+
const eventArg = args[0];
|
|
267
|
+
if (eventArg === "CodexHook") {
|
|
268
|
+
await runCodexHookCli(args);
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
const event = eventArg;
|
|
197
272
|
if (!event || !VALID_EVENTS.has(event)) {
|
|
198
273
|
writeOutput(silentOutput());
|
|
199
274
|
return;
|
|
@@ -245,7 +320,7 @@ export async function runHookEvent(options) {
|
|
|
245
320
|
const writeRuntimeDecision = async (output, result, wfState, traceId = randomUUID()) => {
|
|
246
321
|
const decision = normalizeDecision(output);
|
|
247
322
|
const reason = decision !== "allow" ? (output.reason ?? output.hookSpecificOutput?.additionalContext ?? "unknown") : "allowed by active policy";
|
|
248
|
-
|
|
323
|
+
const runtimeEvent = buildRuntimeDecisionEvent({
|
|
249
324
|
ir,
|
|
250
325
|
rawInput,
|
|
251
326
|
output,
|
|
@@ -257,7 +332,18 @@ export async function runHookEvent(options) {
|
|
|
257
332
|
decision,
|
|
258
333
|
reason,
|
|
259
334
|
wfState,
|
|
260
|
-
})
|
|
335
|
+
});
|
|
336
|
+
await appendRuntimeDecisionEvent(projectDir, runtimeEvent, sessionId);
|
|
337
|
+
await appendEvidenceCaptureEvent(projectDir, buildClaudeEvidenceCaptureEvent({
|
|
338
|
+
ir,
|
|
339
|
+
rawInput,
|
|
340
|
+
event,
|
|
341
|
+
projectDir,
|
|
342
|
+
decision,
|
|
343
|
+
reason,
|
|
344
|
+
runtimeEvent,
|
|
345
|
+
wfState,
|
|
346
|
+
}));
|
|
261
347
|
};
|
|
262
348
|
let wfStateRaw = null;
|
|
263
349
|
// Load workflow state for events that need it (handoff context + PreCompact preservation)
|
|
@@ -272,6 +358,7 @@ export async function runHookEvent(options) {
|
|
|
272
358
|
state.workflowState = {
|
|
273
359
|
current_step: wfStateRaw.current_step,
|
|
274
360
|
workflow: wfStateRaw.workflow,
|
|
361
|
+
workflow_asset: wfStateRaw.workflow_asset,
|
|
275
362
|
current_role: wfStateRaw.current_role,
|
|
276
363
|
completed_artifacts: wfStateRaw.completed_artifacts,
|
|
277
364
|
iteration: wfStateRaw.iteration, // G4: pass iteration for state-driven rules
|
|
@@ -337,6 +424,8 @@ export async function runHookEvent(options) {
|
|
|
337
424
|
current_step: wfState.current_step,
|
|
338
425
|
current_role: wfState.current_role,
|
|
339
426
|
started_at: wfState.started_at,
|
|
427
|
+
inputs: wfState.inputs,
|
|
428
|
+
resolved_variables: wfState.resolved_variables,
|
|
340
429
|
completed_artifacts: wfState.completed_artifacts,
|
|
341
430
|
artifact_facts: stopArtifactFacts,
|
|
342
431
|
} : null;
|
|
@@ -421,6 +510,8 @@ export async function runHookEvent(options) {
|
|
|
421
510
|
workflow: wfStateRaw.workflow,
|
|
422
511
|
current_step: wfStateRaw.current_step,
|
|
423
512
|
current_role: wfStateRaw.current_role,
|
|
513
|
+
inputs: wfStateRaw.inputs,
|
|
514
|
+
resolved_variables: wfStateRaw.resolved_variables,
|
|
424
515
|
}, "post_tool_use", sessionId);
|
|
425
516
|
const failingResults = verifierResults.filter((result) => result.status === "fail");
|
|
426
517
|
if (failingResults.length > 0) {
|
|
@@ -554,6 +645,62 @@ function dispatch(event, ir, input, state, roles) {
|
|
|
554
645
|
return null;
|
|
555
646
|
}
|
|
556
647
|
}
|
|
648
|
+
// ── Codex Hook Entry ───────────────────────────────────────
|
|
649
|
+
function argValue(args, flag) {
|
|
650
|
+
const index = args.indexOf(flag);
|
|
651
|
+
return index >= 0 ? args[index + 1] : undefined;
|
|
652
|
+
}
|
|
653
|
+
function codexInstallMode(value) {
|
|
654
|
+
return value === "plugin_scoped" || value === "managed" || value === "none" ? value : "project_fallback";
|
|
655
|
+
}
|
|
656
|
+
export function codexHookEventForInput(rawInput, args) {
|
|
657
|
+
return argValue(args, "--codex-event") ?? rawInput.event;
|
|
658
|
+
}
|
|
659
|
+
function inferWorkflowAssetFromIr(ir, workflowId) {
|
|
660
|
+
const attribution = trustedEvidenceCaptureAttribution(ir, { workflowId });
|
|
661
|
+
return attribution.workflowAsset === "unknown" ? undefined : attribution.workflowAsset;
|
|
662
|
+
}
|
|
663
|
+
async function runCodexHookWithLoadedIr(rawInput, projectDir, args, ir) {
|
|
664
|
+
const sessionId = typeof rawInput.session_id === "string"
|
|
665
|
+
? rawInput.session_id
|
|
666
|
+
: typeof rawInput.sessionId === "string"
|
|
667
|
+
? rawInput.sessionId
|
|
668
|
+
: undefined;
|
|
669
|
+
const sessionWorkflowState = sessionId
|
|
670
|
+
? await readWorkflowState(projectDir, sessionId)
|
|
671
|
+
: null;
|
|
672
|
+
const workflowState = sessionWorkflowState ?? await readWorkflowState(projectDir);
|
|
673
|
+
const workflowAsset = inferWorkflowAssetFromIr(ir, workflowState?.workflow);
|
|
674
|
+
return runCodexHook({ ...rawInput, event: codexHookEventForInput(rawInput, args) }, {
|
|
675
|
+
ir,
|
|
676
|
+
projectDir,
|
|
677
|
+
hookInstallMode: codexInstallMode(argValue(args, "--codex-install-mode")),
|
|
678
|
+
hookTrusted: true,
|
|
679
|
+
codexVersion: process.env.CODEX_VERSION,
|
|
680
|
+
workflowAsset,
|
|
681
|
+
workflowId: workflowState?.workflow,
|
|
682
|
+
stepId: workflowState?.current_step,
|
|
683
|
+
appendEvent: true,
|
|
684
|
+
});
|
|
685
|
+
}
|
|
686
|
+
async function runCodexHookCli(args) {
|
|
687
|
+
try {
|
|
688
|
+
const rawInput = await readStdin(5000);
|
|
689
|
+
const projectDir = typeof rawInput.cwd === "string" ? rawInput.cwd : process.cwd();
|
|
690
|
+
const irPath = argValue(args, "--ir") ?? DEFAULT_IR_PATH;
|
|
691
|
+
const ir = await loadIR(resolve(projectDir, irPath));
|
|
692
|
+
if (!ir) {
|
|
693
|
+
writeOutput(silentOutput());
|
|
694
|
+
return;
|
|
695
|
+
}
|
|
696
|
+
const result = await runCodexHookWithLoadedIr(rawInput, projectDir, args, ir);
|
|
697
|
+
writeOutput(result.output);
|
|
698
|
+
}
|
|
699
|
+
catch (err) {
|
|
700
|
+
process.stderr.write(`\n Intent DNA: CodexHook failed open — ${err instanceof Error ? err.message : String(err)}\n`);
|
|
701
|
+
writeOutput(silentOutput());
|
|
702
|
+
}
|
|
703
|
+
}
|
|
557
704
|
// ── IR Loading ─────────────────────────────────────────────
|
|
558
705
|
/** Expected IR schema version — must match plugin-adapter.ts */
|
|
559
706
|
const EXPECTED_IR_VERSION = 1;
|
|
@@ -777,6 +924,8 @@ function appendArtifactResolverTrace(projectDir, event, wfState, output, session
|
|
|
777
924
|
export const recordProducedArtifactsForTest = recordProducedArtifacts;
|
|
778
925
|
export const resolveWorkflowArtifactFactsForTest = resolveWorkflowArtifactFacts;
|
|
779
926
|
export const readWorkflowStateForHookTest = readWorkflowStateForHook;
|
|
927
|
+
export const runCodexHookWithLoadedIrForTest = runCodexHookWithLoadedIr;
|
|
928
|
+
export const inferWorkflowAssetFromIrForTest = inferWorkflowAssetFromIr;
|
|
780
929
|
export const resolveWorkflowArtifactFactsForHookTest = resolveWorkflowArtifactFactsForHook;
|
|
781
930
|
export const finalizeAndResolveArtifactsForHookTest = finalizeAndResolveArtifactsForHook;
|
|
782
931
|
function clearBlockingVerifierCheckpoints(ir, workflowState) {
|
|
@@ -807,8 +956,6 @@ function clearBlockingVerifierCheckpoints(ir, workflowState) {
|
|
|
807
956
|
}),
|
|
808
957
|
};
|
|
809
958
|
}
|
|
810
|
-
const DEFAULT_VERIFIER_COMMAND_TIMEOUT_MS = 30_000;
|
|
811
|
-
const MAX_VERIFIER_EVIDENCE_BYTES = 4096;
|
|
812
959
|
function appendOutputText(output, text, hookEventName) {
|
|
813
960
|
if (!text)
|
|
814
961
|
return output;
|
|
@@ -855,316 +1002,6 @@ export function appendStopVerifierWarnings(output, verifierResults, currentStep)
|
|
|
855
1002
|
"\nFix the warning above before relying on this step, or rerun the verifier after repair.";
|
|
856
1003
|
return appendOutputText(output, warningText, "Stop");
|
|
857
1004
|
}
|
|
858
|
-
function trimEvidence(raw) {
|
|
859
|
-
if (!raw)
|
|
860
|
-
return undefined;
|
|
861
|
-
const trimmed = raw.trim();
|
|
862
|
-
if (!trimmed)
|
|
863
|
-
return undefined;
|
|
864
|
-
return trimmed.length > MAX_VERIFIER_EVIDENCE_BYTES
|
|
865
|
-
? trimmed.slice(0, MAX_VERIFIER_EVIDENCE_BYTES)
|
|
866
|
-
: trimmed;
|
|
867
|
-
}
|
|
868
|
-
function hasUnsafeShellControl(command) {
|
|
869
|
-
let inSingle = false;
|
|
870
|
-
let inDouble = false;
|
|
871
|
-
let escaped = false;
|
|
872
|
-
for (let i = 0; i < command.length; i++) {
|
|
873
|
-
const ch = command[i];
|
|
874
|
-
const next = command[i + 1] ?? "";
|
|
875
|
-
if (escaped) {
|
|
876
|
-
escaped = false;
|
|
877
|
-
continue;
|
|
878
|
-
}
|
|
879
|
-
if (ch === "\\" && !inSingle) {
|
|
880
|
-
escaped = true;
|
|
881
|
-
continue;
|
|
882
|
-
}
|
|
883
|
-
if (ch === "'" && !inDouble) {
|
|
884
|
-
inSingle = !inSingle;
|
|
885
|
-
continue;
|
|
886
|
-
}
|
|
887
|
-
if (ch === '"' && !inSingle) {
|
|
888
|
-
inDouble = !inDouble;
|
|
889
|
-
continue;
|
|
890
|
-
}
|
|
891
|
-
if (!inSingle && !inDouble) {
|
|
892
|
-
if (ch === ";" || ch === "`" || ch === "\n" || ch === "\r")
|
|
893
|
-
return true;
|
|
894
|
-
if ((ch === "&" || ch === "|" || ch === "<" || ch === ">") && next === ch)
|
|
895
|
-
return true;
|
|
896
|
-
if (ch === "$" && next === "(")
|
|
897
|
-
return true;
|
|
898
|
-
if ((ch === "<" || ch === ">") && next === "(")
|
|
899
|
-
return true;
|
|
900
|
-
if (ch === "|" || ch === "&" || ch === "<" || ch === ">")
|
|
901
|
-
return true;
|
|
902
|
-
}
|
|
903
|
-
if (inDouble) {
|
|
904
|
-
if (ch === "`")
|
|
905
|
-
return true;
|
|
906
|
-
if (ch === "$" && next === "(")
|
|
907
|
-
return true;
|
|
908
|
-
}
|
|
909
|
-
}
|
|
910
|
-
return false;
|
|
911
|
-
}
|
|
912
|
-
function isVerifierCommandAllowed(ir, command) {
|
|
913
|
-
const normalized = command.trim();
|
|
914
|
-
const policy = ir.verifier_policy;
|
|
915
|
-
if (!policy)
|
|
916
|
-
return false;
|
|
917
|
-
if (policy.allow_commands?.includes(normalized))
|
|
918
|
-
return true;
|
|
919
|
-
if (hasUnsafeShellControl(normalized))
|
|
920
|
-
return false;
|
|
921
|
-
return policy.allow_command_prefixes?.some((prefix) => normalized.startsWith(prefix)) ?? false;
|
|
922
|
-
}
|
|
923
|
-
function verifierCommandPolicyMessage(command) {
|
|
924
|
-
return `Verifier command not allowed by verifier_policy: ${command}`;
|
|
925
|
-
}
|
|
926
|
-
function isBuiltinAssertAllowed(ir, assertName) {
|
|
927
|
-
const policy = ir.verifier_policy;
|
|
928
|
-
return policy?.allow_builtin_asserts?.includes(assertName) ?? false;
|
|
929
|
-
}
|
|
930
|
-
function verifierAssertPolicyMessage(assertName) {
|
|
931
|
-
return `Verifier assert not allowed by verifier_policy: ${assertName}`;
|
|
932
|
-
}
|
|
933
|
-
function summarizeCommandEvidence(stdout, stderr) {
|
|
934
|
-
const stdoutBytes = Buffer.byteLength(stdout, "utf8");
|
|
935
|
-
const stderrBytes = Buffer.byteLength(stderr, "utf8");
|
|
936
|
-
if (stdoutBytes === 0 && stderrBytes === 0)
|
|
937
|
-
return undefined;
|
|
938
|
-
return trimEvidence(`stdout_bytes=${stdoutBytes} stderr_bytes=${stderrBytes}`);
|
|
939
|
-
}
|
|
940
|
-
async function execVerifierCommand(projectDir, command, timeoutMs = DEFAULT_VERIFIER_COMMAND_TIMEOUT_MS) {
|
|
941
|
-
return new Promise((resolvePromise) => {
|
|
942
|
-
const child = spawn("bash", ["-lc", command], { cwd: projectDir, stdio: ["ignore", "pipe", "pipe"] });
|
|
943
|
-
let stdout = "";
|
|
944
|
-
let stderr = "";
|
|
945
|
-
let settled = false;
|
|
946
|
-
const settle = (result) => {
|
|
947
|
-
if (settled)
|
|
948
|
-
return;
|
|
949
|
-
settled = true;
|
|
950
|
-
clearTimeout(timeoutId);
|
|
951
|
-
resolvePromise(result);
|
|
952
|
-
};
|
|
953
|
-
const timeoutId = setTimeout(() => {
|
|
954
|
-
child.kill("SIGTERM");
|
|
955
|
-
setTimeout(() => child.kill("SIGKILL"), 1000).unref();
|
|
956
|
-
settle({ passed: false, timedOut: true, exitCode: 124, evidence: summarizeCommandEvidence(stdout, stderr) });
|
|
957
|
-
}, timeoutMs);
|
|
958
|
-
child.stdout.on("data", (chunk) => {
|
|
959
|
-
stdout += String(chunk);
|
|
960
|
-
if (stdout.length > MAX_VERIFIER_EVIDENCE_BYTES) {
|
|
961
|
-
stdout = stdout.slice(0, MAX_VERIFIER_EVIDENCE_BYTES);
|
|
962
|
-
}
|
|
963
|
-
});
|
|
964
|
-
child.stderr.on("data", (chunk) => {
|
|
965
|
-
stderr += String(chunk);
|
|
966
|
-
if (stderr.length > MAX_VERIFIER_EVIDENCE_BYTES) {
|
|
967
|
-
stderr = stderr.slice(0, MAX_VERIFIER_EVIDENCE_BYTES);
|
|
968
|
-
}
|
|
969
|
-
});
|
|
970
|
-
child.on("error", (_error) => settle({
|
|
971
|
-
passed: false,
|
|
972
|
-
timedOut: false,
|
|
973
|
-
exitCode: 1,
|
|
974
|
-
evidence: summarizeCommandEvidence(stdout, stderr),
|
|
975
|
-
}));
|
|
976
|
-
child.on("close", (code) => settle({
|
|
977
|
-
passed: code === 0,
|
|
978
|
-
timedOut: false,
|
|
979
|
-
exitCode: code ?? 1,
|
|
980
|
-
evidence: summarizeCommandEvidence(stdout, stderr),
|
|
981
|
-
}));
|
|
982
|
-
});
|
|
983
|
-
}
|
|
984
|
-
async function runCheckpointVerifier(projectDir, ir, checkpoint, commandTimeoutMs = DEFAULT_VERIFIER_COMMAND_TIMEOUT_MS) {
|
|
985
|
-
const assertCommands = {
|
|
986
|
-
clean_working_tree: "git status --porcelain",
|
|
987
|
-
lint_passing: "npm run lint --silent 2>/dev/null",
|
|
988
|
-
build_passing: "npm run build --silent 2>/dev/null",
|
|
989
|
-
};
|
|
990
|
-
if (checkpoint.command) {
|
|
991
|
-
if (!isVerifierCommandAllowed(ir, checkpoint.command)) {
|
|
992
|
-
return {
|
|
993
|
-
passed: false,
|
|
994
|
-
target: checkpoint.command,
|
|
995
|
-
evidence: "policy_denied",
|
|
996
|
-
exit_code: 126,
|
|
997
|
-
message: verifierCommandPolicyMessage(checkpoint.command),
|
|
998
|
-
};
|
|
999
|
-
}
|
|
1000
|
-
const commandResult = await execVerifierCommand(projectDir, checkpoint.command, commandTimeoutMs);
|
|
1001
|
-
return {
|
|
1002
|
-
passed: commandResult.passed,
|
|
1003
|
-
target: checkpoint.command,
|
|
1004
|
-
evidence: commandResult.evidence,
|
|
1005
|
-
exit_code: commandResult.exitCode,
|
|
1006
|
-
message: commandResult.passed
|
|
1007
|
-
? checkpoint.message
|
|
1008
|
-
: commandResult.timedOut
|
|
1009
|
-
? `Verifier command timed out after ${commandTimeoutMs}ms: ${checkpoint.command}`
|
|
1010
|
-
: `Verifier command failed: ${checkpoint.command}`,
|
|
1011
|
-
};
|
|
1012
|
-
}
|
|
1013
|
-
if (checkpoint.assert === "clean_working_tree") {
|
|
1014
|
-
if (!isBuiltinAssertAllowed(ir, checkpoint.assert)) {
|
|
1015
|
-
return {
|
|
1016
|
-
passed: false,
|
|
1017
|
-
target: checkpoint.assert,
|
|
1018
|
-
evidence: "policy_denied",
|
|
1019
|
-
exit_code: 126,
|
|
1020
|
-
message: verifierAssertPolicyMessage(checkpoint.assert),
|
|
1021
|
-
};
|
|
1022
|
-
}
|
|
1023
|
-
try {
|
|
1024
|
-
const raw = await new Promise((resolvePromise, rejectPromise) => {
|
|
1025
|
-
const child = spawn("git", ["status", "--porcelain"], { cwd: projectDir, stdio: ["ignore", "pipe", "ignore"] });
|
|
1026
|
-
let stdout = "";
|
|
1027
|
-
child.stdout.on("data", (chunk) => { stdout += String(chunk); });
|
|
1028
|
-
child.on("error", rejectPromise);
|
|
1029
|
-
child.on("close", (code) => code === 0 ? resolvePromise(stdout) : rejectPromise(new Error("git status failed")));
|
|
1030
|
-
});
|
|
1031
|
-
const changedEntries = raw.split("\n").filter(Boolean).length;
|
|
1032
|
-
return {
|
|
1033
|
-
passed: raw.trim() === "",
|
|
1034
|
-
target: checkpoint.assert,
|
|
1035
|
-
evidence: `changed_entries=${changedEntries}`,
|
|
1036
|
-
exit_code: 0,
|
|
1037
|
-
message: checkpoint.message,
|
|
1038
|
-
};
|
|
1039
|
-
}
|
|
1040
|
-
catch {
|
|
1041
|
-
return { passed: false, target: checkpoint.assert, exit_code: 1, message: checkpoint.message };
|
|
1042
|
-
}
|
|
1043
|
-
}
|
|
1044
|
-
if (checkpoint.assert === "no_test_regression") {
|
|
1045
|
-
if (!isBuiltinAssertAllowed(ir, checkpoint.assert)) {
|
|
1046
|
-
return {
|
|
1047
|
-
passed: false,
|
|
1048
|
-
target: checkpoint.assert,
|
|
1049
|
-
evidence: "policy_denied",
|
|
1050
|
-
exit_code: 126,
|
|
1051
|
-
message: verifierAssertPolicyMessage(checkpoint.assert),
|
|
1052
|
-
};
|
|
1053
|
-
}
|
|
1054
|
-
try {
|
|
1055
|
-
const baseline = parseInt(await readFile(resolve(projectDir, ".dna/test-baseline"), "utf-8").catch(() => "0"), 10);
|
|
1056
|
-
const current = parseInt(await readFile(resolve(projectDir, ".dna/test-current"), "utf-8").catch(() => "0"), 10);
|
|
1057
|
-
return {
|
|
1058
|
-
passed: current <= baseline,
|
|
1059
|
-
target: checkpoint.assert,
|
|
1060
|
-
evidence: `baseline=${baseline} current=${current}`,
|
|
1061
|
-
exit_code: 0,
|
|
1062
|
-
message: checkpoint.message,
|
|
1063
|
-
};
|
|
1064
|
-
}
|
|
1065
|
-
catch {
|
|
1066
|
-
return { passed: false, target: checkpoint.assert, exit_code: 1, message: checkpoint.message };
|
|
1067
|
-
}
|
|
1068
|
-
}
|
|
1069
|
-
if (assertCommands[checkpoint.assert]) {
|
|
1070
|
-
if (!isBuiltinAssertAllowed(ir, checkpoint.assert)) {
|
|
1071
|
-
return {
|
|
1072
|
-
passed: false,
|
|
1073
|
-
target: checkpoint.assert,
|
|
1074
|
-
evidence: "policy_denied",
|
|
1075
|
-
exit_code: 126,
|
|
1076
|
-
message: verifierAssertPolicyMessage(checkpoint.assert),
|
|
1077
|
-
};
|
|
1078
|
-
}
|
|
1079
|
-
const commandResult = await execVerifierCommand(projectDir, assertCommands[checkpoint.assert], commandTimeoutMs);
|
|
1080
|
-
return {
|
|
1081
|
-
passed: commandResult.passed,
|
|
1082
|
-
target: checkpoint.assert,
|
|
1083
|
-
evidence: commandResult.evidence,
|
|
1084
|
-
exit_code: commandResult.exitCode,
|
|
1085
|
-
message: commandResult.passed
|
|
1086
|
-
? checkpoint.message
|
|
1087
|
-
: commandResult.timedOut
|
|
1088
|
-
? `Verifier command timed out after ${commandTimeoutMs}ms: ${assertCommands[checkpoint.assert]}`
|
|
1089
|
-
: checkpoint.message,
|
|
1090
|
-
};
|
|
1091
|
-
}
|
|
1092
|
-
return { passed: false, target: checkpoint.assert, exit_code: 1, message: checkpoint.message };
|
|
1093
|
-
}
|
|
1094
|
-
function isWithinProjectRoot(projectRoot, targetPath) {
|
|
1095
|
-
const relPath = relative(projectRoot, targetPath);
|
|
1096
|
-
return relPath === "" || (!relPath.startsWith("..") && !isAbsolute(relPath));
|
|
1097
|
-
}
|
|
1098
|
-
async function resolveVerifierTarget(projectDir, targetPath) {
|
|
1099
|
-
const projectRoot = await realpath(projectDir).catch(() => resolve(projectDir));
|
|
1100
|
-
const resolvedPath = resolve(projectDir, targetPath);
|
|
1101
|
-
if (!isWithinProjectRoot(projectRoot, resolvedPath)) {
|
|
1102
|
-
return null;
|
|
1103
|
-
}
|
|
1104
|
-
const canonicalParent = await realpath(dirname(resolvedPath)).catch(() => null);
|
|
1105
|
-
if (canonicalParent && !isWithinProjectRoot(projectRoot, canonicalParent)) {
|
|
1106
|
-
return null;
|
|
1107
|
-
}
|
|
1108
|
-
const canonicalTarget = await realpath(resolvedPath).catch(() => null);
|
|
1109
|
-
if (canonicalTarget && !isWithinProjectRoot(projectRoot, canonicalTarget)) {
|
|
1110
|
-
return null;
|
|
1111
|
-
}
|
|
1112
|
-
return canonicalTarget ?? resolvedPath;
|
|
1113
|
-
}
|
|
1114
|
-
async function toProjectRelativePath(projectDir, filePath) {
|
|
1115
|
-
const projectRoot = await realpath(projectDir).catch(() => resolve(projectDir));
|
|
1116
|
-
const resolvedPath = resolve(projectDir, filePath);
|
|
1117
|
-
const canonicalPath = await realpath(resolvedPath).catch(() => resolvedPath);
|
|
1118
|
-
const targetPath = isWithinProjectRoot(projectRoot, canonicalPath) ? canonicalPath : resolvedPath;
|
|
1119
|
-
const relPath = relative(projectRoot, targetPath);
|
|
1120
|
-
return relPath === "" ? "." : relPath;
|
|
1121
|
-
}
|
|
1122
|
-
function getCompletionCheckFields(completion) {
|
|
1123
|
-
if (typeof completion !== "object" || completion === null || Array.isArray(completion))
|
|
1124
|
-
return [];
|
|
1125
|
-
const fields = [];
|
|
1126
|
-
if (completion.file_exists !== undefined)
|
|
1127
|
-
fields.push("file_exists");
|
|
1128
|
-
if (completion.file_not_empty !== undefined)
|
|
1129
|
-
fields.push("file_not_empty");
|
|
1130
|
-
if (completion.file_contains !== undefined)
|
|
1131
|
-
fields.push("file_contains");
|
|
1132
|
-
if (completion.command_success !== undefined)
|
|
1133
|
-
fields.push("command_success");
|
|
1134
|
-
return fields;
|
|
1135
|
-
}
|
|
1136
|
-
function validateCompletionVerifier(completion) {
|
|
1137
|
-
const fields = getCompletionCheckFields(completion);
|
|
1138
|
-
if (fields.length !== 1) {
|
|
1139
|
-
return {
|
|
1140
|
-
message: "Completion verifier must define exactly one check",
|
|
1141
|
-
evidence: `fields:${fields.join(",") || "none"}`,
|
|
1142
|
-
};
|
|
1143
|
-
}
|
|
1144
|
-
const field = fields[0];
|
|
1145
|
-
if (field === "file_exists" && (typeof completion.file_exists !== "string" || !completion.file_exists)) {
|
|
1146
|
-
return { message: "Completion verifier file_exists must be a non-empty string", evidence: "invalid:file_exists" };
|
|
1147
|
-
}
|
|
1148
|
-
if (field === "file_not_empty" && (typeof completion.file_not_empty !== "string" || !completion.file_not_empty)) {
|
|
1149
|
-
return { message: "Completion verifier file_not_empty must be a non-empty string", evidence: "invalid:file_not_empty" };
|
|
1150
|
-
}
|
|
1151
|
-
if (field === "command_success" && (typeof completion.command_success !== "string" || !completion.command_success)) {
|
|
1152
|
-
return { message: "Completion verifier command_success must be a non-empty string", evidence: "invalid:command_success" };
|
|
1153
|
-
}
|
|
1154
|
-
if (field === "file_contains") {
|
|
1155
|
-
const fileContains = completion.file_contains;
|
|
1156
|
-
if (typeof fileContains !== "object" || fileContains === null) {
|
|
1157
|
-
return { message: "Completion verifier file_contains must define path and pattern", evidence: "invalid:file_contains" };
|
|
1158
|
-
}
|
|
1159
|
-
if (typeof fileContains.path !== "string" || !fileContains.path) {
|
|
1160
|
-
return { message: "Completion verifier file_contains.path must be a non-empty string", evidence: "invalid:file_contains.path" };
|
|
1161
|
-
}
|
|
1162
|
-
if (typeof fileContains.pattern !== "string" || !fileContains.pattern) {
|
|
1163
|
-
return { message: "Completion verifier file_contains.pattern must be a non-empty string", evidence: "invalid:file_contains.pattern" };
|
|
1164
|
-
}
|
|
1165
|
-
}
|
|
1166
|
-
return null;
|
|
1167
|
-
}
|
|
1168
1005
|
function getAuditCheckType(spec) {
|
|
1169
1006
|
if (spec.completion?.file_exists !== undefined)
|
|
1170
1007
|
return "file_exists";
|
|
@@ -1209,7 +1046,11 @@ async function runVerifierSpec(projectDir, ir, spec, workflowState, sessionId, o
|
|
|
1209
1046
|
let artifact;
|
|
1210
1047
|
let message = spec.checkpoint?.message;
|
|
1211
1048
|
if (spec.kind === "checkpoint" && spec.checkpoint) {
|
|
1212
|
-
const checkpointResult = await
|
|
1049
|
+
const checkpointResult = await runSharedCheckpointVerifier(spec.checkpoint, ir, {
|
|
1050
|
+
projectDir,
|
|
1051
|
+
variables: { ...(workflowState.resolved_variables ?? {}), ...(workflowState.inputs ?? {}) },
|
|
1052
|
+
commandTimeoutMs: options?.commandTimeoutMs,
|
|
1053
|
+
});
|
|
1213
1054
|
passed = checkpointResult.passed;
|
|
1214
1055
|
target = checkpointResult.target;
|
|
1215
1056
|
evidence = checkpointResult.evidence;
|
|
@@ -1225,99 +1066,17 @@ async function runVerifierSpec(projectDir, ir, spec, workflowState, sessionId, o
|
|
|
1225
1066
|
message = "Completion verifier must define exactly one check";
|
|
1226
1067
|
}
|
|
1227
1068
|
else {
|
|
1228
|
-
const
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
if (!resolvedTarget) {
|
|
1240
|
-
passed = false;
|
|
1241
|
-
message = `Verifier target escapes project root: ${completion.file_exists}`;
|
|
1242
|
-
}
|
|
1243
|
-
else {
|
|
1244
|
-
try {
|
|
1245
|
-
await stat(resolvedTarget);
|
|
1246
|
-
}
|
|
1247
|
-
catch {
|
|
1248
|
-
passed = false;
|
|
1249
|
-
message = `Required file missing: ${completion.file_exists}`;
|
|
1250
|
-
}
|
|
1251
|
-
}
|
|
1252
|
-
}
|
|
1253
|
-
else if (completion.file_not_empty) {
|
|
1254
|
-
target = completion.file_not_empty;
|
|
1255
|
-
const resolvedTarget = await resolveVerifierTarget(projectDir, completion.file_not_empty);
|
|
1256
|
-
artifact = resolvedTarget ?? undefined;
|
|
1257
|
-
evidence = resolvedTarget ? `not_empty:${resolvedTarget}` : "path_escape";
|
|
1258
|
-
if (!resolvedTarget) {
|
|
1259
|
-
passed = false;
|
|
1260
|
-
message = `Verifier target escapes project root: ${completion.file_not_empty}`;
|
|
1261
|
-
}
|
|
1262
|
-
else {
|
|
1263
|
-
try {
|
|
1264
|
-
const raw = await readFile(resolvedTarget, "utf-8");
|
|
1265
|
-
evidence = `bytes=${Buffer.byteLength(raw, "utf8")}`;
|
|
1266
|
-
if (raw.trim().length === 0) {
|
|
1267
|
-
passed = false;
|
|
1268
|
-
message = `Required file is empty: ${completion.file_not_empty}`;
|
|
1269
|
-
}
|
|
1270
|
-
}
|
|
1271
|
-
catch {
|
|
1272
|
-
passed = false;
|
|
1273
|
-
message = `Required file missing: ${completion.file_not_empty}`;
|
|
1274
|
-
}
|
|
1275
|
-
}
|
|
1276
|
-
}
|
|
1277
|
-
else if (completion.file_contains) {
|
|
1278
|
-
target = completion.file_contains.path;
|
|
1279
|
-
const resolvedTarget = await resolveVerifierTarget(projectDir, completion.file_contains.path);
|
|
1280
|
-
artifact = resolvedTarget ?? undefined;
|
|
1281
|
-
evidence = `pattern:${completion.file_contains.pattern}`;
|
|
1282
|
-
if (!resolvedTarget) {
|
|
1283
|
-
passed = false;
|
|
1284
|
-
message = `Verifier target escapes project root: ${completion.file_contains.path}`;
|
|
1285
|
-
}
|
|
1286
|
-
else {
|
|
1287
|
-
try {
|
|
1288
|
-
const raw = await readFile(resolvedTarget, "utf-8");
|
|
1289
|
-
const re = new RegExp(completion.file_contains.pattern);
|
|
1290
|
-
if (!re.test(raw)) {
|
|
1291
|
-
passed = false;
|
|
1292
|
-
message = `Required pattern missing in ${completion.file_contains.path}`;
|
|
1293
|
-
}
|
|
1294
|
-
}
|
|
1295
|
-
catch {
|
|
1296
|
-
passed = false;
|
|
1297
|
-
message = `Required file missing: ${completion.file_contains.path}`;
|
|
1298
|
-
}
|
|
1299
|
-
}
|
|
1300
|
-
}
|
|
1301
|
-
else if (completion.command_success) {
|
|
1302
|
-
target = completion.command_success;
|
|
1303
|
-
if (!isVerifierCommandAllowed(ir, completion.command_success)) {
|
|
1304
|
-
passed = false;
|
|
1305
|
-
evidence = "policy_denied";
|
|
1306
|
-
exit_code = 126;
|
|
1307
|
-
message = verifierCommandPolicyMessage(completion.command_success);
|
|
1308
|
-
}
|
|
1309
|
-
else {
|
|
1310
|
-
const commandResult = await execVerifierCommand(projectDir, completion.command_success, options?.commandTimeoutMs);
|
|
1311
|
-
passed = commandResult.passed;
|
|
1312
|
-
evidence = commandResult.evidence;
|
|
1313
|
-
exit_code = commandResult.exitCode;
|
|
1314
|
-
if (!passed) {
|
|
1315
|
-
message = commandResult.timedOut
|
|
1316
|
-
? `Verifier command timed out after ${options?.commandTimeoutMs ?? DEFAULT_VERIFIER_COMMAND_TIMEOUT_MS}ms: ${completion.command_success}`
|
|
1317
|
-
: `Verifier command failed: ${completion.command_success}`;
|
|
1318
|
-
}
|
|
1319
|
-
}
|
|
1320
|
-
}
|
|
1069
|
+
const completionResult = await runCompletionVerifier(completion, ir, {
|
|
1070
|
+
projectDir,
|
|
1071
|
+
variables: { ...(workflowState.resolved_variables ?? {}), ...(workflowState.inputs ?? {}) },
|
|
1072
|
+
commandTimeoutMs: options?.commandTimeoutMs,
|
|
1073
|
+
});
|
|
1074
|
+
passed = completionResult.passed;
|
|
1075
|
+
target = completionResult.target;
|
|
1076
|
+
evidence = completionResult.evidence;
|
|
1077
|
+
exit_code = completionResult.exit_code;
|
|
1078
|
+
artifact = completionResult.artifact;
|
|
1079
|
+
message = completionResult.message;
|
|
1321
1080
|
}
|
|
1322
1081
|
}
|
|
1323
1082
|
const entry = {
|