intentdna 1.5.16 → 1.5.18

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.
Files changed (36) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.md +72 -92
  4. package/dist/audit/index.d.ts +12 -3
  5. package/dist/cli/commands/feedback.d.ts +3 -0
  6. package/dist/cli/commands/feedback.js +33 -11
  7. package/dist/cli/commands/run.js +1 -1
  8. package/dist/cli/commands/sync.js +5 -5
  9. package/dist/compiler/cascade.d.ts +3 -1
  10. package/dist/compiler/cascade.js +86 -2
  11. package/dist/compiler/compile.js +93 -3
  12. package/dist/compiler/workflow.d.ts +1 -0
  13. package/dist/compiler/workflow.js +1 -0
  14. package/dist/evolution/trace-bridge.d.ts +4 -5
  15. package/dist/evolution/trace-bridge.js +31 -55
  16. package/dist/hooks/cli.d.ts +18 -1
  17. package/dist/hooks/cli.js +624 -24
  18. package/dist/hooks/enforce.js +3 -2
  19. package/dist/hooks/state.d.ts +20 -1
  20. package/dist/hooks/state.js +55 -0
  21. package/dist/runtime/markdown.d.ts +1 -1
  22. package/dist/runtime/markdown.js +149 -0
  23. package/dist/runtime/settings-adapter.d.ts +3 -2
  24. package/dist/runtime/settings-adapter.js +26 -4
  25. package/dist/runtime/skill-adapter.js +49 -23
  26. package/dist/schema/types.d.ts +39 -0
  27. package/dist/schema/validate.js +143 -0
  28. package/dist/signals/index.d.ts +45 -0
  29. package/dist/signals/index.js +117 -0
  30. package/dist/templates/code-review-pipeline.dna.yaml +4 -0
  31. package/dist/templates/flutter-rewrite.dna.yaml +139 -212
  32. package/dist/templates/full-pipeline.dna.yaml +4 -0
  33. package/dist/templates/mobile-dev.dna.yaml +5 -0
  34. package/hooks/hooks.json +1 -1
  35. package/package.json +1 -1
  36. package/spec/control-plane-convergence-handoff-2026-04-24.md +432 -0
@@ -150,6 +150,38 @@ function geneToValidators(name, gene) {
150
150
  }
151
151
  return validators;
152
152
  }
153
+ function legibilityAssetsToContextFiles(assets) {
154
+ if (!assets)
155
+ return undefined;
156
+ const mandatory = assets.mandatory
157
+ ?.filter((asset) => asset.type === "required_read")
158
+ .map((asset) => asset.path);
159
+ const per_role = assets.per_role
160
+ ? Object.fromEntries(Object.entries(assets.per_role).map(([role, roleAssets]) => [
161
+ role,
162
+ roleAssets.filter((asset) => asset.type === "required_read").map((asset) => asset.path),
163
+ ]).filter(([, paths]) => paths.length > 0))
164
+ : undefined;
165
+ const per_workflow = assets.per_workflow
166
+ ? Object.fromEntries(Object.entries(assets.per_workflow).map(([workflow, wfAssets]) => [
167
+ workflow,
168
+ wfAssets.filter((asset) => asset.type === "required_read").map((asset) => asset.path),
169
+ ]).filter(([, paths]) => paths.length > 0))
170
+ : undefined;
171
+ if ((!mandatory || mandatory.length === 0) && !per_role && !per_workflow)
172
+ return undefined;
173
+ return { mandatory, per_role, per_workflow };
174
+ }
175
+ function resolvePostCheckCommand(check) {
176
+ const normalized = check.trim();
177
+ const builtins = {
178
+ vitest_run: "npm test",
179
+ tsc_no_errors: "npx tsc --noEmit",
180
+ dart_analyze: "dart analyze",
181
+ flutter_test: "flutter test",
182
+ };
183
+ return builtins[normalized] ?? normalized;
184
+ }
153
185
  /**
154
186
  * Compile activated DNA into a Constraint IR.
155
187
  */
@@ -158,6 +190,7 @@ export function compileDNA(activated, cascaded) {
158
190
  const toolFilters = [];
159
191
  const gates = [];
160
192
  const validators = [];
193
+ const verifierSpecs = [];
161
194
  const injections = [];
162
195
  for (const [name, gene] of Object.entries(activated.genes)) {
163
196
  directives.push(...geneToDirectives(name, gene));
@@ -210,15 +243,67 @@ export function compileDNA(activated, cascaded) {
210
243
  }
211
244
  // Build step_checkpoints from all workflow steps that have checkpoints
212
245
  const stepCheckpoints = [];
213
- const allWorkflows = Object.values(cascaded?.workflows ?? {});
214
- for (const wf of allWorkflows) {
246
+ const allWorkflows = Object.entries(cascaded?.workflows ?? {});
247
+ for (const [workflowKey, wf] of allWorkflows) {
215
248
  for (const step of wf.steps ?? []) {
249
+ const roleGeneHints = cascaded?.roles?.[step.role]?.activates_genes ?? [];
216
250
  if (step.checkpoints && step.checkpoints.length > 0) {
217
251
  stepCheckpoints.push({
218
252
  step_role: step.role,
219
253
  step_id: step.id,
220
254
  checkpoints: [...step.checkpoints],
221
255
  });
256
+ for (const checkpoint of step.checkpoints) {
257
+ verifierSpecs.push({
258
+ id: `${workflowKey}:${step.id}:checkpoint:${checkpoint.assert}`,
259
+ when: "post_step",
260
+ severity: checkpoint.action ?? "block",
261
+ kind: "checkpoint",
262
+ workflow_name: workflowKey,
263
+ step_id: step.id,
264
+ step_role: step.role,
265
+ source_genes: roleGeneHints.length > 0 ? [...roleGeneHints] : undefined,
266
+ checkpoint,
267
+ source: "workflow_checkpoint",
268
+ });
269
+ }
270
+ }
271
+ if (step.completion && step.completion.length > 0) {
272
+ for (let i = 0; i < step.completion.length; i++) {
273
+ verifierSpecs.push({
274
+ id: `${workflowKey}:${step.id}:completion:${i}`,
275
+ when: "pre_handoff",
276
+ severity: "block",
277
+ kind: "completion",
278
+ workflow_name: workflowKey,
279
+ step_id: step.id,
280
+ step_role: step.role,
281
+ source_genes: roleGeneHints.length > 0 ? [...roleGeneHints] : undefined,
282
+ completion: step.completion[i],
283
+ source: "workflow_completion",
284
+ });
285
+ }
286
+ }
287
+ const rolePostChecks = cascaded?.roles?.[step.role]?.post_checks ?? [];
288
+ for (let i = 0; i < rolePostChecks.length; i++) {
289
+ const resolvedCommand = resolvePostCheckCommand(rolePostChecks[i]);
290
+ verifierSpecs.push({
291
+ id: `${workflowKey}:${step.id}:post-check:${i}`,
292
+ when: "post_step",
293
+ severity: "warn",
294
+ kind: "checkpoint",
295
+ workflow_name: workflowKey,
296
+ step_id: step.id,
297
+ step_role: step.role,
298
+ source_genes: roleGeneHints.length > 0 ? [...roleGeneHints] : undefined,
299
+ checkpoint: {
300
+ assert: `role_post_check_${i}`,
301
+ command: resolvedCommand,
302
+ message: `Post-check failed: ${rolePostChecks[i]}`,
303
+ action: "warn",
304
+ },
305
+ source: "role_post_check",
306
+ });
222
307
  }
223
308
  }
224
309
  }
@@ -275,11 +360,14 @@ export function compileDNA(activated, cascaded) {
275
360
  // Sort directives by priority
276
361
  const priorityOrder = { high: 0, medium: 1, low: 2 };
277
362
  directives.sort((a, b) => priorityOrder[a.priority] - priorityOrder[b.priority]);
363
+ const legacyContextFiles = cascaded?.context_files;
364
+ const compiledContextFiles = legacyContextFiles ?? legibilityAssetsToContextFiles(cascaded?.legibility_assets);
278
365
  return {
279
366
  prompt_directives: directives,
280
367
  tool_filters: toolFilters,
281
368
  pre_execution_gates: gates,
282
369
  post_execution_validators: validators,
370
+ verifier_specs: verifierSpecs.length > 0 ? verifierSpecs : undefined,
283
371
  context_injections: injections,
284
372
  source_dna_ids: activated.source_ids,
285
373
  compiled_at: new Date().toISOString(),
@@ -290,6 +378,8 @@ export function compileDNA(activated, cascaded) {
290
378
  roles_scope_map: rolesScopeMap.length > 0 ? rolesScopeMap : undefined,
291
379
  step_checkpoints: stepCheckpoints.length > 0 ? stepCheckpoints : undefined,
292
380
  workflows_ir: workflowsIR.length > 0 ? workflowsIR : undefined,
293
- context_files: cascaded?.context_files,
381
+ legibility_assets: cascaded?.legibility_assets,
382
+ context_files: compiledContextFiles,
383
+ verifier_policy: cascaded?.verifier_policy,
294
384
  };
295
385
  }
@@ -11,6 +11,7 @@ export interface CompileWorkflowOptions {
11
11
  mermaid?: boolean;
12
12
  /** Roles for scope overlap analysis (needed for isolation: auto) */
13
13
  roles?: Record<string, RoleDef>;
14
+ workflow_key?: string;
14
15
  }
15
16
  export interface CompileWorkflowError {
16
17
  path: string;
@@ -443,6 +443,7 @@ export function compileWorkflow(workflow, options) {
443
443
  retry,
444
444
  compiled_at: new Date().toISOString(),
445
445
  source_workflow: workflow.name,
446
+ workflow_key: options?.workflow_key ?? workflow.name,
446
447
  };
447
448
  // 10. Optional Mermaid diagram
448
449
  if (options?.mermaid) {
@@ -10,7 +10,7 @@
10
10
  * - Role scope: "Role '<name>'" → gene "scope:<role>"
11
11
  * - Gates: "(gene: <name>)" pattern or fallback to "gate"
12
12
  */
13
- import type { TraceEntry } from "../hooks/state.js";
13
+ import type { TraceEntry, VerifierResultEntry } from "../hooks/state.js";
14
14
  import type { ExecutionOutcome } from "./types.js";
15
15
  /**
16
16
  * Extract the gene name from a trace entry's reason string.
@@ -19,12 +19,11 @@ import type { ExecutionOutcome } from "./types.js";
19
19
  export declare function extractGeneFromReason(reason: string | undefined): string | null;
20
20
  /**
21
21
  * Convert trace entries into ExecutionOutcomes for the evolution engine.
22
- *
23
- * Groups block/warn traces by gene, creates one outcome per unique
24
- * (gene, decision) pair. Allow decisions are not converted since
25
- * they don't carry gene information.
22
+ * Backward-compatible wrapper over the normalized signals path.
26
23
  */
27
24
  export declare function traceToOutcomes(traces: TraceEntry[], dnaId?: string): ExecutionOutcome[];
25
+ export declare function verifierResultsToOutcomes(results: VerifierResultEntry[], dnaId?: string): ExecutionOutcome[];
26
+ export declare function mergeOutcomes(traces: TraceEntry[], verifierResults: VerifierResultEntry[], dnaId?: string): ExecutionOutcome[];
28
27
  /**
29
28
  * Summarize trace-to-outcome conversion for human review.
30
29
  */
@@ -10,7 +10,7 @@
10
10
  * - Role scope: "Role '<name>'" → gene "scope:<role>"
11
11
  * - Gates: "(gene: <name>)" pattern or fallback to "gate"
12
12
  */
13
- import { randomUUID } from "node:crypto";
13
+ import { signalsToOutcomes, traceToSignals, verifierResultsToSignals } from "../signals/index.js";
14
14
  /**
15
15
  * Extract the gene name from a trace entry's reason string.
16
16
  * Returns null if no gene can be determined.
@@ -19,69 +19,34 @@ export function extractGeneFromReason(reason) {
19
19
  if (!reason)
20
20
  return null;
21
21
  // Pattern: (gene: <name>) — from tool filters and gates
22
- const geneMatch = reason.match(/\(gene:\s*(\w+)\)/);
22
+ const geneMatch = reason.match(/\(gene:\s*([A-Za-z0-9:_-]+)\)/);
23
23
  if (geneMatch)
24
24
  return geneMatch[1];
25
25
  // Pattern: Role '<name>' — from scope enforcement
26
- const roleMatch = reason.match(/Role '(\w+)'/);
26
+ const roleMatch = reason.match(/Role '([A-Za-z0-9:_-]+)'/);
27
27
  if (roleMatch)
28
28
  return `scope:${roleMatch[1]}`;
29
29
  // Pattern: source_gene in gate messages
30
- const sourceMatch = reason.match(/source_gene:\s*(\w+)/);
30
+ const sourceMatch = reason.match(/source_gene:\s*([A-Za-z0-9:_-]+)/);
31
31
  if (sourceMatch)
32
32
  return sourceMatch[1];
33
33
  return null;
34
34
  }
35
35
  /**
36
36
  * Convert trace entries into ExecutionOutcomes for the evolution engine.
37
- *
38
- * Groups block/warn traces by gene, creates one outcome per unique
39
- * (gene, decision) pair. Allow decisions are not converted since
40
- * they don't carry gene information.
37
+ * Backward-compatible wrapper over the normalized signals path.
41
38
  */
42
39
  export function traceToOutcomes(traces, dnaId = "default") {
43
- // Collect feedback per gene from block/warn traces
44
- const geneFeedback = new Map();
45
- for (const trace of traces) {
46
- if (trace.decision === "allow")
47
- continue;
48
- const gene = extractGeneFromReason(trace.reason);
49
- if (!gene)
50
- continue;
51
- const existing = geneFeedback.get(gene) ?? { hindered: 0, details: [] };
52
- existing.hindered++;
53
- if (trace.reason && existing.details.length < 3) {
54
- const short = trace.reason.split("\n")[0].slice(0, 80);
55
- existing.details.push(short);
56
- }
57
- geneFeedback.set(gene, existing);
58
- }
59
- // Convert to ExecutionOutcomes
60
- const outcomes = [];
61
- const totalEvents = traces.length;
62
- const timestamp = new Date().toISOString();
63
- for (const [gene, stats] of geneFeedback) {
64
- const feedback = [];
65
- // Each block counts as a "hindered" feedback
66
- for (let i = 0; i < stats.hindered; i++) {
67
- feedback.push({
68
- gene,
69
- effect: "hindered",
70
- detail: stats.details[i] ?? `blocked ${stats.hindered} times`,
71
- });
72
- }
73
- outcomes.push({
74
- id: `trace_${randomUUID().slice(0, 8)}`,
75
- timestamp,
76
- dna_id: dnaId,
77
- action: `enforcement: ${gene} blocked ${stats.hindered}/${totalEvents} events`,
78
- active_genes: [gene],
79
- constraints_applied: [gene],
80
- success: false, // Blocks indicate friction
81
- constraint_feedback: feedback,
82
- });
83
- }
84
- return outcomes;
40
+ return signalsToOutcomes(traceToSignals(traces), dnaId);
41
+ }
42
+ export function verifierResultsToOutcomes(results, dnaId = "default") {
43
+ return signalsToOutcomes(verifierResultsToSignals(results), dnaId);
44
+ }
45
+ export function mergeOutcomes(traces, verifierResults, dnaId = "default") {
46
+ return signalsToOutcomes([
47
+ ...traceToSignals(traces),
48
+ ...verifierResultsToSignals(verifierResults),
49
+ ], dnaId);
85
50
  }
86
51
  /**
87
52
  * Summarize trace-to-outcome conversion for human review.
@@ -89,11 +54,22 @@ export function traceToOutcomes(traces, dnaId = "default") {
89
54
  export function summarizeConversion(outcomes) {
90
55
  if (outcomes.length === 0)
91
56
  return "No actionable gene feedback found in traces.";
92
- const lines = ["Trace → Epigenetic conversion:"];
93
- for (const o of outcomes) {
94
- const gene = o.active_genes[0];
95
- const count = o.constraint_feedback.length;
96
- lines.push(` ${gene}: ${count} block${count > 1 ? "s" : ""} → will suppress`);
57
+ const lines = ["Signals → Epigenetic conversion:"];
58
+ for (const outcome of outcomes) {
59
+ const gene = outcome.active_genes[0];
60
+ const helped = outcome.constraint_feedback.filter((item) => item.effect === "helped").length;
61
+ const hindered = outcome.constraint_feedback.filter((item) => item.effect === "hindered").length;
62
+ const neutral = outcome.constraint_feedback.filter((item) => item.effect === "neutral").length;
63
+ if (hindered > 0 && helped === 0 && neutral === 0) {
64
+ lines.push(` ${gene}: ${hindered} block${hindered > 1 ? "s" : ""} → will suppress`);
65
+ continue;
66
+ }
67
+ if (helped > 0 && hindered === 0 && neutral === 0) {
68
+ lines.push(` ${gene}: ${helped} success${helped > 1 ? "es" : ""} → will amplify`);
69
+ continue;
70
+ }
71
+ const direction = outcome.success ? "amplify" : "suppress";
72
+ lines.push(` ${gene}: ${helped} positive, ${hindered} negative, ${neutral} neutral → net ${direction}`);
97
73
  }
98
74
  return lines.join("\n");
99
75
  }
@@ -14,9 +14,11 @@
14
14
  *
15
15
  * Fail-open: all errors → { continue: true, suppressOutput: true }
16
16
  */
17
- import type { ConstraintIR } from "../schema/types.js";
17
+ import type { ConstraintIR, VerifierSpec } from "../schema/types.js";
18
+ import type { HookOutput } from "./protocol.js";
18
19
  import { blockOutput } from "./protocol.js";
19
20
  import { readWorkflowState } from "./state.js";
21
+ import type { VerifierResultEntry } from "./state.js";
20
22
  export interface SessionSummary {
21
23
  total: number;
22
24
  blocks: number;
@@ -37,6 +39,21 @@ export declare function computeSummary(traces: Array<{
37
39
  reason?: string;
38
40
  }>): SessionSummary;
39
41
  export declare function formatSummary(s: SessionSummary): string | null;
42
+ export declare function appendStopVerifierWarnings(output: HookOutput, verifierResults: VerifierResultEntry[], currentStep?: string): HookOutput;
43
+ export declare function runVerifiersForTest(projectDir: string, ir: ConstraintIR, workflowState: {
44
+ workflow: string;
45
+ current_step: string;
46
+ current_role: string;
47
+ }, when: VerifierSpec["when"], sessionId?: string, options?: {
48
+ commandTimeoutMs?: number;
49
+ }): Promise<VerifierResultEntry[]>;
50
+ export declare function runStopVerifiersForTest(projectDir: string, ir: ConstraintIR, workflowState: {
51
+ workflow: string;
52
+ current_step: string;
53
+ current_role: string;
54
+ }, sessionId?: string, options?: {
55
+ commandTimeoutMs?: number;
56
+ }): Promise<VerifierResultEntry[]>;
40
57
  /**
41
58
  * Evaluate PreToolUse gates in priority order:
42
59
  * 1. Workflow Boundary — block Skill() after workflow completed