intentdna 1.8.3 → 1.8.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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +2 -2
- package/dist/cli/commands/assets.d.ts +1 -0
- package/dist/cli/commands/assets.js +51 -17
- package/dist/cli/commands/sync.js +10 -8
- package/dist/cli/index.js +0 -0
- package/dist/hooks/cli.d.ts +3 -0
- package/dist/hooks/cli.js +108 -15
- package/dist/hooks/event-registry.js +1 -1
- package/dist/hooks/state.d.ts +2 -0
- package/dist/hooks/state.js +4 -0
- package/dist/mcp/index.js +2 -0
- package/dist/mcp/tools-artifacts.d.ts +2 -0
- package/dist/mcp/tools-artifacts.js +137 -0
- package/dist/runtime/claude-sync-target.d.ts +3 -1
- package/dist/runtime/claude-sync-target.js +16 -4
- package/dist/runtime/settings-adapter.d.ts +12 -0
- package/dist/runtime/settings-adapter.js +121 -3
- package/dist/runtime/skill-adapter.js +34 -4
- package/dist/runtime/verifier.d.ts +3 -1
- package/dist/runtime/verifier.js +82 -27
- package/dist/templates/flutter-rewrite.dna.yaml +19 -9
- package/hooks/hooks.json +1 -1
- package/package.json +1 -1
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
{
|
|
10
10
|
"name": "intentdna",
|
|
11
11
|
"description": "Declarative policy layer for AI agent behavior with plugin-managed hook runtime for Claude Code.",
|
|
12
|
-
"version": "1.8.
|
|
12
|
+
"version": "1.8.5",
|
|
13
13
|
"author": {
|
|
14
14
|
"name": "Samuel"
|
|
15
15
|
},
|
|
@@ -25,5 +25,5 @@
|
|
|
25
25
|
]
|
|
26
26
|
}
|
|
27
27
|
],
|
|
28
|
-
"version": "1.8.
|
|
28
|
+
"version": "1.8.5"
|
|
29
29
|
}
|
package/README.md
CHANGED
|
@@ -17,11 +17,11 @@ IntentDNA is not a model company, generic agent framework, prompt library, canva
|
|
|
17
17
|
## Install or update
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
|
-
npm install --global intentdna@1.8.
|
|
20
|
+
npm install --global intentdna@1.8.5
|
|
21
21
|
dna --version
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
-
Version `1.8.
|
|
24
|
+
Version `1.8.5` keeps Claude plugin Hook delivery in `hooks/hooks.json`: plugin sync does not register project `settings.json` Hooks, retracts exact stale bin-mode `dna-hook` entries once, and leaves user settings untouched in steady state. Online generates copyable local commands only; you still run and review them in your own project.
|
|
25
25
|
|
|
26
26
|
## Product Shape
|
|
27
27
|
|
|
@@ -644,7 +644,7 @@ function suggestionAction(params) {
|
|
|
644
644
|
asset: params.asset,
|
|
645
645
|
suggestionId: params.suggestion.id,
|
|
646
646
|
saveTemplate: params.saveTemplate,
|
|
647
|
-
workflow: params.workflow,
|
|
647
|
+
workflow: params.workflow ?? params.suggestion.targetWorkflow,
|
|
648
648
|
}),
|
|
649
649
|
};
|
|
650
650
|
}
|
|
@@ -667,7 +667,7 @@ function buildSuggestionImprovementRoute(params) {
|
|
|
667
667
|
? suggestionReviewCommand({
|
|
668
668
|
asset: params.asset.name,
|
|
669
669
|
suggestionId: params.draftSuggestion.id,
|
|
670
|
-
workflow: "fix",
|
|
670
|
+
workflow: params.draftSuggestion.targetWorkflow ?? "fix",
|
|
671
671
|
decision: "accept",
|
|
672
672
|
})
|
|
673
673
|
: undefined;
|
|
@@ -722,8 +722,8 @@ function buildSuggestionImprovementRoute(params) {
|
|
|
722
722
|
];
|
|
723
723
|
}
|
|
724
724
|
function firstDraftableSuggestion(suggestions) {
|
|
725
|
-
return suggestions.find((suggestion) => suggestion.
|
|
726
|
-
suggestion.
|
|
725
|
+
return suggestions.find((suggestion) => suggestion.id === WORKFLOW_SUGGESTION_IDS.c5CapturedRunFacts && suggestion.targetWorkflow) ??
|
|
726
|
+
suggestions.find((suggestion) => suggestion.type === "template_improvement");
|
|
727
727
|
}
|
|
728
728
|
function evidenceSuggestionDraftCommand(asset, evidence, saveTemplate) {
|
|
729
729
|
if (!evidence || asset.status.evidencePosture !== "dogfood_linked")
|
|
@@ -753,7 +753,10 @@ function c5GapCaptures(index) {
|
|
|
753
753
|
}
|
|
754
754
|
function c5EvidenceRefs(captures) {
|
|
755
755
|
const refs = captures.flatMap((capture) => [
|
|
756
|
+
{ type: "capture_event_id", ref: capture.eventId },
|
|
756
757
|
{ type: "capture_event", ref: capture.path },
|
|
758
|
+
...(capture.workflowAsset !== "unknown" ? [{ type: "workflow_asset", ref: capture.workflowAsset }] : []),
|
|
759
|
+
...(capture.workflowId !== "unknown" ? [{ type: "workflow", ref: capture.workflowId }] : []),
|
|
757
760
|
...capture.evidenceRefs,
|
|
758
761
|
]);
|
|
759
762
|
const seen = new Set();
|
|
@@ -765,6 +768,31 @@ function c5EvidenceRefs(captures) {
|
|
|
765
768
|
return true;
|
|
766
769
|
});
|
|
767
770
|
}
|
|
771
|
+
function assetWorkflowTarget(asset, runtimeWorkflowId) {
|
|
772
|
+
if (!runtimeWorkflowId || runtimeWorkflowId === "unknown")
|
|
773
|
+
return undefined;
|
|
774
|
+
const workflowIds = new Set(asset.workflows.map((workflow) => workflow.id));
|
|
775
|
+
if (workflowIds.has(runtimeWorkflowId))
|
|
776
|
+
return runtimeWorkflowId;
|
|
777
|
+
if (asset.namespace && runtimeWorkflowId.startsWith(`${asset.namespace}_`)) {
|
|
778
|
+
const unprefixed = runtimeWorkflowId.slice(asset.namespace.length + 1);
|
|
779
|
+
if (workflowIds.has(unprefixed))
|
|
780
|
+
return unprefixed;
|
|
781
|
+
}
|
|
782
|
+
return undefined;
|
|
783
|
+
}
|
|
784
|
+
function c5TargetWorkflow(asset, captures) {
|
|
785
|
+
const targets = new Set();
|
|
786
|
+
for (const capture of captures) {
|
|
787
|
+
const target = assetWorkflowTarget(asset, capture.workflowId);
|
|
788
|
+
if (!target)
|
|
789
|
+
return undefined;
|
|
790
|
+
targets.add(target);
|
|
791
|
+
if (targets.size > 1)
|
|
792
|
+
return undefined;
|
|
793
|
+
}
|
|
794
|
+
return targets.size === 1 ? [...targets][0] : undefined;
|
|
795
|
+
}
|
|
768
796
|
function c5CaptureBootstrapCommands(assetName) {
|
|
769
797
|
return [
|
|
770
798
|
`dna init --template ${assetName}`,
|
|
@@ -836,7 +864,7 @@ function defaultSuggestionCommands(params) {
|
|
|
836
864
|
const reviewCommand = suggestionReviewCommand({
|
|
837
865
|
asset: params.asset.name,
|
|
838
866
|
suggestionId: params.suggestion.id,
|
|
839
|
-
workflow: "fix",
|
|
867
|
+
workflow: params.suggestion.targetWorkflow ?? "fix",
|
|
840
868
|
decision: "accept",
|
|
841
869
|
});
|
|
842
870
|
return [
|
|
@@ -846,7 +874,7 @@ function defaultSuggestionCommands(params) {
|
|
|
846
874
|
asset: params.asset.name,
|
|
847
875
|
suggestionId: params.suggestion.id,
|
|
848
876
|
saveTemplate: `${params.asset.name}-v2`,
|
|
849
|
-
workflow: "fix",
|
|
877
|
+
workflow: params.suggestion.targetWorkflow ?? "fix",
|
|
850
878
|
}),
|
|
851
879
|
`dna assets diff ${params.asset.name} ${params.asset.name}-v2`,
|
|
852
880
|
`dna assets adoption ${params.asset.name}-v2`,
|
|
@@ -926,20 +954,21 @@ export function buildAssetSuggestions(asset, index, assets = [asset]) {
|
|
|
926
954
|
});
|
|
927
955
|
}
|
|
928
956
|
if (c5Gaps.length > 0) {
|
|
957
|
+
const targetWorkflow = c5TargetWorkflow(asset, c5Gaps);
|
|
929
958
|
const evidenceCommand = `dna assets evidence ${asset.name} --json`;
|
|
930
959
|
const suggestionsCommand = `dna workflow suggestions ${asset.name} --json`;
|
|
931
|
-
const reviewCommand = suggestionReviewCommand({
|
|
960
|
+
const reviewCommand = targetWorkflow ? suggestionReviewCommand({
|
|
932
961
|
asset: asset.name,
|
|
933
962
|
suggestionId: WORKFLOW_SUGGESTION_IDS.c5CapturedRunFacts,
|
|
934
|
-
workflow:
|
|
963
|
+
workflow: targetWorkflow,
|
|
935
964
|
decision: "accept",
|
|
936
|
-
});
|
|
937
|
-
const draftCommand = suggestionDraftCommand({
|
|
965
|
+
}) : undefined;
|
|
966
|
+
const draftCommand = targetWorkflow ? suggestionDraftCommand({
|
|
938
967
|
asset: asset.name,
|
|
939
968
|
suggestionId: WORKFLOW_SUGGESTION_IDS.c5CapturedRunFacts,
|
|
940
969
|
saveTemplate: `${asset.name}-v2`,
|
|
941
|
-
workflow:
|
|
942
|
-
});
|
|
970
|
+
workflow: targetWorkflow,
|
|
971
|
+
}) : undefined;
|
|
943
972
|
suggestions.push({
|
|
944
973
|
id: WORKFLOW_SUGGESTION_IDS.c5CapturedRunFacts,
|
|
945
974
|
type: "evidence_improvement",
|
|
@@ -947,14 +976,17 @@ export function buildAssetSuggestions(asset, index, assets = [asset]) {
|
|
|
947
976
|
title: "Review captured local run facts before changing the workflow asset",
|
|
948
977
|
evidence: c5Gaps.slice(0, 5).map(compactEvidenceCapture).join("; "),
|
|
949
978
|
evidenceRefs: c5EvidenceRefs(c5Gaps),
|
|
979
|
+
targetWorkflow,
|
|
950
980
|
rationale: "C5 hook evidence shows blocked, failed, retry, unsupported, or human-intervention facts. Evidence can suggest a reviewed change, but it must not mutate policy or templates automatically.",
|
|
951
981
|
sourceModules: [],
|
|
952
|
-
nextAction:
|
|
982
|
+
nextAction: reviewCommand
|
|
983
|
+
? `Review and accept the C5-backed suggestion before drafting: ${reviewCommand}`
|
|
984
|
+
: `Inspect the C5 evidence and choose one matching workflow before review; mixed or unknown workflow attribution cannot be routed automatically.`,
|
|
953
985
|
nextCommands: [
|
|
954
986
|
evidenceCommand,
|
|
955
987
|
suggestionsCommand,
|
|
956
|
-
reviewCommand,
|
|
957
|
-
draftCommand,
|
|
988
|
+
...(reviewCommand ? [reviewCommand] : []),
|
|
989
|
+
...(draftCommand ? [draftCommand] : []),
|
|
958
990
|
`record explicit manager rationale for any C5-derived change to ${asset.name}`,
|
|
959
991
|
],
|
|
960
992
|
mutationBoundary: "C5 captured facts are local review evidence only; they do not edit templates, mutate policy, adopt assets, bind organizations, sync runtime artifacts, upload cloud reports, or create runtime authority.",
|
|
@@ -1055,8 +1087,10 @@ export function buildAssetSuggestions(asset, index, assets = [asset]) {
|
|
|
1055
1087
|
index,
|
|
1056
1088
|
suggestion,
|
|
1057
1089
|
}));
|
|
1058
|
-
const activeFollowup = active.length > 0 ? activeEvidenceFollowupReviewRoute(asset.name, assets, index) : undefined;
|
|
1059
1090
|
const draftSuggestion = firstDraftableSuggestion(completedSuggestions);
|
|
1091
|
+
const activeFollowup = active.length > 0 && draftSuggestion?.id !== WORKFLOW_SUGGESTION_IDS.c5CapturedRunFacts
|
|
1092
|
+
? activeEvidenceFollowupReviewRoute(asset.name, assets, index)
|
|
1093
|
+
: undefined;
|
|
1060
1094
|
const nextCommands = [
|
|
1061
1095
|
`dna assets evidence ${asset.name}`,
|
|
1062
1096
|
];
|
|
@@ -1075,7 +1109,7 @@ export function buildAssetSuggestions(asset, index, assets = [asset]) {
|
|
|
1075
1109
|
nextCommands.push(suggestionReviewCommand({
|
|
1076
1110
|
asset: asset.name,
|
|
1077
1111
|
suggestionId: draftSuggestion.id,
|
|
1078
|
-
workflow: "fix",
|
|
1112
|
+
workflow: draftSuggestion.targetWorkflow ?? "fix",
|
|
1079
1113
|
decision: "accept",
|
|
1080
1114
|
}));
|
|
1081
1115
|
}
|
|
@@ -612,6 +612,13 @@ export async function runSync(opts) {
|
|
|
612
612
|
opts.skillsDir = resolve(projectDir, ".agents", "skills");
|
|
613
613
|
process.stderr.write("Codex workflow projection: .agents/skills\n");
|
|
614
614
|
}
|
|
615
|
+
const requestedMode = opts.mode ?? (opts.plugin === true ? "plugin" : opts.plugin === false ? "bin" : "auto");
|
|
616
|
+
let resolvedMode = requestedMode === "auto" ? "bin" : requestedMode;
|
|
617
|
+
if (!opts.remove && requestedMode === "auto") {
|
|
618
|
+
resolvedMode = await detectMode();
|
|
619
|
+
process.stderr.write(`Auto-detect: ${resolvedMode} mode${resolvedMode === "plugin" ? " (intentdna registered as Claude Code plugin)" : ""}\n`);
|
|
620
|
+
}
|
|
621
|
+
opts.plugin = resolvedMode === "plugin";
|
|
615
622
|
if (!opts.remove && !opts.inject && !opts.hooksDir && !opts.agentsDir && !opts.workflowDir && !opts.skillsDir && !opts.settingsPath && !opts.codex) {
|
|
616
623
|
// Auto-detect Claude Code environment → fill all outputs
|
|
617
624
|
const hasClaudeDir = await fileExists(resolve(projectDir, ".claude"));
|
|
@@ -621,16 +628,11 @@ export async function runSync(opts) {
|
|
|
621
628
|
opts.agentsDir = resolve(projectDir, ".claude", "agents");
|
|
622
629
|
opts.skillsDir = resolve(projectDir, ".claude", "skills");
|
|
623
630
|
opts.settingsPath = resolve(projectDir, ".claude", "settings.json");
|
|
624
|
-
process.stderr.write(
|
|
631
|
+
process.stderr.write(resolvedMode === "plugin"
|
|
632
|
+
? "Auto-detect: Claude Code → CLAUDE.md + agents + skills; plugin hooks (no settings injection)\n"
|
|
633
|
+
: "Auto-detect: Claude Code → CLAUDE.md + agents + skills + settings\n");
|
|
625
634
|
}
|
|
626
635
|
}
|
|
627
|
-
const requestedMode = opts.mode ?? (opts.plugin === true ? "plugin" : opts.plugin === false ? "bin" : "auto");
|
|
628
|
-
let resolvedMode = requestedMode === "auto" ? "bin" : requestedMode;
|
|
629
|
-
if (!opts.remove && requestedMode === "auto") {
|
|
630
|
-
resolvedMode = await detectMode();
|
|
631
|
-
process.stderr.write(`Auto-detect: ${resolvedMode} mode${resolvedMode === "plugin" ? " (intentdna registered as Claude Code plugin)" : ""}\n`);
|
|
632
|
-
}
|
|
633
|
-
opts.plugin = resolvedMode === "plugin";
|
|
634
636
|
// Handle --remove
|
|
635
637
|
if (opts.remove) {
|
|
636
638
|
if (!opts.inject && !opts.hooksDir && !opts.agentsDir && !opts.workflowDir && !opts.skillsDir && !opts.codex) {
|
package/dist/cli/index.js
CHANGED
|
File without changes
|
package/dist/hooks/cli.d.ts
CHANGED
|
@@ -99,10 +99,13 @@ export declare function runVerifiersForTest(projectDir: string, ir: ConstraintIR
|
|
|
99
99
|
workflow: string;
|
|
100
100
|
current_step: string;
|
|
101
101
|
current_role: string;
|
|
102
|
+
iteration?: number;
|
|
103
|
+
started_at?: string;
|
|
102
104
|
inputs?: Record<string, string>;
|
|
103
105
|
resolved_variables?: Record<string, string>;
|
|
104
106
|
}, when: VerifierSpec["when"], sessionId?: string, options?: {
|
|
105
107
|
commandTimeoutMs?: number;
|
|
108
|
+
triggerEvent?: HookEvent;
|
|
106
109
|
}): Promise<VerifierResultEntry[]>;
|
|
107
110
|
export declare function runStopVerifiersForTest(projectDir: string, ir: ConstraintIR, workflowState: {
|
|
108
111
|
workflow: string;
|
package/dist/hooks/cli.js
CHANGED
|
@@ -23,7 +23,7 @@ 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, appendEvidenceCaptureEvent, rotateTraces, readTraces, cleanStaleState, readSurgeonAttempts, writeSurgeonAttempts, appendSessionRead, readSessionReads, appendVerifierResult, appendCompletedArtifact, artifactIdentity, buildArtifactKey, readArtifactManifest, resolveArtifactTemplate, safePathComponent, writeArtifactManifest, EVIDENCE_CAPTURE_SCHEMA_VERSION } from "./state.js";
|
|
26
|
+
import { appendAudit, readWorkflowState, appendTrace, appendRuntimeDecisionEvent, appendEvidenceCaptureEvent, rotateTraces, readTraces, cleanStaleState, readSurgeonAttempts, writeSurgeonAttempts, appendSessionRead, readSessionReads, appendVerifierResult, readVerifierResults, 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
29
|
import { RUNTIME_DECISION_EVENT_SCHEMA_VERSION, trustedEvidenceCaptureAttribution } from "../governance/index.js";
|
|
@@ -347,13 +347,16 @@ export async function runHookEvent(options) {
|
|
|
347
347
|
};
|
|
348
348
|
let wfStateRaw = null;
|
|
349
349
|
// Load workflow state for events that need it (handoff context + PreCompact preservation)
|
|
350
|
-
if (event === "PreToolUse" || event === "PostToolUse" || event === "PreCompact") {
|
|
350
|
+
if (event === "PreToolUse" || event === "PostToolUse" || event === "PreCompact" || event === "SubagentStop") {
|
|
351
351
|
const workflowState = await readWorkflowStateForHook(projectDir, event, sessionId);
|
|
352
352
|
if ("output" in workflowState) {
|
|
353
353
|
await writeRuntimeDecision(workflowState.output, { output: workflowState.output, trace: { matched_rule: "workflow_boundary" } }, null);
|
|
354
354
|
return finish(workflowState.output);
|
|
355
355
|
}
|
|
356
356
|
wfStateRaw = workflowState.state;
|
|
357
|
+
if (event === "SubagentStop" && wfStateRaw && !subagentMatchesWorkflowRole(rawInput, wfStateRaw)) {
|
|
358
|
+
wfStateRaw = null;
|
|
359
|
+
}
|
|
357
360
|
if (wfStateRaw && wfStateRaw.active) {
|
|
358
361
|
state.workflowState = {
|
|
359
362
|
current_step: wfStateRaw.current_step,
|
|
@@ -363,13 +366,15 @@ export async function runHookEvent(options) {
|
|
|
363
366
|
completed_artifacts: wfStateRaw.completed_artifacts,
|
|
364
367
|
iteration: wfStateRaw.iteration, // G4: pass iteration for state-driven rules
|
|
365
368
|
};
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
369
|
+
if (event !== "SubagentStop") {
|
|
370
|
+
const artifactFacts = await resolveWorkflowArtifactFactsForHook(projectDir, ir, wfStateRaw, event, sessionId);
|
|
371
|
+
if ("output" in artifactFacts) {
|
|
372
|
+
appendArtifactResolverTrace(projectDir, event, wfStateRaw, artifactFacts.output, sessionId);
|
|
373
|
+
await writeRuntimeDecision(artifactFacts.output, { output: artifactFacts.output, trace: { matched_rule: "handoff", step_id: wfStateRaw.current_step } }, wfStateRaw);
|
|
374
|
+
return finish(artifactFacts.output);
|
|
375
|
+
}
|
|
376
|
+
state.artifactFacts = artifactFacts.facts;
|
|
371
377
|
}
|
|
372
|
-
state.artifactFacts = artifactFacts.facts;
|
|
373
378
|
}
|
|
374
379
|
}
|
|
375
380
|
if (event === "PreToolUse" && wfStateRaw?.active) {
|
|
@@ -400,6 +405,46 @@ export async function runHookEvent(options) {
|
|
|
400
405
|
return finish(gateResult.output);
|
|
401
406
|
}
|
|
402
407
|
}
|
|
408
|
+
if (event === "SubagentStop" && wfStateRaw?.active && subagentMatchesWorkflowRole(rawInput, wfStateRaw)) {
|
|
409
|
+
const artifactFacts = await finalizeAndResolveArtifactsForHook(projectDir, ir, wfStateRaw, event, sessionId);
|
|
410
|
+
if ("output" in artifactFacts) {
|
|
411
|
+
appendArtifactResolverTrace(projectDir, event, wfStateRaw, artifactFacts.output, sessionId);
|
|
412
|
+
await writeRuntimeDecision(artifactFacts.output, { output: artifactFacts.output, trace: { matched_rule: "handoff", step_id: wfStateRaw.current_step } }, wfStateRaw);
|
|
413
|
+
return finish(artifactFacts.output);
|
|
414
|
+
}
|
|
415
|
+
const verifierContext = {
|
|
416
|
+
workflow: wfStateRaw.workflow,
|
|
417
|
+
current_step: wfStateRaw.current_step,
|
|
418
|
+
current_role: wfStateRaw.current_role,
|
|
419
|
+
iteration: wfStateRaw.iteration,
|
|
420
|
+
started_at: wfStateRaw.started_at,
|
|
421
|
+
inputs: wfStateRaw.inputs,
|
|
422
|
+
resolved_variables: wfStateRaw.resolved_variables,
|
|
423
|
+
artifact_facts: artifactFacts.facts,
|
|
424
|
+
};
|
|
425
|
+
const verifierResults = [
|
|
426
|
+
...await runVerifiersForTest(projectDir, ir, verifierContext, "pre_handoff", sessionId, { triggerEvent: "SubagentStop" }),
|
|
427
|
+
...await runVerifiersForTest(projectDir, ir, verifierContext, "post_step", sessionId, { triggerEvent: "SubagentStop" }),
|
|
428
|
+
];
|
|
429
|
+
const blockingVerifierFailures = verifierResults.filter((result) => result.status === "fail" && result.severity === "block");
|
|
430
|
+
if (blockingVerifierFailures.length > 0) {
|
|
431
|
+
const output = blockOutput(`[Intent DNA] SubagentStop verifier failures at step '${wfStateRaw.current_step}':\n` +
|
|
432
|
+
blockingVerifierFailures.map((result) => ` - ${result.message ?? result.verifier_id}`).join("\n"));
|
|
433
|
+
const traceId = randomUUID();
|
|
434
|
+
appendTrace(projectDir, {
|
|
435
|
+
trace_id: traceId,
|
|
436
|
+
event,
|
|
437
|
+
workflow: wfStateRaw.workflow,
|
|
438
|
+
step: wfStateRaw.current_step,
|
|
439
|
+
decision: "block",
|
|
440
|
+
reason: output.reason,
|
|
441
|
+
duration_ms: 0,
|
|
442
|
+
timestamp: new Date().toISOString(),
|
|
443
|
+
}, sessionId).catch(() => { });
|
|
444
|
+
await writeRuntimeDecision(output, { output, trace: { matched_rule: "validator", step_id: wfStateRaw.current_step } }, wfStateRaw, traceId);
|
|
445
|
+
return finish(output);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
403
448
|
// Special handling for Stop — needs async workflow state read + session summary
|
|
404
449
|
if (event === "Stop") {
|
|
405
450
|
const workflowState = await readWorkflowStateForHook(projectDir, event, sessionId);
|
|
@@ -423,6 +468,7 @@ export async function runHookEvent(options) {
|
|
|
423
468
|
workflow: wfState.workflow,
|
|
424
469
|
current_step: wfState.current_step,
|
|
425
470
|
current_role: wfState.current_role,
|
|
471
|
+
iteration: wfState.iteration,
|
|
426
472
|
started_at: wfState.started_at,
|
|
427
473
|
inputs: wfState.inputs,
|
|
428
474
|
resolved_variables: wfState.resolved_variables,
|
|
@@ -430,7 +476,7 @@ export async function runHookEvent(options) {
|
|
|
430
476
|
artifact_facts: stopArtifactFacts,
|
|
431
477
|
} : null;
|
|
432
478
|
const verifierResults = stopContext?.active
|
|
433
|
-
? await
|
|
479
|
+
? await runStopVerifiersForHook(projectDir, ir, stopContext, sessionId)
|
|
434
480
|
: [];
|
|
435
481
|
const blockingVerifierFailures = verifierResults.filter((result) => result.status === "fail" && result.severity === "block");
|
|
436
482
|
let stopOutput = blockingVerifierFailures.length > 0
|
|
@@ -660,16 +706,21 @@ function inferWorkflowAssetFromIr(ir, workflowId) {
|
|
|
660
706
|
const attribution = trustedEvidenceCaptureAttribution(ir, { workflowId });
|
|
661
707
|
return attribution.workflowAsset === "unknown" ? undefined : attribution.workflowAsset;
|
|
662
708
|
}
|
|
709
|
+
function agentTypeForWorkflowRole(role) {
|
|
710
|
+
if (!role)
|
|
711
|
+
return undefined;
|
|
712
|
+
return `dna-${role.replace(/([a-z])([A-Z])/g, "$1-$2").replace(/[_\s]+/g, "-").toLowerCase()}`;
|
|
713
|
+
}
|
|
714
|
+
function subagentMatchesWorkflowRole(rawInput, wfState) {
|
|
715
|
+
return typeof rawInput.agent_type === "string" && rawInput.agent_type === agentTypeForWorkflowRole(wfState.current_role);
|
|
716
|
+
}
|
|
663
717
|
async function runCodexHookWithLoadedIr(rawInput, projectDir, args, ir) {
|
|
664
718
|
const sessionId = typeof rawInput.session_id === "string"
|
|
665
719
|
? rawInput.session_id
|
|
666
720
|
: typeof rawInput.sessionId === "string"
|
|
667
721
|
? rawInput.sessionId
|
|
668
722
|
: undefined;
|
|
669
|
-
const
|
|
670
|
-
? await readWorkflowState(projectDir, sessionId)
|
|
671
|
-
: null;
|
|
672
|
-
const workflowState = sessionWorkflowState ?? await readWorkflowState(projectDir);
|
|
723
|
+
const workflowState = await readWorkflowStateWithRootFallback(projectDir, sessionId);
|
|
673
724
|
const workflowAsset = inferWorkflowAssetFromIr(ir, workflowState?.workflow);
|
|
674
725
|
return runCodexHook({ ...rawInput, event: codexHookEventForInput(rawInput, args) }, {
|
|
675
726
|
ir,
|
|
@@ -887,12 +938,21 @@ function artifactResolverErrorOutput(event, error) {
|
|
|
887
938
|
}
|
|
888
939
|
async function readWorkflowStateForHook(projectDir, event, sessionId) {
|
|
889
940
|
try {
|
|
890
|
-
|
|
941
|
+
const state = event === "SubagentStop"
|
|
942
|
+
? await readWorkflowStateWithRootFallback(projectDir, sessionId)
|
|
943
|
+
: await readWorkflowState(projectDir, sessionId);
|
|
944
|
+
return { state };
|
|
891
945
|
}
|
|
892
946
|
catch (error) {
|
|
893
947
|
return { output: artifactResolverErrorOutput(event, error) };
|
|
894
948
|
}
|
|
895
949
|
}
|
|
950
|
+
async function readWorkflowStateWithRootFallback(projectDir, sessionId) {
|
|
951
|
+
if (!sessionId)
|
|
952
|
+
return readWorkflowState(projectDir);
|
|
953
|
+
const sessionState = await readWorkflowState(projectDir, sessionId);
|
|
954
|
+
return sessionState ?? readWorkflowState(projectDir);
|
|
955
|
+
}
|
|
896
956
|
async function resolveWorkflowArtifactFactsForHook(projectDir, ir, wfState, event, sessionId) {
|
|
897
957
|
try {
|
|
898
958
|
return { facts: await resolveWorkflowArtifactFacts(projectDir, ir, wfState, sessionId) };
|
|
@@ -1082,6 +1142,8 @@ async function runVerifierSpec(projectDir, ir, spec, workflowState, sessionId, o
|
|
|
1082
1142
|
const entry = {
|
|
1083
1143
|
verifier_id: spec.id,
|
|
1084
1144
|
when: spec.when,
|
|
1145
|
+
iteration: workflowState.iteration,
|
|
1146
|
+
trigger_event: options?.triggerEvent,
|
|
1085
1147
|
severity: spec.severity,
|
|
1086
1148
|
kind: spec.kind,
|
|
1087
1149
|
workflow: workflowState.workflow,
|
|
@@ -1109,6 +1171,37 @@ export async function runVerifiersForTest(projectDir, ir, workflowState, when, s
|
|
|
1109
1171
|
}
|
|
1110
1172
|
return results;
|
|
1111
1173
|
}
|
|
1174
|
+
async function reusableSubagentStopResults(projectDir, ir, workflowState, when, sessionId) {
|
|
1175
|
+
if (!sessionId)
|
|
1176
|
+
return null;
|
|
1177
|
+
const specs = (ir.verifier_specs ?? []).filter((spec) => spec.when === when &&
|
|
1178
|
+
spec.workflow_name === workflowState.workflow &&
|
|
1179
|
+
spec.step_id === workflowState.current_step);
|
|
1180
|
+
if (specs.length === 0)
|
|
1181
|
+
return [];
|
|
1182
|
+
const startedAt = workflowState.started_at ? Date.parse(workflowState.started_at) : Number.NaN;
|
|
1183
|
+
const prior = (await readVerifierResults(projectDir, sessionId)).filter((result) => result.trigger_event === "SubagentStop" &&
|
|
1184
|
+
result.workflow === workflowState.workflow &&
|
|
1185
|
+
result.step_id === workflowState.current_step &&
|
|
1186
|
+
result.when === when &&
|
|
1187
|
+
(workflowState.iteration === undefined || result.iteration === workflowState.iteration) &&
|
|
1188
|
+
(Number.isNaN(startedAt) || Date.parse(result.timestamp) >= startedAt));
|
|
1189
|
+
const latestByVerifier = new Map();
|
|
1190
|
+
for (const result of prior)
|
|
1191
|
+
latestByVerifier.set(result.verifier_id, result);
|
|
1192
|
+
if (!specs.every((spec) => latestByVerifier.has(spec.id)))
|
|
1193
|
+
return null;
|
|
1194
|
+
return specs.map((spec) => latestByVerifier.get(spec.id));
|
|
1195
|
+
}
|
|
1196
|
+
async function runStopVerifiersForHook(projectDir, ir, workflowState, sessionId) {
|
|
1197
|
+
const results = [];
|
|
1198
|
+
for (const when of ["pre_handoff", "post_step"]) {
|
|
1199
|
+
const reusable = await reusableSubagentStopResults(projectDir, ir, workflowState, when, sessionId);
|
|
1200
|
+
results.push(...(reusable ?? await runVerifiersForTest(projectDir, ir, workflowState, when, sessionId, { triggerEvent: "Stop" })));
|
|
1201
|
+
}
|
|
1202
|
+
results.push(...await runVerifiersForTest(projectDir, ir, workflowState, "stop", sessionId, { triggerEvent: "Stop" }));
|
|
1203
|
+
return results;
|
|
1204
|
+
}
|
|
1112
1205
|
export async function runStopVerifiersForTest(projectDir, ir, workflowState, sessionId, options) {
|
|
1113
1206
|
const results = [];
|
|
1114
1207
|
results.push(...await runVerifiersForTest(projectDir, ir, workflowState, "pre_handoff", sessionId, options));
|
|
@@ -1229,7 +1322,7 @@ async function handleSurgeonReflection(ir, input, projectDir, sessionId) {
|
|
|
1229
1322
|
if (!wfState || !wfState.active)
|
|
1230
1323
|
return null;
|
|
1231
1324
|
// Match agent_type to current role in workflow
|
|
1232
|
-
const expectedAgentType =
|
|
1325
|
+
const expectedAgentType = agentTypeForWorkflowRole(wfState.current_role);
|
|
1233
1326
|
if (agentType !== expectedAgentType)
|
|
1234
1327
|
return null;
|
|
1235
1328
|
// Look up step config from the compiled IR's workflow
|
package/dist/hooks/state.d.ts
CHANGED
package/dist/hooks/state.js
CHANGED
|
@@ -338,6 +338,8 @@ export function verifierResultId(result) {
|
|
|
338
338
|
.update(JSON.stringify({
|
|
339
339
|
verifier_id: result.verifier_id,
|
|
340
340
|
when: result.when,
|
|
341
|
+
iteration: result.iteration ?? 0,
|
|
342
|
+
trigger_event: result.trigger_event ?? "",
|
|
341
343
|
severity: result.severity,
|
|
342
344
|
kind: result.kind,
|
|
343
345
|
workflow: result.workflow ?? "",
|
|
@@ -370,6 +372,8 @@ function verifierResultKey(result) {
|
|
|
370
372
|
return [
|
|
371
373
|
result.verifier_id,
|
|
372
374
|
result.when,
|
|
375
|
+
result.iteration ?? 0,
|
|
376
|
+
result.trigger_event ?? "",
|
|
373
377
|
result.severity,
|
|
374
378
|
result.kind,
|
|
375
379
|
result.workflow ?? "",
|
package/dist/mcp/index.js
CHANGED
|
@@ -16,6 +16,7 @@ import { createCompileTools } from "./tools-compile.js";
|
|
|
16
16
|
import { createEnforceTools } from "./tools-enforce.js";
|
|
17
17
|
import { createObservabilityTools } from "./tools-observability.js";
|
|
18
18
|
import { createContextTools } from "./tools-context.js";
|
|
19
|
+
import { createArtifactTools } from "./tools-artifacts.js";
|
|
19
20
|
// Parse args
|
|
20
21
|
const args = process.argv.slice(2);
|
|
21
22
|
let projectDir = process.cwd();
|
|
@@ -38,6 +39,7 @@ const tools = [
|
|
|
38
39
|
...createStateTools(projectDir),
|
|
39
40
|
...createCompileTools(projectDir),
|
|
40
41
|
...createContextTools(projectDir),
|
|
42
|
+
...createArtifactTools(projectDir),
|
|
41
43
|
...createEnforceTools(projectDir),
|
|
42
44
|
...createObservabilityTools(projectDir),
|
|
43
45
|
];
|