intentdna 1.6.5 → 1.7.4
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 +26 -15
- package/dist/cli/commands/compile.js +2 -47
- package/dist/cli/commands/context.d.ts +8 -0
- package/dist/cli/commands/context.js +63 -0
- package/dist/cli/commands/feedback.d.ts +3 -2
- package/dist/cli/commands/feedback.js +16 -5
- package/dist/cli/commands/init.js +11 -63
- package/dist/cli/commands/run.js +5 -4
- package/dist/cli/commands/show.js +2 -38
- package/dist/cli/commands/sync.d.ts +9 -6
- package/dist/cli/commands/sync.js +191 -186
- package/dist/cli/commands/templates.d.ts +10 -1
- package/dist/cli/commands/templates.js +50 -1
- package/dist/cli/commands/validate.js +15 -9
- package/dist/cli/commands/verify.d.ts +32 -0
- package/dist/cli/commands/verify.js +270 -31
- package/dist/cli/index.js +78 -11
- package/dist/compiler/activate.js +11 -6
- package/dist/compiler/cascade.d.ts +5 -1
- package/dist/compiler/cascade.js +74 -1
- package/dist/compiler/compile.js +38 -0
- package/dist/compiler/diagnostics.d.ts +17 -0
- package/dist/compiler/diagnostics.js +30 -0
- package/dist/compiler/index.d.ts +7 -0
- package/dist/compiler/index.js +13 -13
- package/dist/compiler/input-resolver.d.ts +32 -0
- package/dist/compiler/input-resolver.js +281 -0
- package/dist/compiler/provenance.d.ts +8 -0
- package/dist/compiler/provenance.js +127 -0
- package/dist/governance/index.d.ts +4 -3
- package/dist/governance/index.js +3 -4
- package/dist/governance/runtime-decision-event.d.ts +85 -0
- package/dist/governance/runtime-decision-event.js +231 -0
- package/dist/governance/types.d.ts +3 -3
- package/dist/governance/types.js +3 -3
- package/dist/hooks/cli.d.ts +10 -1
- package/dist/hooks/cli.js +199 -35
- package/dist/hooks/state.d.ts +5 -10
- package/dist/hooks/state.js +170 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/mcp/index.js +2 -0
- package/dist/mcp/tools-compile.js +18 -49
- package/dist/mcp/tools-context.d.ts +2 -0
- package/dist/mcp/tools-context.js +85 -0
- package/dist/mcp/tools-enforce.d.ts +2 -2
- package/dist/mcp/tools-enforce.js +19 -49
- package/dist/mcp/tools-observability.js +26 -2
- package/dist/mcp/tools-state.d.ts +1 -1
- package/dist/mcp/tools-state.js +17 -7
- package/dist/report/kernel-report.d.ts +27 -0
- package/dist/report/kernel-report.js +60 -19
- package/dist/report/kernel-signals.d.ts +5 -2
- package/dist/report/kernel-signals.js +87 -2
- package/dist/report/report-package.d.ts +64 -0
- package/dist/report/report-package.js +90 -0
- package/dist/runtime/agent-md.d.ts +1 -0
- package/dist/runtime/agent-md.js +21 -3
- package/dist/runtime/claude-sdk.d.ts +9 -4
- package/dist/runtime/claude-sdk.js +9 -0
- package/dist/runtime/context-sources.d.ts +14 -0
- package/dist/runtime/context-sources.js +60 -0
- package/dist/runtime/plugin-adapter.d.ts +5 -1
- package/dist/runtime/plugin-adapter.js +2 -0
- package/dist/runtime/skill-adapter.d.ts +32 -4
- package/dist/runtime/skill-adapter.js +184 -9
- package/dist/runtime/workflow-runner.d.ts +1 -1
- package/dist/runtime/workflow-runner.js +1 -1
- package/dist/schema/types.d.ts +84 -0
- package/dist/schema/validate.js +156 -2
- package/dist/schema/validators/controllers.js +16 -0
- package/dist/signals/index.d.ts +10 -0
- package/dist/signals/index.js +90 -5
- package/dist/templates/catalog.d.ts +19 -0
- package/dist/templates/catalog.js +57 -0
- package/dist/templates/flutter-rewrite.dna.yaml +2 -2
- package/package.json +1 -1
- package/spec/README.md +1 -1
- package/spec/foundation-hardening.md +2 -1
- package/spec/schema-spec.md +78 -1
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* post_execution_validators → PostToolUse hooks
|
|
9
9
|
* context_injections → UserPromptSubmit hook
|
|
10
10
|
*/
|
|
11
|
-
import type { ConstraintIR } from "../schema/types.js";
|
|
11
|
+
import type { CompiledProvenanceManifest, ConstraintIR, ConstraintProvenance } from "../schema/types.js";
|
|
12
12
|
export interface DNAHookOutput {
|
|
13
13
|
hookSpecificOutput?: {
|
|
14
14
|
hookEventName: string;
|
|
@@ -32,19 +32,24 @@ export interface ClaudeDNAConfig {
|
|
|
32
32
|
source_dna_ids: string[];
|
|
33
33
|
/** Compilation timestamp */
|
|
34
34
|
compiled_at: string;
|
|
35
|
+
/** Stable compiled IR hash for runtime decision events */
|
|
36
|
+
compiled_ir_hash?: string;
|
|
37
|
+
/** Cascade/source provenance for SDK-side checks */
|
|
38
|
+
provenance?: CompiledProvenanceManifest;
|
|
35
39
|
}
|
|
36
40
|
export interface PreToolRule {
|
|
37
41
|
condition: string;
|
|
38
42
|
action: "block" | "escalate" | "warn";
|
|
39
43
|
message: string;
|
|
40
44
|
source_gene: string;
|
|
45
|
+
constraint_provenance?: ConstraintProvenance;
|
|
46
|
+
compiled_ir_hash?: string;
|
|
41
47
|
}
|
|
42
48
|
export interface PostToolRule {
|
|
43
49
|
check: string;
|
|
44
50
|
threshold?: number;
|
|
45
51
|
source_gene: string;
|
|
52
|
+
constraint_provenance?: ConstraintProvenance;
|
|
53
|
+
compiled_ir_hash?: string;
|
|
46
54
|
}
|
|
47
|
-
/**
|
|
48
|
-
* Compile Constraint IR to Claude Agent SDK configuration.
|
|
49
|
-
*/
|
|
50
55
|
export declare function compileForClaude(ir: ConstraintIR): ClaudeDNAConfig;
|
|
@@ -12,6 +12,9 @@
|
|
|
12
12
|
/**
|
|
13
13
|
* Compile Constraint IR to Claude Agent SDK configuration.
|
|
14
14
|
*/
|
|
15
|
+
function provenanceForGene(ir, geneId) {
|
|
16
|
+
return ir.provenance?.constraint_winners.find((constraint) => constraint.gene_id === geneId);
|
|
17
|
+
}
|
|
15
18
|
export function compileForClaude(ir) {
|
|
16
19
|
// Build system prompt addition from directives
|
|
17
20
|
const promptLines = [];
|
|
@@ -61,11 +64,15 @@ export function compileForClaude(ir) {
|
|
|
61
64
|
action: g.action,
|
|
62
65
|
message: g.message,
|
|
63
66
|
source_gene: g.source_gene,
|
|
67
|
+
constraint_provenance: provenanceForGene(ir, g.source_gene),
|
|
68
|
+
compiled_ir_hash: ir.compiled_ir_hash,
|
|
64
69
|
})),
|
|
65
70
|
post_tool_rules: ir.post_execution_validators.map((v) => ({
|
|
66
71
|
check: v.check,
|
|
67
72
|
threshold: v.threshold,
|
|
68
73
|
source_gene: v.source_gene,
|
|
74
|
+
constraint_provenance: provenanceForGene(ir, v.source_gene),
|
|
75
|
+
compiled_ir_hash: ir.compiled_ir_hash,
|
|
69
76
|
})),
|
|
70
77
|
context_additions: ir.context_injections.map((c) => c.text),
|
|
71
78
|
deprioritized_tools: ir.tool_filters
|
|
@@ -73,5 +80,7 @@ export function compileForClaude(ir) {
|
|
|
73
80
|
.map((f) => f.target),
|
|
74
81
|
source_dna_ids: ir.source_dna_ids,
|
|
75
82
|
compiled_at: ir.compiled_at,
|
|
83
|
+
compiled_ir_hash: ir.compiled_ir_hash,
|
|
84
|
+
provenance: ir.provenance,
|
|
76
85
|
};
|
|
77
86
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { CompiledAdvisoryContextSource } from "../schema/types.js";
|
|
2
|
+
export interface ContextSourceDocument {
|
|
3
|
+
source: CompiledAdvisoryContextSource;
|
|
4
|
+
content?: string;
|
|
5
|
+
}
|
|
6
|
+
export interface ContextSourceIndex {
|
|
7
|
+
sources: CompiledAdvisoryContextSource[];
|
|
8
|
+
planning_instructions: string[];
|
|
9
|
+
}
|
|
10
|
+
export declare function loadContextSourceIndex(projectDir: string, files?: string[]): Promise<ContextSourceIndex>;
|
|
11
|
+
export declare function searchContextSources(index: ContextSourceIndex, query: string): CompiledAdvisoryContextSource[];
|
|
12
|
+
export declare function findContextSource(index: ContextSourceIndex, id: string): CompiledAdvisoryContextSource | undefined;
|
|
13
|
+
export declare function readContextSourceDocument(projectDir: string, source: CompiledAdvisoryContextSource): Promise<ContextSourceDocument>;
|
|
14
|
+
export declare function formatContextSource(source: CompiledAdvisoryContextSource): string;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { readFile, realpath } from "node:fs/promises";
|
|
2
|
+
import { isAbsolute, relative, resolve } from "node:path";
|
|
3
|
+
import { compileFromFiles, detectDNAConfigs, expandDNAInputFiles } from "../compiler/index.js";
|
|
4
|
+
export async function loadContextSourceIndex(projectDir, files) {
|
|
5
|
+
const inputFiles = files && files.length > 0
|
|
6
|
+
? files.map((file) => resolve(projectDir, file))
|
|
7
|
+
: await detectDNAConfigs(projectDir);
|
|
8
|
+
if (inputFiles.length === 0)
|
|
9
|
+
return { sources: [], planning_instructions: [] };
|
|
10
|
+
const expandedFiles = await expandDNAInputFiles(inputFiles);
|
|
11
|
+
const ir = await compileFromFiles(expandedFiles);
|
|
12
|
+
return {
|
|
13
|
+
sources: ir.context_sources ?? [],
|
|
14
|
+
planning_instructions: ir.planning_context?.instructions ?? [],
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export function searchContextSources(index, query) {
|
|
18
|
+
const normalized = query.trim().toLowerCase();
|
|
19
|
+
if (!normalized)
|
|
20
|
+
return index.sources;
|
|
21
|
+
return index.sources.filter((source) => {
|
|
22
|
+
const haystack = [
|
|
23
|
+
source.id,
|
|
24
|
+
source.type,
|
|
25
|
+
source.path,
|
|
26
|
+
source.url,
|
|
27
|
+
source.title,
|
|
28
|
+
source.description,
|
|
29
|
+
...(source.tags ?? []),
|
|
30
|
+
].filter(Boolean).join("\n").toLowerCase();
|
|
31
|
+
return haystack.includes(normalized);
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
export function findContextSource(index, id) {
|
|
35
|
+
return index.sources.find((source) => source.id === id);
|
|
36
|
+
}
|
|
37
|
+
export async function readContextSourceDocument(projectDir, source) {
|
|
38
|
+
if (source.type !== "file" && source.type !== "doc") {
|
|
39
|
+
return { source };
|
|
40
|
+
}
|
|
41
|
+
if (!source.path)
|
|
42
|
+
return { source };
|
|
43
|
+
if (isAbsolute(source.path) || source.path.split(/[\\/]+/).includes("..")) {
|
|
44
|
+
throw new Error(`Context source '${source.id}' path must stay within the project`);
|
|
45
|
+
}
|
|
46
|
+
const projectRoot = await realpath(projectDir);
|
|
47
|
+
const filePath = await realpath(resolve(projectRoot, source.path));
|
|
48
|
+
const rel = relative(projectRoot, filePath);
|
|
49
|
+
if (rel === "" || rel.startsWith("..") || isAbsolute(rel)) {
|
|
50
|
+
throw new Error(`Context source '${source.id}' path must stay within the project`);
|
|
51
|
+
}
|
|
52
|
+
const content = await readFile(filePath, "utf-8");
|
|
53
|
+
return { source, content };
|
|
54
|
+
}
|
|
55
|
+
export function formatContextSource(source) {
|
|
56
|
+
const locator = source.path ?? source.url ?? "(no locator)";
|
|
57
|
+
const title = source.title ? ` — ${source.title}` : "";
|
|
58
|
+
const tags = source.tags?.length ? ` [${source.tags.join(", ")}]` : "";
|
|
59
|
+
return `${source.id}: ${source.type} ${locator}${title}${tags}`;
|
|
60
|
+
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* This is the bridge between compile-time (dna sync) and runtime (hook execution).
|
|
8
8
|
*/
|
|
9
|
-
import type { ConstraintIR, RoleDef } from "../schema/types.js";
|
|
9
|
+
import type { CompiledProvenanceManifest, ConstraintIR, RoleDef } from "../schema/types.js";
|
|
10
10
|
/** Wrapper around ConstraintIR with versioning metadata */
|
|
11
11
|
export interface CompiledIRFile {
|
|
12
12
|
/** Schema version for forward compatibility */
|
|
@@ -19,6 +19,10 @@ export interface CompiledIRFile {
|
|
|
19
19
|
ir: ConstraintIR;
|
|
20
20
|
/** Cascaded role definitions (for output_schema enforcement) */
|
|
21
21
|
roles?: Record<string, RoleDef>;
|
|
22
|
+
/** Stable hash of the compiled IR payload, excluding provenance/hash fields */
|
|
23
|
+
compiled_ir_hash?: string;
|
|
24
|
+
/** Cascade and source provenance used by enterprise evidence */
|
|
25
|
+
provenance?: CompiledProvenanceManifest;
|
|
22
26
|
}
|
|
23
27
|
/**
|
|
24
28
|
* Create a CompiledIRFile wrapper around a ConstraintIR.
|
|
@@ -7,17 +7,45 @@
|
|
|
7
7
|
* Skills are compiled views of DNA's structured definitions — not stored content.
|
|
8
8
|
*/
|
|
9
9
|
import type { WorkflowPlan, WorkflowDef, RoleDef, ConstraintIR, ControllerDef } from "../schema/types.js";
|
|
10
|
+
export interface SkillFile {
|
|
11
|
+
relativePath: string;
|
|
12
|
+
content: string;
|
|
13
|
+
}
|
|
14
|
+
export interface SkillMapEntry {
|
|
15
|
+
name: string;
|
|
16
|
+
dirName: string;
|
|
17
|
+
source: string;
|
|
18
|
+
kind: "workflow" | "controller";
|
|
19
|
+
bodyPath: string;
|
|
20
|
+
roles: string[];
|
|
21
|
+
consumes: Array<{
|
|
22
|
+
step_id: string;
|
|
23
|
+
type: string;
|
|
24
|
+
path?: string;
|
|
25
|
+
name?: string;
|
|
26
|
+
artifact_id?: string;
|
|
27
|
+
description: string;
|
|
28
|
+
}>;
|
|
29
|
+
produces: Array<{
|
|
30
|
+
step_id: string;
|
|
31
|
+
type: string;
|
|
32
|
+
path?: string;
|
|
33
|
+
name?: string;
|
|
34
|
+
artifact_id?: string;
|
|
35
|
+
description: string;
|
|
36
|
+
}>;
|
|
37
|
+
}
|
|
10
38
|
export interface SkillResult {
|
|
11
39
|
name: string;
|
|
12
40
|
fileName: string;
|
|
13
41
|
dirName: string;
|
|
14
42
|
content: string;
|
|
43
|
+
launcherContent?: string;
|
|
44
|
+
bodyContent?: string;
|
|
45
|
+
bodyFileName?: string;
|
|
46
|
+
mapEntry?: SkillMapEntry;
|
|
15
47
|
}
|
|
16
48
|
export declare function compileControllerToSkill(controllerKey: string, controller: ControllerDef, variables?: Record<string, string>, workflows?: Record<string, WorkflowDef>): SkillResult;
|
|
17
|
-
/**
|
|
18
|
-
* Compile a WorkflowPlan + roles + IR into a SKILL.md file.
|
|
19
|
-
* Variables from DNA config are substituted into prompts and descriptions.
|
|
20
|
-
*/
|
|
21
49
|
export declare function compileWorkflowToSkill(plan: WorkflowPlan, roles: Record<string, RoleDef>, ir?: ConstraintIR, variables?: Record<string, string>): SkillResult;
|
|
22
50
|
export declare function writeSkillFiles(results: SkillResult[], outputDir: string): Promise<string[]>;
|
|
23
51
|
export declare function removeSkillFiles(outputDir: string): Promise<number>;
|
|
@@ -7,10 +7,18 @@
|
|
|
7
7
|
* Skills are compiled views of DNA's structured definitions — not stored content.
|
|
8
8
|
*/
|
|
9
9
|
import { mkdir, writeFile, readdir, readFile, rm } from "node:fs/promises";
|
|
10
|
-
import { join } from "node:path";
|
|
10
|
+
import { join, resolve, sep } from "node:path";
|
|
11
11
|
import { getControllerKindMetadata } from "../schema/controller-registry.js";
|
|
12
|
-
import { toKebabCase } from "./agent-md.js";
|
|
12
|
+
import { assertSafeGeneratedName, toKebabCase } from "./agent-md.js";
|
|
13
13
|
const SENTINEL = "<!-- intentdna:managed — do not edit manually -->";
|
|
14
|
+
function resolveContainedPath(baseDir, ...segments) {
|
|
15
|
+
const base = resolve(baseDir);
|
|
16
|
+
const target = resolve(baseDir, ...segments);
|
|
17
|
+
if (target !== base && !target.startsWith(`${base}${sep}`)) {
|
|
18
|
+
throw new Error(`generated path escapes output directory: ${target}`);
|
|
19
|
+
}
|
|
20
|
+
return target;
|
|
21
|
+
}
|
|
14
22
|
// ── Compile Controller → Skill ─────────────────────────────
|
|
15
23
|
export function compileControllerToSkill(controllerKey, controller, variables, workflows) {
|
|
16
24
|
const kindMetadata = getControllerKindMetadata(controller.kind);
|
|
@@ -18,6 +26,7 @@ export function compileControllerToSkill(controllerKey, controller, variables, w
|
|
|
18
26
|
throw new Error(`Unsupported controller kind: ${controller.kind}`);
|
|
19
27
|
}
|
|
20
28
|
const skillName = `dna-${toKebabCase(controllerKey)}`;
|
|
29
|
+
assertSafeGeneratedName(skillName, "skill name");
|
|
21
30
|
const analyzerAgent = `dna-${toKebabCase(controller.roles.analyzer)}`;
|
|
22
31
|
const analysisReviewerAgent = `dna-${toKebabCase(controller.roles.analysis_reviewer)}`;
|
|
23
32
|
const surgeonAgent = `dna-${toKebabCase(controller.roles.surgeon)}`;
|
|
@@ -136,6 +145,7 @@ export function compileControllerToSkill(controllerKey, controller, variables, w
|
|
|
136
145
|
lines.push("</Verdict_Mapping>");
|
|
137
146
|
lines.push("");
|
|
138
147
|
lines.push("<Stop_Report>");
|
|
148
|
+
lines.push("Every critical decision in the stop report must cite at least one verifier result_id, trace_id, or ArtifactManifest artifact id; prose-only claims are not sufficient.");
|
|
139
149
|
for (const line of policy.stop_report) {
|
|
140
150
|
lines.push(line);
|
|
141
151
|
}
|
|
@@ -155,7 +165,28 @@ export function compileControllerToSkill(controllerKey, controller, variables, w
|
|
|
155
165
|
}
|
|
156
166
|
const dirName = skillName;
|
|
157
167
|
const fileName = join(dirName, "SKILL.md");
|
|
158
|
-
return { name: skillName, fileName, dirName, content }
|
|
168
|
+
return attachSkillBody({ name: skillName, fileName, dirName, content }, {
|
|
169
|
+
description: controller.description || controller.name,
|
|
170
|
+
triggers: [toKebabCase(controllerKey), `run ${toKebabCase(controllerKey)}`],
|
|
171
|
+
kind: "controller",
|
|
172
|
+
mapEntry: {
|
|
173
|
+
name: skillName,
|
|
174
|
+
dirName,
|
|
175
|
+
source: controllerKey,
|
|
176
|
+
kind: "controller",
|
|
177
|
+
bodyPath: join(dirName, "skill-bodies", "body.md"),
|
|
178
|
+
roles: Object.values(controller.roles),
|
|
179
|
+
consumes: [
|
|
180
|
+
{ step_id: "controller", type: "file", path: controller.diagnosis_artifact_path, description: "Diagnosis artifact" },
|
|
181
|
+
{ step_id: "controller", type: "file", path: controller.diagnosis_review_artifact_path, description: "Diagnosis review artifact" },
|
|
182
|
+
{ step_id: "controller", type: "file", path: controller.review_artifact_path, description: "Fix review artifact" },
|
|
183
|
+
{ step_id: "controller", type: "file", path: controller.progress_artifact_path, description: "Progress JSON artifact" },
|
|
184
|
+
],
|
|
185
|
+
produces: [
|
|
186
|
+
{ step_id: "controller", type: "file", path: controller.progress_artifact_path, description: "Progress JSON artifact" },
|
|
187
|
+
],
|
|
188
|
+
},
|
|
189
|
+
});
|
|
159
190
|
}
|
|
160
191
|
function requireStepByRole(workflow, role, workflowName) {
|
|
161
192
|
const step = workflow?.steps.find((candidate) => candidate.role === role);
|
|
@@ -164,6 +195,73 @@ function requireStepByRole(workflow, role, workflowName) {
|
|
|
164
195
|
}
|
|
165
196
|
return step;
|
|
166
197
|
}
|
|
198
|
+
function createSkillLauncher(params) {
|
|
199
|
+
const lines = [];
|
|
200
|
+
lines.push("---");
|
|
201
|
+
lines.push(`name: ${params.skillName}`);
|
|
202
|
+
lines.push(`description: "Use when user says /${params.skillName}. ${escapeYaml(params.description)}"`);
|
|
203
|
+
lines.push("user-invocable: true");
|
|
204
|
+
lines.push("triggers:");
|
|
205
|
+
for (const trigger of params.triggers)
|
|
206
|
+
lines.push(` - "${escapeYaml(trigger)}"`);
|
|
207
|
+
lines.push("---");
|
|
208
|
+
lines.push("");
|
|
209
|
+
lines.push(SENTINEL);
|
|
210
|
+
lines.push(`<!-- Compiled: ${new Date().toISOString()} -->`);
|
|
211
|
+
lines.push("");
|
|
212
|
+
lines.push(`# /${params.skillName}`);
|
|
213
|
+
lines.push("");
|
|
214
|
+
lines.push("<Purpose>");
|
|
215
|
+
lines.push(params.description);
|
|
216
|
+
lines.push("</Purpose>");
|
|
217
|
+
lines.push("");
|
|
218
|
+
lines.push("<Skill_Body>");
|
|
219
|
+
lines.push(`Full ${params.kind} instructions live in \`skill-bodies/${params.bodyFileName}\`.`);
|
|
220
|
+
lines.push("Read that body before executing any step; the launcher is only an entrypoint.");
|
|
221
|
+
lines.push("</Skill_Body>");
|
|
222
|
+
lines.push("");
|
|
223
|
+
if (params.bodyContent.includes("<Required_Context>")) {
|
|
224
|
+
lines.push("<Required_Context>");
|
|
225
|
+
lines.push("The skill body contains required context files. Read them before any Edit, Write, or Bash action.");
|
|
226
|
+
lines.push("</Required_Context>");
|
|
227
|
+
lines.push("");
|
|
228
|
+
}
|
|
229
|
+
if (params.bodyContent.includes("<Advisory_Context>")) {
|
|
230
|
+
lines.push("<Advisory_Context>");
|
|
231
|
+
lines.push("The skill body contains planning-only advisory context sources. Quote and cite them when used; they never satisfy handoff or enforcement checks.");
|
|
232
|
+
lines.push("</Advisory_Context>");
|
|
233
|
+
lines.push("");
|
|
234
|
+
}
|
|
235
|
+
if (params.bodyContent.includes("<Handoff>")) {
|
|
236
|
+
lines.push("<Handoff>");
|
|
237
|
+
lines.push("Write human-readable handoff notes when requested, but do not treat handoff.md as a machine fact.");
|
|
238
|
+
lines.push("ArtifactManifest entries written by runtime hooks remain the machine source of truth for handoff satisfaction.");
|
|
239
|
+
lines.push("</Handoff>");
|
|
240
|
+
lines.push("");
|
|
241
|
+
}
|
|
242
|
+
lines.push("<Execution>");
|
|
243
|
+
lines.push(`Follow \`skill-bodies/${params.bodyFileName}\` exactly.`);
|
|
244
|
+
lines.push("</Execution>");
|
|
245
|
+
return lines.join("\n") + "\n";
|
|
246
|
+
}
|
|
247
|
+
function attachSkillBody(result, params) {
|
|
248
|
+
const bodyFileName = "body.md";
|
|
249
|
+
const launcherContent = createSkillLauncher({
|
|
250
|
+
skillName: result.name,
|
|
251
|
+
description: params.description,
|
|
252
|
+
triggers: params.triggers,
|
|
253
|
+
bodyFileName,
|
|
254
|
+
bodyContent: result.content,
|
|
255
|
+
kind: params.kind,
|
|
256
|
+
});
|
|
257
|
+
return {
|
|
258
|
+
...result,
|
|
259
|
+
launcherContent,
|
|
260
|
+
bodyContent: result.content,
|
|
261
|
+
bodyFileName,
|
|
262
|
+
mapEntry: params.mapEntry,
|
|
263
|
+
};
|
|
264
|
+
}
|
|
167
265
|
function normalizeControllerPolicy(controller) {
|
|
168
266
|
const defaults = defaultControllerPolicy(controller);
|
|
169
267
|
const policy = {
|
|
@@ -234,7 +332,7 @@ function defaultControllerPolicy(controller) {
|
|
|
234
332
|
"- MAX_ROUNDS: stop and report max rounds reached",
|
|
235
333
|
],
|
|
236
334
|
stop_report: [
|
|
237
|
-
"On REQUEST_CHANGES, BLOCKED, or MAX_ROUNDS, explain the stop reason, the evidence,
|
|
335
|
+
"On REQUEST_CHANGES, BLOCKED, or MAX_ROUNDS, explain the stop reason, the evidence, the next action, and cite at least one verifier result_id, trace_id, or ArtifactManifest artifact id.",
|
|
238
336
|
],
|
|
239
337
|
constraints: [
|
|
240
338
|
`The controller must schedule only from ${controller.progress_artifact_path}.`,
|
|
@@ -247,9 +345,46 @@ function defaultControllerPolicy(controller) {
|
|
|
247
345
|
* Compile a WorkflowPlan + roles + IR into a SKILL.md file.
|
|
248
346
|
* Variables from DNA config are substituted into prompts and descriptions.
|
|
249
347
|
*/
|
|
348
|
+
function workflowSkillMapEntry(skillName, dirName, plan) {
|
|
349
|
+
const consumes = [];
|
|
350
|
+
const produces = [];
|
|
351
|
+
for (const step of plan.steps) {
|
|
352
|
+
for (const artifact of step.handoff?.consumes ?? []) {
|
|
353
|
+
consumes.push({
|
|
354
|
+
step_id: step.id,
|
|
355
|
+
type: artifact.type,
|
|
356
|
+
path: artifact.path,
|
|
357
|
+
name: artifact.name,
|
|
358
|
+
artifact_id: artifact.artifact_id,
|
|
359
|
+
description: artifact.description,
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
for (const artifact of step.handoff?.produces ?? []) {
|
|
363
|
+
produces.push({
|
|
364
|
+
step_id: step.id,
|
|
365
|
+
type: artifact.type,
|
|
366
|
+
path: artifact.path,
|
|
367
|
+
name: artifact.name,
|
|
368
|
+
artifact_id: artifact.artifact_id,
|
|
369
|
+
description: artifact.description,
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
return {
|
|
374
|
+
name: skillName,
|
|
375
|
+
dirName,
|
|
376
|
+
source: plan.workflow_key ?? plan.source_workflow,
|
|
377
|
+
kind: "workflow",
|
|
378
|
+
bodyPath: join(dirName, "skill-bodies", "body.md"),
|
|
379
|
+
roles: [...new Set(plan.steps.map((step) => step.role))],
|
|
380
|
+
consumes,
|
|
381
|
+
produces,
|
|
382
|
+
};
|
|
383
|
+
}
|
|
250
384
|
export function compileWorkflowToSkill(plan, roles, ir, variables) {
|
|
251
385
|
const lines = [];
|
|
252
386
|
const skillName = `dna-${toKebabCase(plan.name)}`;
|
|
387
|
+
assertSafeGeneratedName(skillName, "skill name");
|
|
253
388
|
// Frontmatter
|
|
254
389
|
lines.push("---");
|
|
255
390
|
lines.push(`name: ${skillName}`);
|
|
@@ -341,6 +476,22 @@ export function compileWorkflowToSkill(plan, roles, ir, variables) {
|
|
|
341
476
|
lines.push("</Required_Context>");
|
|
342
477
|
lines.push("");
|
|
343
478
|
}
|
|
479
|
+
if (ir?.context_sources?.length) {
|
|
480
|
+
lines.push("<Advisory_Context>");
|
|
481
|
+
lines.push("These sources are planning-only references. They do not satisfy ArtifactManifest, handoff consumes, completion checks, or enforcement facts.");
|
|
482
|
+
lines.push("If you use them, quote the relevant passage and cite the source id.");
|
|
483
|
+
lines.push("");
|
|
484
|
+
for (const source of ir.context_sources) {
|
|
485
|
+
const locator = source.path ?? source.url ?? "(no locator)";
|
|
486
|
+
const title = source.title ? ` — ${source.title}` : "";
|
|
487
|
+
lines.push(`- ${source.id}: ${source.type} ${locator}${title}`);
|
|
488
|
+
}
|
|
489
|
+
for (const instruction of ir.planning_context?.instructions ?? []) {
|
|
490
|
+
lines.push(`- Planning instruction: ${instruction}`);
|
|
491
|
+
}
|
|
492
|
+
lines.push("</Advisory_Context>");
|
|
493
|
+
lines.push("");
|
|
494
|
+
}
|
|
344
495
|
// Steps — each step MUST be executed via Agent() tool call
|
|
345
496
|
const usedRoles = new Set(plan.steps.map((s) => s.role));
|
|
346
497
|
// Pre-compute handoff targets: steps that are pointed to by another step's handoff_to
|
|
@@ -489,6 +640,8 @@ export function compileWorkflowToSkill(plan, roles, ir, variables) {
|
|
|
489
640
|
const stepsWithHandoff = plan.steps.filter((s) => s.handoff && (s.handoff.consumes?.length || s.handoff.produces?.length));
|
|
490
641
|
if (stepsWithHandoff.length > 0) {
|
|
491
642
|
lines.push("<Handoff>");
|
|
643
|
+
lines.push("Human-readable handoff notes are useful for review, but they do not satisfy machine handoff gates.");
|
|
644
|
+
lines.push("ArtifactManifest records written by runtime hooks remain the machine source of truth for produced/consumed artifacts.");
|
|
492
645
|
for (const step of stepsWithHandoff) {
|
|
493
646
|
const h = step.handoff;
|
|
494
647
|
if (h.produces && h.produces.length > 0) {
|
|
@@ -532,7 +685,12 @@ export function compileWorkflowToSkill(plan, roles, ir, variables) {
|
|
|
532
685
|
}
|
|
533
686
|
const dirName = skillName;
|
|
534
687
|
const fileName = join(dirName, "SKILL.md");
|
|
535
|
-
return { name: skillName, fileName, dirName, content }
|
|
688
|
+
return attachSkillBody({ name: skillName, fileName, dirName, content }, {
|
|
689
|
+
description: plan.description || plan.name,
|
|
690
|
+
triggers: [plan.name, `run ${plan.name}`],
|
|
691
|
+
kind: "workflow",
|
|
692
|
+
mapEntry: workflowSkillMapEntry(skillName, dirName, plan),
|
|
693
|
+
});
|
|
536
694
|
}
|
|
537
695
|
/** Collect all {{var_name}} references from a workflow plan. */
|
|
538
696
|
function collectVariableRefs(plan) {
|
|
@@ -555,12 +713,29 @@ function collectVariableRefs(plan) {
|
|
|
555
713
|
// ── File Operations ────────────────────────────────────────
|
|
556
714
|
export async function writeSkillFiles(results, outputDir) {
|
|
557
715
|
const written = [];
|
|
716
|
+
const mapEntries = [];
|
|
558
717
|
for (const result of results) {
|
|
559
|
-
|
|
718
|
+
assertSafeGeneratedName(result.dirName, "skill directory");
|
|
719
|
+
const dir = resolveContainedPath(outputDir, result.dirName);
|
|
560
720
|
await mkdir(dir, { recursive: true });
|
|
561
|
-
const
|
|
562
|
-
await writeFile(
|
|
563
|
-
written.push(
|
|
721
|
+
const skillPath = resolveContainedPath(dir, "SKILL.md");
|
|
722
|
+
await writeFile(skillPath, result.launcherContent ?? result.content, "utf-8");
|
|
723
|
+
written.push(skillPath);
|
|
724
|
+
if (result.bodyContent && result.bodyFileName) {
|
|
725
|
+
assertSafeGeneratedName(result.bodyFileName.replace(/\.md$/, ""), "skill body file");
|
|
726
|
+
const bodyDir = resolveContainedPath(dir, "skill-bodies");
|
|
727
|
+
await mkdir(bodyDir, { recursive: true });
|
|
728
|
+
const bodyPath = resolveContainedPath(bodyDir, result.bodyFileName);
|
|
729
|
+
await writeFile(bodyPath, result.bodyContent, "utf-8");
|
|
730
|
+
written.push(bodyPath);
|
|
731
|
+
}
|
|
732
|
+
if (result.mapEntry)
|
|
733
|
+
mapEntries.push(result.mapEntry);
|
|
734
|
+
}
|
|
735
|
+
if (mapEntries.length > 0) {
|
|
736
|
+
const mapPath = resolveContainedPath(outputDir, ".intentdna-skill-map.json");
|
|
737
|
+
await writeFile(mapPath, JSON.stringify({ generated_at: new Date().toISOString(), skills: mapEntries }, null, 2) + "\n", "utf-8");
|
|
738
|
+
written.push(mapPath);
|
|
564
739
|
}
|
|
565
740
|
return written;
|
|
566
741
|
}
|
|
@@ -19,7 +19,7 @@ export interface WorkflowShellOptions {
|
|
|
19
19
|
maxBudgetPerAgent?: number;
|
|
20
20
|
/** Output format for agent results (default "json") */
|
|
21
21
|
outputFormat?: "json" | "text";
|
|
22
|
-
/** Permission mode (default "
|
|
22
|
+
/** Permission mode (default "default") */
|
|
23
23
|
permissionMode?: string;
|
|
24
24
|
/** Log directory relative to project root (default ".") */
|
|
25
25
|
logDir?: string;
|
|
@@ -466,7 +466,7 @@ export function compileWorkflowToShell(plan, options) {
|
|
|
466
466
|
agentPrefix: options?.agentPrefix ?? "dna-",
|
|
467
467
|
maxBudgetPerAgent: options?.maxBudgetPerAgent ?? 10,
|
|
468
468
|
outputFormat: options?.outputFormat ?? "json",
|
|
469
|
-
permissionMode: options?.permissionMode ?? "
|
|
469
|
+
permissionMode: options?.permissionMode ?? "default",
|
|
470
470
|
logDir: options?.logDir ?? ".",
|
|
471
471
|
pauseSupport: options?.pauseSupport ?? false,
|
|
472
472
|
};
|
package/dist/schema/types.d.ts
CHANGED
|
@@ -17,9 +17,16 @@ export interface RepelCodon {
|
|
|
17
17
|
type: "repel";
|
|
18
18
|
target: string;
|
|
19
19
|
}
|
|
20
|
+
export interface ConstraintOrigin {
|
|
21
|
+
source_dna_id: string;
|
|
22
|
+
source_layer: DNAType;
|
|
23
|
+
cascade_priority: number;
|
|
24
|
+
protected_threshold?: boolean;
|
|
25
|
+
}
|
|
20
26
|
export interface ThresholdCodon {
|
|
21
27
|
type: "threshold";
|
|
22
28
|
condition: string;
|
|
29
|
+
origin?: ConstraintOrigin;
|
|
23
30
|
}
|
|
24
31
|
export interface WeightCodon {
|
|
25
32
|
type: "weight";
|
|
@@ -262,6 +269,47 @@ export interface CascadeConfig {
|
|
|
262
269
|
inherits: string[];
|
|
263
270
|
priority: number;
|
|
264
271
|
}
|
|
272
|
+
export type ProvenanceCascadeLayerName = "species" | "enterprise" | "project" | "personal" | "role" | "context" | "task";
|
|
273
|
+
export type ProvenanceLayerStatus = "active" | "unknown" | "unsupported";
|
|
274
|
+
export type ProvenanceSourceTrust = "builtin" | "admin_shared" | "project_local" | "user_local" | "explicit_file" | "input";
|
|
275
|
+
export interface DNASourceProvenance {
|
|
276
|
+
source_id: string;
|
|
277
|
+
source_ref: string;
|
|
278
|
+
resolved_path: string;
|
|
279
|
+
fingerprint: string;
|
|
280
|
+
type: DNAType;
|
|
281
|
+
cascade_priority: number;
|
|
282
|
+
version: string;
|
|
283
|
+
trust: ProvenanceSourceTrust;
|
|
284
|
+
inherited_by?: string;
|
|
285
|
+
genes?: string[];
|
|
286
|
+
}
|
|
287
|
+
export interface CascadeLayerProvenance {
|
|
288
|
+
layer: ProvenanceCascadeLayerName;
|
|
289
|
+
source_id: string;
|
|
290
|
+
source_ref: string;
|
|
291
|
+
fingerprint: string;
|
|
292
|
+
status: ProvenanceLayerStatus;
|
|
293
|
+
sources?: string[];
|
|
294
|
+
}
|
|
295
|
+
export interface ConstraintProvenance {
|
|
296
|
+
constraint_id: string;
|
|
297
|
+
source_layer: ProvenanceCascadeLayerName | "unknown" | "unsupported";
|
|
298
|
+
source_dna_id: string;
|
|
299
|
+
gene_id: string;
|
|
300
|
+
codon_type: CodonType | "unknown";
|
|
301
|
+
action: "allow" | "warn" | "block" | "escalate" | "validate";
|
|
302
|
+
provenance_reason: string;
|
|
303
|
+
}
|
|
304
|
+
export interface CompiledProvenanceManifest {
|
|
305
|
+
schema_version: "intentdna.provenance.v1.7.2";
|
|
306
|
+
policy_bundle_id: string;
|
|
307
|
+
policy_bundle_version: string;
|
|
308
|
+
sources: DNASourceProvenance[];
|
|
309
|
+
cascade_layers: Record<ProvenanceCascadeLayerName, CascadeLayerProvenance>;
|
|
310
|
+
constraint_winners: ConstraintProvenance[];
|
|
311
|
+
compiled_ir_hash: string;
|
|
312
|
+
}
|
|
265
313
|
export interface VariableDef {
|
|
266
314
|
description: string;
|
|
267
315
|
default: string;
|
|
@@ -295,6 +343,35 @@ export interface LegibilityAssetMap {
|
|
|
295
343
|
per_role?: Record<string, LegibilityAsset[]>;
|
|
296
344
|
per_workflow?: Record<string, LegibilityAsset[]>;
|
|
297
345
|
}
|
|
346
|
+
export type AdvisoryContextSourceType = "file" | "url" | "doc" | "note";
|
|
347
|
+
export interface AdvisoryContextSource {
|
|
348
|
+
id: string;
|
|
349
|
+
type: AdvisoryContextSourceType;
|
|
350
|
+
path?: string;
|
|
351
|
+
url?: string;
|
|
352
|
+
title?: string;
|
|
353
|
+
description?: string;
|
|
354
|
+
tags?: string[];
|
|
355
|
+
}
|
|
356
|
+
export interface PlanningContext {
|
|
357
|
+
sources: string[];
|
|
358
|
+
instructions?: string[];
|
|
359
|
+
require_quotes?: boolean;
|
|
360
|
+
require_attribution?: boolean;
|
|
361
|
+
}
|
|
362
|
+
export interface CompiledAdvisoryContextSource extends AdvisoryContextSource {
|
|
363
|
+
usage: "planning_only";
|
|
364
|
+
non_enforcing: true;
|
|
365
|
+
requires_quotes: boolean;
|
|
366
|
+
requires_attribution: boolean;
|
|
367
|
+
}
|
|
368
|
+
export interface CompiledPlanningContext {
|
|
369
|
+
sources: string[];
|
|
370
|
+
instructions?: string[];
|
|
371
|
+
require_quotes: boolean;
|
|
372
|
+
require_attribution: boolean;
|
|
373
|
+
non_enforcing: true;
|
|
374
|
+
}
|
|
298
375
|
export interface IntentDNA {
|
|
299
376
|
$schema?: string;
|
|
300
377
|
version: string;
|
|
@@ -311,6 +388,8 @@ export interface IntentDNA {
|
|
|
311
388
|
variables?: Record<string, string | VariableDef>;
|
|
312
389
|
mcp?: Record<string, MCPServerDef>;
|
|
313
390
|
legibility_assets?: LegibilityAssetMap;
|
|
391
|
+
context_sources?: AdvisoryContextSource[];
|
|
392
|
+
planning_context?: PlanningContext;
|
|
314
393
|
context_files?: {
|
|
315
394
|
mandatory?: string[];
|
|
316
395
|
per_role?: Record<string, string[]>;
|
|
@@ -341,6 +420,7 @@ export interface PreExecutionGate {
|
|
|
341
420
|
action: "block" | "escalate" | "warn";
|
|
342
421
|
message: string;
|
|
343
422
|
source_gene: string;
|
|
423
|
+
origin?: ConstraintOrigin;
|
|
344
424
|
}
|
|
345
425
|
export interface PostExecutionValidator {
|
|
346
426
|
check: string;
|
|
@@ -439,6 +519,8 @@ export interface ConstraintIR {
|
|
|
439
519
|
post_execution_validators: PostExecutionValidator[];
|
|
440
520
|
verifier_specs?: VerifierSpec[];
|
|
441
521
|
context_injections: ContextInjection[];
|
|
522
|
+
context_sources?: CompiledAdvisoryContextSource[];
|
|
523
|
+
planning_context?: CompiledPlanningContext;
|
|
442
524
|
source_dna_ids: string[];
|
|
443
525
|
compiled_at: string;
|
|
444
526
|
active_context?: string;
|
|
@@ -455,6 +537,8 @@ export interface ConstraintIR {
|
|
|
455
537
|
per_workflow?: Record<string, string[]>;
|
|
456
538
|
};
|
|
457
539
|
verifier_policy?: VerifierCommandPolicy;
|
|
540
|
+
provenance?: CompiledProvenanceManifest;
|
|
541
|
+
compiled_ir_hash?: string;
|
|
458
542
|
}
|
|
459
543
|
/** A compiled workflow step with resolved metadata */
|
|
460
544
|
export interface WorkflowStep {
|