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/hooks/enforce.js
CHANGED
|
@@ -713,8 +713,9 @@ export function checkReflectionLimit(failCount, maxAttempts, handoffCount, maxHa
|
|
|
713
713
|
* Pure function — no I/O.
|
|
714
714
|
*/
|
|
715
715
|
export function checkContextReadiness(sessionReads, required) {
|
|
716
|
-
const
|
|
717
|
-
const
|
|
716
|
+
const normalizePath = (value) => normalize(value);
|
|
717
|
+
const readSet = new Set(sessionReads.map(normalizePath));
|
|
718
|
+
const missing = required.filter((f) => !readSet.has(normalizePath(f)));
|
|
718
719
|
return { ready: missing.length === 0, missing };
|
|
719
720
|
}
|
|
720
721
|
// ── Workflow Boundary Gate ────────────────────────────────────
|
package/dist/hooks/state.d.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* All read operations are fail-safe (return null on error, never throw).
|
|
10
10
|
* All write operations use atomic temp+rename pattern.
|
|
11
11
|
*/
|
|
12
|
-
import type { CompletedArtifactEntry } from "../schema/types.js";
|
|
12
|
+
import type { CompletedArtifactEntry, VerifierKind, VerifierSeverity, VerifierWhen } from "../schema/types.js";
|
|
13
13
|
/** Workflow state written by runtime and read by hooks */
|
|
14
14
|
export interface DNAWorkflowState {
|
|
15
15
|
active: boolean;
|
|
@@ -115,6 +115,25 @@ export declare function writeSessionReads(projectDir: string, state: SessionRead
|
|
|
115
115
|
* @deprecated Prefer `DNAStateManager.appendSessionRead()`.
|
|
116
116
|
*/
|
|
117
117
|
export declare function appendSessionRead(projectDir: string, filePath: string, sessionId?: string): Promise<void>;
|
|
118
|
+
export interface VerifierResultEntry {
|
|
119
|
+
verifier_id: string;
|
|
120
|
+
when: VerifierWhen;
|
|
121
|
+
severity: VerifierSeverity;
|
|
122
|
+
kind: VerifierKind;
|
|
123
|
+
workflow?: string;
|
|
124
|
+
step_id?: string;
|
|
125
|
+
source_genes?: string[];
|
|
126
|
+
status: "pass" | "fail";
|
|
127
|
+
target?: string;
|
|
128
|
+
evidence?: string;
|
|
129
|
+
exit_code?: number;
|
|
130
|
+
artifact?: string;
|
|
131
|
+
message?: string;
|
|
132
|
+
timestamp: string;
|
|
133
|
+
}
|
|
134
|
+
export declare function readVerifierResults(projectDir: string, sessionId?: string): Promise<VerifierResultEntry[]>;
|
|
135
|
+
export declare function writeVerifierResults(projectDir: string, results: VerifierResultEntry[], sessionId?: string): Promise<void>;
|
|
136
|
+
export declare function appendVerifierResult(projectDir: string, result: VerifierResultEntry, sessionId?: string): Promise<void>;
|
|
118
137
|
/** Trace entry for hook call observability */
|
|
119
138
|
export interface TraceEntry {
|
|
120
139
|
trace_id: string;
|
package/dist/hooks/state.js
CHANGED
|
@@ -214,6 +214,61 @@ export async function appendSessionRead(projectDir, filePath, sessionId) {
|
|
|
214
214
|
await writeSessionReads(projectDir, state, sessionId);
|
|
215
215
|
}
|
|
216
216
|
}
|
|
217
|
+
const VERIFIER_RESULTS_FILE = "workflow/verifier-results.json";
|
|
218
|
+
async function readVerifierResultsFile(projectDir, sessionId) {
|
|
219
|
+
const stateDir = resolveStateDir(projectDir, sessionId);
|
|
220
|
+
const filePath = join(stateDir, VERIFIER_RESULTS_FILE);
|
|
221
|
+
try {
|
|
222
|
+
const raw = await readFile(filePath, "utf-8");
|
|
223
|
+
const parsed = JSON.parse(raw);
|
|
224
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
225
|
+
}
|
|
226
|
+
catch {
|
|
227
|
+
return [];
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
function verifierResultKey(result) {
|
|
231
|
+
return [
|
|
232
|
+
result.verifier_id,
|
|
233
|
+
result.when,
|
|
234
|
+
result.severity,
|
|
235
|
+
result.kind,
|
|
236
|
+
result.workflow ?? "",
|
|
237
|
+
result.step_id ?? "",
|
|
238
|
+
result.status,
|
|
239
|
+
result.target ?? "",
|
|
240
|
+
result.timestamp,
|
|
241
|
+
].join("|");
|
|
242
|
+
}
|
|
243
|
+
export async function readVerifierResults(projectDir, sessionId) {
|
|
244
|
+
if (sessionId) {
|
|
245
|
+
return readVerifierResultsFile(projectDir, sessionId);
|
|
246
|
+
}
|
|
247
|
+
const sessions = await listSessions(projectDir);
|
|
248
|
+
const all = await readVerifierResultsFile(projectDir);
|
|
249
|
+
for (const id of sessions) {
|
|
250
|
+
const results = await readVerifierResultsFile(projectDir, id);
|
|
251
|
+
all.push(...results);
|
|
252
|
+
}
|
|
253
|
+
const seen = new Set();
|
|
254
|
+
return all.filter((result) => {
|
|
255
|
+
const key = verifierResultKey(result);
|
|
256
|
+
if (seen.has(key))
|
|
257
|
+
return false;
|
|
258
|
+
seen.add(key);
|
|
259
|
+
return true;
|
|
260
|
+
}).sort((a, b) => a.timestamp.localeCompare(b.timestamp));
|
|
261
|
+
}
|
|
262
|
+
export async function writeVerifierResults(projectDir, results, sessionId) {
|
|
263
|
+
const stateDir = resolveStateDir(projectDir, sessionId);
|
|
264
|
+
const filePath = join(stateDir, VERIFIER_RESULTS_FILE);
|
|
265
|
+
await atomicWrite(filePath, JSON.stringify(results, null, 2));
|
|
266
|
+
}
|
|
267
|
+
export async function appendVerifierResult(projectDir, result, sessionId) {
|
|
268
|
+
const results = await readVerifierResultsFile(projectDir, sessionId);
|
|
269
|
+
results.push(result);
|
|
270
|
+
await writeVerifierResults(projectDir, results, sessionId);
|
|
271
|
+
}
|
|
217
272
|
const TRACE_DIR = "trace";
|
|
218
273
|
const MAX_TRACE_SIZE = 10 * 1024 * 1024; // 10MB
|
|
219
274
|
const TRACE_RETENTION_DAYS = 7;
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* <!-- intentdna:end -->
|
|
14
14
|
*/
|
|
15
15
|
import type { ConstraintIR } from "../schema/types.js";
|
|
16
|
-
export type MarkdownTarget = "claude-md" | "soul-md" | "cursorrules" | "system-prompt";
|
|
16
|
+
export type MarkdownTarget = "claude-md" | "soul-md" | "cursorrules" | "system-prompt" | "agents-md";
|
|
17
17
|
/**
|
|
18
18
|
* Compile Constraint IR to a markdown block suitable for the target format.
|
|
19
19
|
*/
|
package/dist/runtime/markdown.js
CHANGED
|
@@ -28,6 +28,8 @@ export function compileToMarkdown(ir, target) {
|
|
|
28
28
|
return renderCursorrules(ir);
|
|
29
29
|
case "system-prompt":
|
|
30
30
|
return renderSystemPrompt(ir);
|
|
31
|
+
case "agents-md":
|
|
32
|
+
return renderAgentsMd(ir);
|
|
31
33
|
}
|
|
32
34
|
}
|
|
33
35
|
/**
|
|
@@ -191,6 +193,153 @@ function renderCursorrules(ir) {
|
|
|
191
193
|
}
|
|
192
194
|
return lines.join("\n");
|
|
193
195
|
}
|
|
196
|
+
function renderAgentsMd(ir) {
|
|
197
|
+
const lines = [];
|
|
198
|
+
const high = ir.prompt_directives.filter(d => d.priority === "high");
|
|
199
|
+
const med = ir.prompt_directives.filter(d => d.priority === "medium");
|
|
200
|
+
const low = ir.prompt_directives.filter(d => d.priority === "low");
|
|
201
|
+
lines.push("# Intent DNA — Codex Instructions");
|
|
202
|
+
lines.push("");
|
|
203
|
+
lines.push("Codex loads AGENTS.md at session start. After running `dna sync`, restart Codex so these instructions are reloaded.");
|
|
204
|
+
lines.push("These instructions are behavioral guidance; use Codex sandbox/config for deterministic enforcement.");
|
|
205
|
+
lines.push("");
|
|
206
|
+
if (ir.active_context) {
|
|
207
|
+
lines.push(`Active context: ${ir.active_context}`);
|
|
208
|
+
lines.push("");
|
|
209
|
+
}
|
|
210
|
+
if (high.length > 0) {
|
|
211
|
+
lines.push("## Critical Directives");
|
|
212
|
+
for (const d of high)
|
|
213
|
+
lines.push(`- ${d.text}`);
|
|
214
|
+
lines.push("");
|
|
215
|
+
}
|
|
216
|
+
if (med.length > 0) {
|
|
217
|
+
lines.push("## Standard Directives");
|
|
218
|
+
for (const d of med)
|
|
219
|
+
lines.push(`- ${d.text}`);
|
|
220
|
+
lines.push("");
|
|
221
|
+
}
|
|
222
|
+
if (low.length > 0) {
|
|
223
|
+
lines.push("## Preferences");
|
|
224
|
+
for (const d of low)
|
|
225
|
+
lines.push(`- ${d.text}`);
|
|
226
|
+
lines.push("");
|
|
227
|
+
}
|
|
228
|
+
if (ir.context_files || ir.legibility_assets) {
|
|
229
|
+
lines.push("## Required Context");
|
|
230
|
+
const files = collectContextFiles(ir);
|
|
231
|
+
if (files.length > 0) {
|
|
232
|
+
lines.push("Read these files before work that depends on project context:");
|
|
233
|
+
for (const file of files)
|
|
234
|
+
lines.push(`- ${file}`);
|
|
235
|
+
}
|
|
236
|
+
else {
|
|
237
|
+
lines.push("Follow any required-read assets declared by the active DNA.");
|
|
238
|
+
}
|
|
239
|
+
lines.push("");
|
|
240
|
+
}
|
|
241
|
+
if (ir.roles_scope_map && ir.roles_scope_map.length > 0) {
|
|
242
|
+
lines.push("## Role Boundaries");
|
|
243
|
+
for (const role of ir.roles_scope_map) {
|
|
244
|
+
lines.push(`- ${role.role_name}: read ${formatList(role.scope.read)}; write ${formatList(role.scope.write)}${formatToolPermissions(role.tool_permissions)}`);
|
|
245
|
+
}
|
|
246
|
+
lines.push("");
|
|
247
|
+
}
|
|
248
|
+
else if (ir.role_scope || ir.role_tool_permissions) {
|
|
249
|
+
lines.push("## Active Role Boundary");
|
|
250
|
+
if (ir.role_scope)
|
|
251
|
+
lines.push(`- Read: ${formatList(ir.role_scope.read)}`);
|
|
252
|
+
if (ir.role_scope)
|
|
253
|
+
lines.push(`- Write: ${formatList(ir.role_scope.write)}`);
|
|
254
|
+
if (ir.role_tool_permissions)
|
|
255
|
+
lines.push(`- Tools:${formatToolPermissions(ir.role_tool_permissions).replace(/^;/, "")}`);
|
|
256
|
+
lines.push("");
|
|
257
|
+
}
|
|
258
|
+
if (ir.pre_execution_gates.length > 0) {
|
|
259
|
+
lines.push("## Runtime Constraints");
|
|
260
|
+
for (const g of ir.pre_execution_gates) {
|
|
261
|
+
const action = g.action === "block" ? "must not" : g.action === "escalate" ? "ask first" : "warn";
|
|
262
|
+
lines.push(`- [${action}] ${g.message}`);
|
|
263
|
+
}
|
|
264
|
+
lines.push("");
|
|
265
|
+
}
|
|
266
|
+
if (ir.tool_filters.length > 0) {
|
|
267
|
+
lines.push("## Tool and Sandbox Guidance");
|
|
268
|
+
for (const f of ir.tool_filters)
|
|
269
|
+
lines.push(`- ${f.action}: ${f.target ?? f.reason} (${f.reason})`);
|
|
270
|
+
lines.push("- Prefer Codex `workspace-write` with `on-request` approvals for editable work; use `read-only` for planning/review.");
|
|
271
|
+
lines.push("");
|
|
272
|
+
}
|
|
273
|
+
if ((ir.workflows_ir && ir.workflows_ir.length > 0) || (ir.verifier_specs && ir.verifier_specs.length > 0)) {
|
|
274
|
+
lines.push("## Workflow and Verification Expectations");
|
|
275
|
+
for (const wf of ir.workflows_ir ?? []) {
|
|
276
|
+
lines.push(`- Workflow ${wf.workflow_name}: roles ${formatList(wf.active_roles)}.`);
|
|
277
|
+
for (const handoff of wf.handoff_chain ?? []) {
|
|
278
|
+
const produced = handoff.produces?.map(a => a.path ?? a.type).join(", ");
|
|
279
|
+
if (produced)
|
|
280
|
+
lines.push(` - Step ${handoff.step_id} should produce: ${produced}`);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
for (const spec of ir.verifier_specs ?? []) {
|
|
284
|
+
const target = spec.checkpoint?.assert ?? spec.completion?.file_exists ?? spec.completion?.file_not_empty ?? spec.completion?.command_success ?? spec.id;
|
|
285
|
+
lines.push(`- Verify ${spec.when}/${spec.severity}: ${target}`);
|
|
286
|
+
}
|
|
287
|
+
lines.push("");
|
|
288
|
+
}
|
|
289
|
+
if (ir.post_execution_validators.length > 0) {
|
|
290
|
+
lines.push("## Post-Work Checks");
|
|
291
|
+
for (const v of ir.post_execution_validators)
|
|
292
|
+
lines.push(`- ${v.check}`);
|
|
293
|
+
lines.push("");
|
|
294
|
+
}
|
|
295
|
+
lines.push(`_Compiled: ${ir.compiled_at} | Sources: ${ir.source_dna_ids.join(", ")}_`);
|
|
296
|
+
return lines.join("\n");
|
|
297
|
+
}
|
|
298
|
+
function collectContextFiles(ir) {
|
|
299
|
+
const files = [];
|
|
300
|
+
const add = (file) => {
|
|
301
|
+
if (file && !files.includes(file))
|
|
302
|
+
files.push(file);
|
|
303
|
+
};
|
|
304
|
+
for (const file of ir.context_files?.mandatory ?? [])
|
|
305
|
+
add(file);
|
|
306
|
+
for (const roleFiles of Object.values(ir.context_files?.per_role ?? {})) {
|
|
307
|
+
for (const file of roleFiles)
|
|
308
|
+
add(file);
|
|
309
|
+
}
|
|
310
|
+
for (const workflowFiles of Object.values(ir.context_files?.per_workflow ?? {})) {
|
|
311
|
+
for (const file of workflowFiles)
|
|
312
|
+
add(file);
|
|
313
|
+
}
|
|
314
|
+
for (const asset of ir.legibility_assets?.mandatory ?? []) {
|
|
315
|
+
if (asset.type === "required_read")
|
|
316
|
+
add(asset.path);
|
|
317
|
+
}
|
|
318
|
+
for (const roleAssets of Object.values(ir.legibility_assets?.per_role ?? {})) {
|
|
319
|
+
for (const asset of roleAssets)
|
|
320
|
+
if (asset.type === "required_read")
|
|
321
|
+
add(asset.path);
|
|
322
|
+
}
|
|
323
|
+
for (const workflowAssets of Object.values(ir.legibility_assets?.per_workflow ?? {})) {
|
|
324
|
+
for (const asset of workflowAssets)
|
|
325
|
+
if (asset.type === "required_read")
|
|
326
|
+
add(asset.path);
|
|
327
|
+
}
|
|
328
|
+
return files;
|
|
329
|
+
}
|
|
330
|
+
function formatList(values) {
|
|
331
|
+
return values && values.length > 0 ? values.join(", ") : "none";
|
|
332
|
+
}
|
|
333
|
+
function formatToolPermissions(permissions) {
|
|
334
|
+
if (!permissions)
|
|
335
|
+
return "";
|
|
336
|
+
const parts = [];
|
|
337
|
+
if (permissions.allow?.length)
|
|
338
|
+
parts.push(`allow ${permissions.allow.join(", ")}`);
|
|
339
|
+
if (permissions.deny?.length)
|
|
340
|
+
parts.push(`deny ${permissions.deny.join(", ")}`);
|
|
341
|
+
return parts.length > 0 ? `; tools ${parts.join("; ")}` : "";
|
|
342
|
+
}
|
|
194
343
|
function renderSystemPrompt(ir) {
|
|
195
344
|
const lines = [];
|
|
196
345
|
lines.push("# Behavioral Guidelines");
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* See docs/references/cc-source-analysis.md §2 for the full hook event catalog.
|
|
8
8
|
*/
|
|
9
|
-
export type HookEventKey = "preToolUse" | "postToolUse" | "userPromptSubmit" | "subagentStop" | "preCompact" | "notification";
|
|
9
|
+
export type HookEventKey = "preToolUse" | "postToolUse" | "userPromptSubmit" | "subagentStop" | "preCompact" | "notification" | "stop";
|
|
10
10
|
export interface SettingsHookEntry {
|
|
11
11
|
matcher: string;
|
|
12
12
|
hooks: Array<{
|
|
@@ -21,7 +21,8 @@ export interface DNASettings {
|
|
|
21
21
|
* Compile settings.json hook configuration for dna-hook binary.
|
|
22
22
|
* Uses `dna-hook <event>` commands.
|
|
23
23
|
*/
|
|
24
|
-
export declare function compilePluginSettings(enabledEvents: HookEventKey[], timeout?: number): DNASettings;
|
|
24
|
+
export declare function compilePluginSettings(enabledEvents: HookEventKey[], timeout?: number, timeouts?: Partial<Record<HookEventKey, number>>): DNASettings;
|
|
25
|
+
export declare function recommendHookTimeouts(ir: import("../schema/types.js").ConstraintIR, baseTimeout?: number): Partial<Record<HookEventKey, number>>;
|
|
25
26
|
/**
|
|
26
27
|
* Determine which hook events are needed based on IR content.
|
|
27
28
|
*/
|
|
@@ -16,17 +16,19 @@ const HOOK_EVENT_MAP = {
|
|
|
16
16
|
subagentStop: "SubagentStop",
|
|
17
17
|
preCompact: "PreCompact",
|
|
18
18
|
notification: "Notification",
|
|
19
|
+
stop: "Stop",
|
|
19
20
|
};
|
|
20
21
|
/**
|
|
21
22
|
* Compile settings.json hook configuration for dna-hook binary.
|
|
22
23
|
* Uses `dna-hook <event>` commands.
|
|
23
24
|
*/
|
|
24
|
-
export function compilePluginSettings(enabledEvents, timeout = 10) {
|
|
25
|
+
export function compilePluginSettings(enabledEvents, timeout = 10, timeouts) {
|
|
25
26
|
const settings = { hooks: {} };
|
|
26
27
|
for (const key of enabledEvents) {
|
|
27
28
|
const eventName = HOOK_EVENT_MAP[key];
|
|
28
29
|
if (!eventName)
|
|
29
30
|
continue;
|
|
31
|
+
const eventTimeout = timeouts?.[key] ?? timeout;
|
|
30
32
|
settings.hooks[eventName] = [
|
|
31
33
|
{
|
|
32
34
|
matcher: "",
|
|
@@ -34,7 +36,7 @@ export function compilePluginSettings(enabledEvents, timeout = 10) {
|
|
|
34
36
|
{
|
|
35
37
|
type: "command",
|
|
36
38
|
command: `dna-hook ${eventName}`,
|
|
37
|
-
...(
|
|
39
|
+
...(eventTimeout > 0 ? { timeout: eventTimeout } : {}),
|
|
38
40
|
},
|
|
39
41
|
],
|
|
40
42
|
},
|
|
@@ -42,6 +44,20 @@ export function compilePluginSettings(enabledEvents, timeout = 10) {
|
|
|
42
44
|
}
|
|
43
45
|
return settings;
|
|
44
46
|
}
|
|
47
|
+
export function recommendHookTimeouts(ir, baseTimeout = 10) {
|
|
48
|
+
const timeouts = {};
|
|
49
|
+
const hasStopCommandVerifiers = ir.verifier_specs?.some((spec) => (spec.when === "stop" || spec.when === "pre_handoff" || spec.when === "post_step") &&
|
|
50
|
+
((spec.kind === "checkpoint" && !!spec.checkpoint?.command) ||
|
|
51
|
+
(spec.kind === "completion" && !!spec.completion?.command_success) ||
|
|
52
|
+
spec.checkpoint?.assert === "build_passing" ||
|
|
53
|
+
spec.checkpoint?.assert === "lint_passing" ||
|
|
54
|
+
spec.checkpoint?.assert === "clean_working_tree" ||
|
|
55
|
+
spec.checkpoint?.assert === "no_test_regression")) ?? false;
|
|
56
|
+
if (hasStopCommandVerifiers) {
|
|
57
|
+
timeouts.stop = Math.max(baseTimeout, 45);
|
|
58
|
+
}
|
|
59
|
+
return timeouts;
|
|
60
|
+
}
|
|
45
61
|
/**
|
|
46
62
|
* Determine which hook events are needed based on IR content.
|
|
47
63
|
*/
|
|
@@ -51,10 +67,13 @@ export function detectEnabledEvents(ir) {
|
|
|
51
67
|
const hasFilters = ir.tool_filters.length > 0;
|
|
52
68
|
const hasRoleScope = ir.roles_scope_map && ir.roles_scope_map.length > 0;
|
|
53
69
|
const hasCheckpoints = ir.step_checkpoints && ir.step_checkpoints.length > 0;
|
|
70
|
+
const hasStopVerifiers = ir.verifier_specs?.some((spec) => spec.when === "stop" || spec.when === "pre_handoff" || spec.when === "post_step") ?? false;
|
|
71
|
+
const hasPostToolVerifiers = ir.verifier_specs?.some((spec) => spec.when === "post_tool_use") ?? false;
|
|
72
|
+
const hasHandoffs = ir.workflows_ir?.some((wf) => wf.handoff_chain.length > 0) ?? false;
|
|
54
73
|
if (hasGates || hasFilters || hasRoleScope || hasCheckpoints) {
|
|
55
74
|
events.push("preToolUse");
|
|
56
75
|
}
|
|
57
|
-
if (ir.post_execution_validators.length > 0) {
|
|
76
|
+
if (ir.post_execution_validators.length > 0 || hasPostToolVerifiers) {
|
|
58
77
|
events.push("postToolUse");
|
|
59
78
|
}
|
|
60
79
|
if (ir.prompt_directives.some(d => d.priority === "high")) {
|
|
@@ -69,7 +88,10 @@ export function detectEnabledEvents(ir) {
|
|
|
69
88
|
if (hasGates || hasFilters) {
|
|
70
89
|
events.push("notification");
|
|
71
90
|
}
|
|
72
|
-
|
|
91
|
+
if (hasStopVerifiers || hasCheckpoints || hasHandoffs) {
|
|
92
|
+
events.push("stop");
|
|
93
|
+
}
|
|
94
|
+
return [...new Set(events)];
|
|
73
95
|
}
|
|
74
96
|
// ── File Operations ────────────────────────────────────────
|
|
75
97
|
/**
|
|
@@ -43,39 +43,65 @@ export function compileWorkflowToSkill(plan, roles, ir, variables) {
|
|
|
43
43
|
lines.push("");
|
|
44
44
|
}
|
|
45
45
|
// Required Context — context files that must be read before any work
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
const workflowKeys = [plan.workflow_key, plan.source_workflow, plan.name].filter((key, index, keys) => Boolean(key) && keys.indexOf(key) === index);
|
|
47
|
+
const requiredReadAssets = [];
|
|
48
|
+
if (ir?.legibility_assets) {
|
|
49
|
+
const assets = ir.legibility_assets;
|
|
50
|
+
for (const asset of assets.mandatory ?? []) {
|
|
51
|
+
if (asset.type === "required_read" && !requiredReadAssets.includes(asset.path))
|
|
52
|
+
requiredReadAssets.push(asset.path);
|
|
53
|
+
}
|
|
54
|
+
for (const workflowKey of workflowKeys) {
|
|
55
|
+
for (const asset of assets.per_workflow?.[workflowKey] ?? []) {
|
|
56
|
+
if (asset.type === "required_read" && !requiredReadAssets.includes(asset.path))
|
|
57
|
+
requiredReadAssets.push(asset.path);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
for (const step of plan.steps) {
|
|
61
|
+
for (const asset of assets.per_role?.[step.role] ?? []) {
|
|
62
|
+
if (asset.type === "required_read" && !requiredReadAssets.includes(asset.path))
|
|
63
|
+
requiredReadAssets.push(asset.path);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
const contextFiles = requiredReadAssets.length > 0 ? requiredReadAssets : (() => {
|
|
68
|
+
if (!ir?.context_files)
|
|
69
|
+
return [];
|
|
70
|
+
const files = [];
|
|
48
71
|
if (ir.context_files.mandatory)
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
72
|
+
files.push(...ir.context_files.mandatory);
|
|
73
|
+
for (const workflowKey of workflowKeys) {
|
|
74
|
+
const workflowFiles = ir.context_files.per_workflow?.[workflowKey] ?? [];
|
|
75
|
+
for (const file of workflowFiles) {
|
|
76
|
+
if (!files.includes(file))
|
|
77
|
+
files.push(file);
|
|
78
|
+
}
|
|
53
79
|
}
|
|
54
|
-
// per_role for all roles used in this workflow
|
|
55
80
|
if (ir.context_files.per_role) {
|
|
56
81
|
for (const step of plan.steps) {
|
|
57
82
|
const roleFiles = ir.context_files.per_role[step.role];
|
|
58
83
|
if (roleFiles) {
|
|
59
84
|
for (const f of roleFiles) {
|
|
60
|
-
if (!
|
|
61
|
-
|
|
85
|
+
if (!files.includes(f))
|
|
86
|
+
files.push(f);
|
|
62
87
|
}
|
|
63
88
|
}
|
|
64
89
|
}
|
|
65
90
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
lines.push(
|
|
74
|
-
lines.push("Without reading these, you cannot perform your role.");
|
|
75
|
-
lines.push("Hook will warn on first Edit/Bash/Write until these are read.");
|
|
76
|
-
lines.push("</Required_Context>");
|
|
77
|
-
lines.push("");
|
|
91
|
+
return files;
|
|
92
|
+
})();
|
|
93
|
+
if (contextFiles.length > 0) {
|
|
94
|
+
lines.push("<Required_Context>");
|
|
95
|
+
lines.push("Your context is EMPTY at startup. You MUST read these files first:");
|
|
96
|
+
lines.push("");
|
|
97
|
+
for (let i = 0; i < contextFiles.length; i++) {
|
|
98
|
+
lines.push(`${i + 1}. ${contextFiles[i]}`);
|
|
78
99
|
}
|
|
100
|
+
lines.push("");
|
|
101
|
+
lines.push("Without reading these, you cannot perform your role.");
|
|
102
|
+
lines.push("Hook will warn on first Edit/Bash/Write until these are read.");
|
|
103
|
+
lines.push("</Required_Context>");
|
|
104
|
+
lines.push("");
|
|
79
105
|
}
|
|
80
106
|
// Steps — each step MUST be executed via Agent() tool call
|
|
81
107
|
const usedRoles = new Set(plan.steps.map((s) => s.role));
|
|
@@ -153,9 +179,9 @@ export function compileWorkflowToSkill(plan, roles, ir, variables) {
|
|
|
153
179
|
// Execution Policy — force Agent dispatch
|
|
154
180
|
lines.push("<Execution_Policy>");
|
|
155
181
|
lines.push("- **CRITICAL**: Each step MUST be executed by spawning an Agent using the Agent tool with the specified subagent_type. DO NOT perform any step's work directly in the main session.");
|
|
156
|
-
lines.push("- Use
|
|
182
|
+
lines.push("- Use the task tracker to track each step only as pending/in_progress/completed; use deleted only for skipped dependent tasks.");
|
|
157
183
|
lines.push("- After completing each step, immediately proceed to the next — do not stop, summarize, or wait for confirmation.");
|
|
158
|
-
lines.push("- If a step
|
|
184
|
+
lines.push("- If a step cannot complete, do not use a failed status. Keep the current task in_progress or record the blocker in the task description/output, delete skipped dependent tasks, and continue only to non-dependent steps.");
|
|
159
185
|
lines.push("- Do not ask the user for permission between steps — the workflow is pre-approved.");
|
|
160
186
|
lines.push("- Wait for each Agent to complete and read its output before proceeding to the next step.");
|
|
161
187
|
lines.push("</Execution_Policy>");
|
package/dist/schema/types.d.ts
CHANGED
|
@@ -83,6 +83,11 @@ export interface RoleDef {
|
|
|
83
83
|
success_criteria?: string[];
|
|
84
84
|
failure_modes?: string[];
|
|
85
85
|
}
|
|
86
|
+
export interface VerifierCommandPolicy {
|
|
87
|
+
allow_commands?: string[];
|
|
88
|
+
allow_command_prefixes?: string[];
|
|
89
|
+
allow_builtin_asserts?: string[];
|
|
90
|
+
}
|
|
86
91
|
/** Handoff artifact type — what kind of artifact is passed between steps */
|
|
87
92
|
export type HandoffType = "file" | "directory" | "test_result" | "git_commit" | "summary" | "state";
|
|
88
93
|
/** A single artifact consumed or produced by a workflow step */
|
|
@@ -218,6 +223,18 @@ export interface MCPServerDef {
|
|
|
218
223
|
timeout?: number;
|
|
219
224
|
optional?: boolean;
|
|
220
225
|
}
|
|
226
|
+
export type LegibilityAssetType = "required_read" | "repo_map" | "manifest" | "artifact_index";
|
|
227
|
+
export interface LegibilityAsset {
|
|
228
|
+
type: LegibilityAssetType;
|
|
229
|
+
path: string;
|
|
230
|
+
description?: string;
|
|
231
|
+
tags?: string[];
|
|
232
|
+
}
|
|
233
|
+
export interface LegibilityAssetMap {
|
|
234
|
+
mandatory?: LegibilityAsset[];
|
|
235
|
+
per_role?: Record<string, LegibilityAsset[]>;
|
|
236
|
+
per_workflow?: Record<string, LegibilityAsset[]>;
|
|
237
|
+
}
|
|
221
238
|
export interface IntentDNA {
|
|
222
239
|
$schema?: string;
|
|
223
240
|
version: string;
|
|
@@ -232,11 +249,13 @@ export interface IntentDNA {
|
|
|
232
249
|
workflows?: Record<string, WorkflowDef>;
|
|
233
250
|
variables?: Record<string, string | VariableDef>;
|
|
234
251
|
mcp?: Record<string, MCPServerDef>;
|
|
252
|
+
legibility_assets?: LegibilityAssetMap;
|
|
235
253
|
context_files?: {
|
|
236
254
|
mandatory?: string[];
|
|
237
255
|
per_role?: Record<string, string[]>;
|
|
238
256
|
per_workflow?: Record<string, string[]>;
|
|
239
257
|
};
|
|
258
|
+
verifier_policy?: VerifierCommandPolicy;
|
|
240
259
|
epigenetic: {
|
|
241
260
|
markers: EpigeneticMarker[];
|
|
242
261
|
};
|
|
@@ -267,6 +286,22 @@ export interface PostExecutionValidator {
|
|
|
267
286
|
threshold?: number;
|
|
268
287
|
source_gene: string;
|
|
269
288
|
}
|
|
289
|
+
export type VerifierWhen = "stop" | "post_tool_use" | "pre_handoff" | "post_step";
|
|
290
|
+
export type VerifierSeverity = "block" | "warn";
|
|
291
|
+
export type VerifierKind = "checkpoint" | "completion";
|
|
292
|
+
export interface VerifierSpec {
|
|
293
|
+
id: string;
|
|
294
|
+
when: VerifierWhen;
|
|
295
|
+
severity: VerifierSeverity;
|
|
296
|
+
kind: VerifierKind;
|
|
297
|
+
workflow_name?: string;
|
|
298
|
+
step_id?: string;
|
|
299
|
+
step_role?: string;
|
|
300
|
+
source_genes?: string[];
|
|
301
|
+
checkpoint?: StepCheckpoint;
|
|
302
|
+
completion?: CompletionCheck;
|
|
303
|
+
source?: "workflow_checkpoint" | "workflow_completion" | "role_post_check";
|
|
304
|
+
}
|
|
270
305
|
export interface ContextInjection {
|
|
271
306
|
type: "experience" | "preference" | "warning";
|
|
272
307
|
text: string;
|
|
@@ -333,6 +368,7 @@ export interface ConstraintIR {
|
|
|
333
368
|
tool_filters: ToolFilter[];
|
|
334
369
|
pre_execution_gates: PreExecutionGate[];
|
|
335
370
|
post_execution_validators: PostExecutionValidator[];
|
|
371
|
+
verifier_specs?: VerifierSpec[];
|
|
336
372
|
context_injections: ContextInjection[];
|
|
337
373
|
source_dna_ids: string[];
|
|
338
374
|
compiled_at: string;
|
|
@@ -343,11 +379,13 @@ export interface ConstraintIR {
|
|
|
343
379
|
roles_scope_map?: RoleScopeEntry[];
|
|
344
380
|
step_checkpoints?: StepCheckpointIR[];
|
|
345
381
|
workflows_ir?: WorkflowIR[];
|
|
382
|
+
legibility_assets?: LegibilityAssetMap;
|
|
346
383
|
context_files?: {
|
|
347
384
|
mandatory?: string[];
|
|
348
385
|
per_role?: Record<string, string[]>;
|
|
349
386
|
per_workflow?: Record<string, string[]>;
|
|
350
387
|
};
|
|
388
|
+
verifier_policy?: VerifierCommandPolicy;
|
|
351
389
|
}
|
|
352
390
|
/** A compiled workflow step with resolved metadata */
|
|
353
391
|
export interface WorkflowStep {
|
|
@@ -416,4 +454,5 @@ export interface WorkflowPlan {
|
|
|
416
454
|
/** Compilation metadata */
|
|
417
455
|
compiled_at: string;
|
|
418
456
|
source_workflow: string;
|
|
457
|
+
workflow_key?: string;
|
|
419
458
|
}
|