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.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +72 -92
- package/dist/audit/index.d.ts +12 -3
- package/dist/cli/commands/feedback.d.ts +3 -0
- package/dist/cli/commands/feedback.js +33 -11
- package/dist/cli/commands/run.js +1 -1
- package/dist/cli/commands/sync.js +5 -5
- package/dist/compiler/cascade.d.ts +3 -1
- package/dist/compiler/cascade.js +86 -2
- package/dist/compiler/compile.js +93 -3
- package/dist/compiler/workflow.d.ts +1 -0
- package/dist/compiler/workflow.js +1 -0
- package/dist/evolution/trace-bridge.d.ts +4 -5
- package/dist/evolution/trace-bridge.js +31 -55
- package/dist/hooks/cli.d.ts +18 -1
- package/dist/hooks/cli.js +624 -24
- package/dist/hooks/enforce.js +3 -2
- package/dist/hooks/state.d.ts +20 -1
- package/dist/hooks/state.js +55 -0
- package/dist/runtime/markdown.d.ts +1 -1
- package/dist/runtime/markdown.js +149 -0
- package/dist/runtime/settings-adapter.d.ts +3 -2
- package/dist/runtime/settings-adapter.js +26 -4
- package/dist/runtime/skill-adapter.js +49 -23
- package/dist/schema/types.d.ts +39 -0
- package/dist/schema/validate.js +143 -0
- package/dist/signals/index.d.ts +45 -0
- package/dist/signals/index.js +117 -0
- package/dist/templates/code-review-pipeline.dna.yaml +4 -0
- package/dist/templates/flutter-rewrite.dna.yaml +139 -212
- package/dist/templates/full-pipeline.dna.yaml +4 -0
- package/dist/templates/mobile-dev.dna.yaml +5 -0
- package/hooks/hooks.json +1 -1
- package/package.json +1 -1
- package/spec/control-plane-convergence-handoff-2026-04-24.md +432 -0
package/dist/schema/validate.js
CHANGED
|
@@ -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
|
+
}
|