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
@@ -7,6 +7,70 @@
7
7
  * - Codon values in valid ranges
8
8
  * - No circular inheritance
9
9
  */
10
+ function isLegibilityAssetArray(value) {
11
+ return Array.isArray(value) && value.every((item) => typeof item === "object" && item !== null &&
12
+ typeof item.type === "string" &&
13
+ typeof item.path === "string");
14
+ }
15
+ function validateLegibilityAssets(dna, roleNamesSet, errors, warnings) {
16
+ const la = dna.legibility_assets;
17
+ if (!la)
18
+ return;
19
+ const validateAssetList = (assets, path) => {
20
+ for (let i = 0; i < assets.length; i++) {
21
+ const asset = assets[i];
22
+ const itemPath = `${path}[${i}]`;
23
+ if (!["required_read", "repo_map", "manifest", "artifact_index"].includes(asset.type)) {
24
+ errors.push({ path: `${itemPath}.type`, message: `unknown legibility asset type '${asset.type}'` });
25
+ }
26
+ if (!asset.path) {
27
+ errors.push({ path: `${itemPath}.path`, message: "legibility asset path cannot be empty" });
28
+ }
29
+ if (asset.tags !== undefined) {
30
+ if (!Array.isArray(asset.tags) || asset.tags.some((tag) => typeof tag !== "string" || !tag)) {
31
+ errors.push({ path: `${itemPath}.tags`, message: "tags must be an array of non-empty strings" });
32
+ }
33
+ }
34
+ }
35
+ };
36
+ if (la.mandatory) {
37
+ if (!isLegibilityAssetArray(la.mandatory)) {
38
+ errors.push({ path: "legibility_assets.mandatory", message: "must be an array of legibility assets" });
39
+ }
40
+ else {
41
+ validateAssetList(la.mandatory, "legibility_assets.mandatory");
42
+ }
43
+ }
44
+ if (la.per_role) {
45
+ for (const [roleName, assets] of Object.entries(la.per_role)) {
46
+ if (roleNamesSet.size > 0 && !roleNamesSet.has(roleName)) {
47
+ warnings.push({ path: `legibility_assets.per_role.${roleName}`, message: `references unknown role '${roleName}'` });
48
+ }
49
+ if (!isLegibilityAssetArray(assets)) {
50
+ errors.push({ path: `legibility_assets.per_role.${roleName}`, message: "must be an array of legibility assets" });
51
+ }
52
+ else {
53
+ validateAssetList(assets, `legibility_assets.per_role.${roleName}`);
54
+ }
55
+ }
56
+ }
57
+ if (la.per_workflow) {
58
+ const wfNames = new Set(Object.keys(dna.workflows ?? {}));
59
+ if (dna.workflow)
60
+ wfNames.add(dna.workflow.name);
61
+ for (const [wfName, assets] of Object.entries(la.per_workflow)) {
62
+ if (wfNames.size > 0 && !wfNames.has(wfName)) {
63
+ warnings.push({ path: `legibility_assets.per_workflow.${wfName}`, message: `references unknown workflow '${wfName}'` });
64
+ }
65
+ if (!isLegibilityAssetArray(assets)) {
66
+ errors.push({ path: `legibility_assets.per_workflow.${wfName}`, message: "must be an array of legibility assets" });
67
+ }
68
+ else {
69
+ validateAssetList(assets, `legibility_assets.per_workflow.${wfName}`);
70
+ }
71
+ }
72
+ }
73
+ }
10
74
  function validateCodon(codon, path) {
11
75
  const errors = [];
12
76
  switch (codon.type) {
@@ -186,6 +250,55 @@ function validateRole(name, role, geneNames) {
186
250
  }
187
251
  return errors;
188
252
  }
253
+ function getCompletionCheckFields(check) {
254
+ if (typeof check !== "object" || check === null || Array.isArray(check))
255
+ return [];
256
+ const fields = [];
257
+ if (check.file_exists !== undefined)
258
+ fields.push("file_exists");
259
+ if (check.file_not_empty !== undefined)
260
+ fields.push("file_not_empty");
261
+ if (check.file_contains !== undefined)
262
+ fields.push("file_contains");
263
+ if (check.command_success !== undefined)
264
+ fields.push("command_success");
265
+ return fields;
266
+ }
267
+ function validateCompletionCheck(check, path) {
268
+ const errors = [];
269
+ if (typeof check !== "object" || check === null || Array.isArray(check)) {
270
+ errors.push({ path, message: "completion check must be an object" });
271
+ return errors;
272
+ }
273
+ const fields = getCompletionCheckFields(check);
274
+ if (fields.length !== 1) {
275
+ errors.push({ path, message: "completion check must define exactly one of: file_exists, file_not_empty, file_contains, command_success" });
276
+ return errors;
277
+ }
278
+ if (check.file_exists !== undefined && (typeof check.file_exists !== "string" || !check.file_exists)) {
279
+ errors.push({ path: `${path}.file_exists`, message: "must be a non-empty string" });
280
+ }
281
+ if (check.file_not_empty !== undefined && (typeof check.file_not_empty !== "string" || !check.file_not_empty)) {
282
+ errors.push({ path: `${path}.file_not_empty`, message: "must be a non-empty string" });
283
+ }
284
+ if (check.command_success !== undefined && (typeof check.command_success !== "string" || !check.command_success)) {
285
+ errors.push({ path: `${path}.command_success`, message: "must be a non-empty string" });
286
+ }
287
+ if (check.file_contains !== undefined) {
288
+ if (typeof check.file_contains !== "object" || check.file_contains === null) {
289
+ errors.push({ path: `${path}.file_contains`, message: "must define path and pattern" });
290
+ }
291
+ else {
292
+ if (typeof check.file_contains.path !== "string" || !check.file_contains.path) {
293
+ errors.push({ path: `${path}.file_contains.path`, message: "must be a non-empty string" });
294
+ }
295
+ if (typeof check.file_contains.pattern !== "string" || !check.file_contains.pattern) {
296
+ errors.push({ path: `${path}.file_contains.pattern`, message: "must be a non-empty string" });
297
+ }
298
+ }
299
+ }
300
+ return errors;
301
+ }
189
302
  function validateWorkflow(workflow, roleNames, pathPrefix) {
190
303
  const errors = [];
191
304
  const path = pathPrefix ?? "workflow";
@@ -259,6 +372,16 @@ function validateWorkflow(workflow, roleNames, pathPrefix) {
259
372
  });
260
373
  }
261
374
  }
375
+ if (step.completion !== undefined) {
376
+ if (!Array.isArray(step.completion)) {
377
+ errors.push({ path: `${stepPath}.completion`, message: "completion must be an array" });
378
+ }
379
+ else {
380
+ for (let j = 0; j < step.completion.length; j++) {
381
+ errors.push(...validateCompletionCheck(step.completion[j], `${stepPath}.completion[${j}]`));
382
+ }
383
+ }
384
+ }
262
385
  // checkpoints validation
263
386
  if (step.checkpoints) {
264
387
  if (!Array.isArray(step.checkpoints)) {
@@ -478,6 +601,7 @@ export function validateDNA(dna) {
478
601
  }
479
602
  }
480
603
  }
604
+ validateLegibilityAssets(dna, roleNamesSet, errors, warnings);
481
605
  // Validate context_files
482
606
  if (dna.context_files) {
483
607
  const cf = dna.context_files;
@@ -505,6 +629,25 @@ export function validateDNA(dna) {
505
629
  }
506
630
  }
507
631
  }
632
+ // Validate verifier_policy
633
+ if (dna.verifier_policy) {
634
+ const { allow_commands, allow_command_prefixes, allow_builtin_asserts } = dna.verifier_policy;
635
+ if (allow_commands !== undefined) {
636
+ if (!Array.isArray(allow_commands) || allow_commands.some((value) => typeof value !== "string" || !value)) {
637
+ errors.push({ path: "verifier_policy.allow_commands", message: "must be an array of non-empty strings" });
638
+ }
639
+ }
640
+ if (allow_command_prefixes !== undefined) {
641
+ if (!Array.isArray(allow_command_prefixes) || allow_command_prefixes.some((value) => typeof value !== "string" || !value)) {
642
+ errors.push({ path: "verifier_policy.allow_command_prefixes", message: "must be an array of non-empty strings" });
643
+ }
644
+ }
645
+ if (allow_builtin_asserts !== undefined) {
646
+ if (!Array.isArray(allow_builtin_asserts) || allow_builtin_asserts.some((value) => typeof value !== "string" || !value)) {
647
+ errors.push({ path: "verifier_policy.allow_builtin_asserts", message: "must be an array of non-empty strings" });
648
+ }
649
+ }
650
+ }
508
651
  // Validate epigenetic markers
509
652
  for (let i = 0; i < (dna.epigenetic?.markers?.length ?? 0); i++) {
510
653
  const marker = dna.epigenetic.markers[i];
@@ -0,0 +1,45 @@
1
+ import type { TraceEntry, VerifierResultEntry } from "../hooks/state.js";
2
+ import type { ExecutionOutcome } from "../evolution/types.js";
3
+ export type SignalKind = "trace" | "verifier" | "external";
4
+ export type SignalStatus = "positive" | "negative" | "neutral";
5
+ export interface SignalEvidence {
6
+ target?: string;
7
+ evidence?: string;
8
+ exit_code?: number;
9
+ artifact?: string;
10
+ message?: string;
11
+ }
12
+ export interface SignalEnvelope {
13
+ id: string;
14
+ kind: SignalKind;
15
+ status: SignalStatus;
16
+ timestamp: string;
17
+ gene?: string;
18
+ source: string;
19
+ detail?: string;
20
+ adapter?: string;
21
+ evidence_payload?: SignalEvidence;
22
+ }
23
+ export interface ExternalSignalInput {
24
+ id?: string;
25
+ status: SignalStatus;
26
+ timestamp?: string;
27
+ gene?: string;
28
+ source: string;
29
+ detail?: string;
30
+ target?: string;
31
+ evidence?: string;
32
+ exit_code?: number;
33
+ artifact?: string;
34
+ message?: string;
35
+ }
36
+ export interface SignalSourceAdapter<T = unknown> {
37
+ name: string;
38
+ adapt(payload: T): SignalEnvelope[];
39
+ }
40
+ export declare const newapiSignalAdapter: SignalSourceAdapter<ExternalSignalInput | ExternalSignalInput[]>;
41
+ export declare const gatewaySignalAdapter: SignalSourceAdapter<ExternalSignalInput | ExternalSignalInput[]>;
42
+ export declare function adaptSignals<T>(adapter: SignalSourceAdapter<T>, payload: T): SignalEnvelope[];
43
+ export declare function traceToSignals(traces: TraceEntry[]): SignalEnvelope[];
44
+ export declare function verifierResultsToSignals(results: VerifierResultEntry[]): SignalEnvelope[];
45
+ export declare function signalsToOutcomes(signals: SignalEnvelope[], dnaId?: string): ExecutionOutcome[];
@@ -0,0 +1,117 @@
1
+ import { extractGeneFromReason } from "../evolution/trace-bridge.js";
2
+ function toSignalEvidence(result) {
3
+ const payload = {
4
+ target: result.target,
5
+ evidence: result.evidence,
6
+ exit_code: result.exit_code,
7
+ artifact: result.artifact,
8
+ message: result.message,
9
+ };
10
+ return Object.values(payload).some((value) => value !== undefined) ? payload : undefined;
11
+ }
12
+ function normalizeExternalSignals(adapterName, payload) {
13
+ const items = Array.isArray(payload) ? payload : [payload];
14
+ return items.map((item, index) => {
15
+ const timestamp = item.timestamp ?? new Date().toISOString();
16
+ return {
17
+ id: item.id ?? `${adapterName}:${item.source}:${timestamp}:${index}`,
18
+ kind: "external",
19
+ status: item.status,
20
+ timestamp,
21
+ gene: item.gene,
22
+ source: item.source,
23
+ detail: item.detail ?? item.message ?? item.evidence,
24
+ adapter: adapterName,
25
+ evidence_payload: toSignalEvidence(item),
26
+ };
27
+ });
28
+ }
29
+ export const newapiSignalAdapter = {
30
+ name: "newapi",
31
+ adapt(payload) {
32
+ return normalizeExternalSignals("newapi", payload);
33
+ },
34
+ };
35
+ export const gatewaySignalAdapter = {
36
+ name: "gateway",
37
+ adapt(payload) {
38
+ return normalizeExternalSignals("gateway", payload);
39
+ },
40
+ };
41
+ export function adaptSignals(adapter, payload) {
42
+ return adapter.adapt(payload);
43
+ }
44
+ export function traceToSignals(traces) {
45
+ const signals = [];
46
+ for (const trace of traces) {
47
+ const gene = extractGeneFromReason(trace.reason);
48
+ if (!gene)
49
+ continue;
50
+ const status = trace.decision === "allow" ? "positive" : trace.decision === "warn" ? "neutral" : "negative";
51
+ signals.push({
52
+ id: trace.trace_id,
53
+ kind: "trace",
54
+ status,
55
+ timestamp: trace.timestamp,
56
+ gene,
57
+ source: trace.event,
58
+ detail: trace.reason,
59
+ });
60
+ }
61
+ return signals;
62
+ }
63
+ export function verifierResultsToSignals(results) {
64
+ const signals = [];
65
+ for (const result of results) {
66
+ const genes = result.source_genes && result.source_genes.length > 0 ? result.source_genes : [undefined];
67
+ const status = result.status === "pass" ? "positive" : result.severity === "warn" ? "neutral" : "negative";
68
+ for (const gene of genes) {
69
+ signals.push({
70
+ id: `${result.verifier_id}:${gene ?? "unknown"}:${result.timestamp}`,
71
+ kind: "verifier",
72
+ status,
73
+ timestamp: result.timestamp,
74
+ gene,
75
+ source: result.verifier_id,
76
+ detail: result.message ?? result.evidence,
77
+ evidence_payload: toSignalEvidence(result),
78
+ });
79
+ }
80
+ }
81
+ return signals;
82
+ }
83
+ export function signalsToOutcomes(signals, dnaId = "default") {
84
+ const feedbackByGene = new Map();
85
+ for (const signal of signals) {
86
+ if (!signal.gene)
87
+ continue;
88
+ const effect = signal.status === "positive"
89
+ ? "helped"
90
+ : signal.status === "negative"
91
+ ? "hindered"
92
+ : "neutral";
93
+ const existing = feedbackByGene.get(signal.gene) ?? [];
94
+ existing.push({
95
+ gene: signal.gene,
96
+ effect,
97
+ detail: signal.detail ?? signal.evidence_payload?.message ?? signal.source,
98
+ });
99
+ feedbackByGene.set(signal.gene, existing);
100
+ }
101
+ const outcomes = [];
102
+ for (const [gene, feedback] of feedbackByGene) {
103
+ const hindered = feedback.filter((item) => item.effect === "hindered").length;
104
+ const helped = feedback.filter((item) => item.effect === "helped").length;
105
+ outcomes.push({
106
+ id: `signal_${gene}_${feedback.length}`,
107
+ timestamp: new Date().toISOString(),
108
+ dna_id: dnaId,
109
+ action: `signals:${gene} helped=${helped} hindered=${hindered}`,
110
+ active_genes: [gene],
111
+ constraints_applied: [gene],
112
+ success: helped >= hindered,
113
+ constraint_feedback: feedback,
114
+ });
115
+ }
116
+ return outcomes;
117
+ }
@@ -71,6 +71,10 @@ roles:
71
71
 
72
72
  contexts: {}
73
73
 
74
+ verifier_policy:
75
+ allow_command_prefixes:
76
+ - npm test
77
+ - npx tsc --noEmit
74
78
 
75
79
  workflow:
76
80
  name: review-cycle