intentdna 1.7.5 → 1.8.1

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 (154) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/LICENSE +201 -0
  4. package/README.md +143 -194
  5. package/dist/assets/candidates.d.ts +124 -0
  6. package/dist/assets/candidates.js +1249 -0
  7. package/dist/assets/catalog.d.ts +101 -0
  8. package/dist/assets/catalog.js +311 -0
  9. package/dist/assets/evidence.d.ts +93 -0
  10. package/dist/assets/evidence.js +391 -0
  11. package/dist/assets/index.d.ts +4 -0
  12. package/dist/assets/index.js +4 -0
  13. package/dist/assets/metadata.d.ts +19 -0
  14. package/dist/assets/metadata.js +227 -0
  15. package/dist/cli/commands/assets.d.ts +65 -0
  16. package/dist/cli/commands/assets.js +2871 -0
  17. package/dist/cli/commands/auth.d.ts +322 -0
  18. package/dist/cli/commands/auth.js +1592 -0
  19. package/dist/cli/commands/cloud-auth-guidance.d.ts +2 -0
  20. package/dist/cli/commands/cloud-auth-guidance.js +4 -0
  21. package/dist/cli/commands/cloud.d.ts +1359 -0
  22. package/dist/cli/commands/cloud.js +8364 -0
  23. package/dist/cli/commands/evolve.js +18 -2
  24. package/dist/cli/commands/generate.js +25 -2
  25. package/dist/cli/commands/guard.d.ts +1 -1
  26. package/dist/cli/commands/guard.js +1 -1
  27. package/dist/cli/commands/guide.d.ts +15 -0
  28. package/dist/cli/commands/guide.js +1747 -0
  29. package/dist/cli/commands/init.d.ts +12 -0
  30. package/dist/cli/commands/init.js +95 -60
  31. package/dist/cli/commands/org.d.ts +16 -0
  32. package/dist/cli/commands/org.js +2393 -0
  33. package/dist/cli/commands/release.d.ts +59 -0
  34. package/dist/cli/commands/release.js +716 -0
  35. package/dist/cli/commands/report.d.ts +82 -0
  36. package/dist/cli/commands/report.js +1516 -0
  37. package/dist/cli/commands/run.d.ts +8 -1
  38. package/dist/cli/commands/run.js +129 -57
  39. package/dist/cli/commands/sync.d.ts +8 -1
  40. package/dist/cli/commands/sync.js +436 -141
  41. package/dist/cli/commands/templates.d.ts +64 -0
  42. package/dist/cli/commands/templates.js +2151 -1
  43. package/dist/cli/commands/workflow.d.ts +26 -0
  44. package/dist/cli/commands/workflow.js +3053 -0
  45. package/dist/cli/index.js +703 -36
  46. package/dist/compiler/cascade.js +4 -9
  47. package/dist/compiler/compile.js +1 -0
  48. package/dist/governance/evidence-capture-attribution.d.ts +12 -0
  49. package/dist/governance/evidence-capture-attribution.js +46 -0
  50. package/dist/governance/index.d.ts +2 -0
  51. package/dist/governance/index.js +1 -0
  52. package/dist/hooks/cli.d.ts +9 -0
  53. package/dist/hooks/cli.js +170 -411
  54. package/dist/hooks/enforce.d.ts +1 -0
  55. package/dist/hooks/protocol.d.ts +1 -1
  56. package/dist/hooks/protocol.js +1 -1
  57. package/dist/hooks/state.d.ts +41 -0
  58. package/dist/hooks/state.js +126 -2
  59. package/dist/index.d.ts +3 -0
  60. package/dist/index.js +3 -0
  61. package/dist/mcp/tools-state.js +2 -0
  62. package/dist/organization/index.d.ts +121 -0
  63. package/dist/organization/index.js +413 -0
  64. package/dist/runtime/claude-sync-target.d.ts +49 -0
  65. package/dist/runtime/claude-sync-target.js +280 -0
  66. package/dist/runtime/codex-adapter.d.ts +254 -0
  67. package/dist/runtime/codex-adapter.js +1204 -0
  68. package/dist/runtime/codex-sync-target.d.ts +33 -0
  69. package/dist/runtime/codex-sync-target.js +241 -0
  70. package/dist/runtime/index.d.ts +8 -0
  71. package/dist/runtime/index.js +4 -0
  72. package/dist/runtime/output-artifact-registry.d.ts +1 -1
  73. package/dist/runtime/output-artifact-registry.js +2 -0
  74. package/dist/runtime/settings-adapter.d.ts +1 -1
  75. package/dist/runtime/settings-adapter.js +1 -1
  76. package/dist/runtime/skill-adapter.d.ts +5 -1
  77. package/dist/runtime/skill-adapter.js +164 -39
  78. package/dist/runtime/sync-artifact-registry.d.ts +99 -0
  79. package/dist/runtime/sync-artifact-registry.js +194 -0
  80. package/dist/runtime/sync-target-plan.d.ts +45 -0
  81. package/dist/runtime/sync-target-plan.js +19 -0
  82. package/dist/runtime/verifier.d.ts +51 -0
  83. package/dist/runtime/verifier.js +462 -0
  84. package/dist/schema/controller-registry.d.ts +8 -1
  85. package/dist/schema/controller-registry.js +8 -0
  86. package/dist/schema/types.d.ts +35 -23
  87. package/dist/schema/types.js +10 -1
  88. package/dist/schema/validate.js +22 -22
  89. package/dist/schema/validators/controllers.js +38 -20
  90. package/dist/schema/workflow-authoring-contract.d.ts +29 -0
  91. package/dist/schema/workflow-authoring-contract.js +109 -0
  92. package/dist/schema/yaml-parser.js +18 -0
  93. package/dist/templates/api-backend.dna.yaml +19 -0
  94. package/dist/templates/brainstorming-first.dna.yaml +19 -0
  95. package/dist/templates/code-cleanup.dna.yaml +19 -0
  96. package/dist/templates/code-review-pipeline.dna.yaml +18 -0
  97. package/dist/templates/documentation-writer.dna.yaml +19 -0
  98. package/dist/templates/flutter-behavior-lock.dna.yaml +19 -0
  99. package/dist/templates/flutter-refactoring-rescue.dna.yaml +19 -0
  100. package/dist/templates/flutter-rewrite-diagnosis-review.dna.yaml +210 -0
  101. package/dist/templates/flutter-rewrite-fix-review-loop.dna.yaml +260 -0
  102. package/dist/templates/flutter-rewrite-new.dna.yaml +506 -0
  103. package/dist/templates/flutter-rewrite.dna.yaml +100 -4
  104. package/dist/templates/frontend-quality.dna.yaml +19 -0
  105. package/dist/templates/full-pipeline.dna.yaml +20 -0
  106. package/dist/templates/marketplace.d.ts +92 -0
  107. package/dist/templates/marketplace.js +502 -0
  108. package/dist/templates/metadata.d.ts +25 -0
  109. package/dist/templates/metadata.js +136 -3
  110. package/dist/templates/mobile-dev.dna.yaml +19 -0
  111. package/dist/templates/multi-agent-handoff-coordination.dna.yaml +156 -0
  112. package/dist/templates/multi-perspective-review.dna.yaml +19 -0
  113. package/dist/templates/persistent-executor.dna.yaml +19 -0
  114. package/dist/templates/qa-loop.dna.yaml +18 -0
  115. package/dist/templates/release-evidence-gate.dna.yaml +131 -0
  116. package/dist/templates/requirements-gate.dna.yaml +19 -0
  117. package/dist/templates/research-orchestration.dna.yaml +19 -0
  118. package/dist/templates/root-cause-analysis.dna.yaml +19 -0
  119. package/dist/templates/safe-refactoring.dna.yaml +321 -0
  120. package/dist/templates/secure-dev.dna.yaml +19 -0
  121. package/dist/templates/subagent-parallel.dna.yaml +19 -0
  122. package/dist/templates/systematic-debugging.dna.yaml +19 -0
  123. package/dist/templates/tdd-strict.dna.yaml +19 -0
  124. package/dist/templates/ui-visual-regression-qa.dna.yaml +133 -0
  125. package/dist/templates/workflow-author.dna.yaml +302 -0
  126. package/dist/templates/workflow-blocked-scope-triage.dna.yaml +242 -0
  127. package/dist/templates/workflow-completion-audit.dna.yaml +254 -0
  128. package/dist/templates/workflow-cross-boundary-resource-approval.dna.yaml +249 -0
  129. package/dist/templates/workflow-evidence-review.dna.yaml +198 -0
  130. package/dist/templates/workflow-module-inventory.dna.yaml +257 -0
  131. package/dist/templates/workflow-runtime-smoke-review.dna.yaml +252 -0
  132. package/dist/templates/workflow-verification-command-routing.dna.yaml +278 -0
  133. package/dist/workflow/authoring-packet.d.ts +67 -0
  134. package/dist/workflow/authoring-packet.js +218 -0
  135. package/dist/workflow/draft.d.ts +159 -0
  136. package/dist/workflow/draft.js +800 -0
  137. package/dist/workflow/index.d.ts +3 -0
  138. package/dist/workflow/index.js +3 -0
  139. package/dist/workflow/suggestion-review.d.ts +65 -0
  140. package/dist/workflow/suggestion-review.js +245 -0
  141. package/package.json +1 -1
  142. package/spec/README.md +15 -48
  143. package/spec/codex-adapter-contract.md +176 -0
  144. package/spec/sync-target-plan.md +158 -0
  145. package/spec/workflow-capability-ir.md +153 -0
  146. package/spec/agent-dispatch-fix.md +0 -116
  147. package/spec/control-plane-convergence-handoff-2026-04-24.md +0 -432
  148. package/spec/flutter-rewrite-template-optimization.md +0 -301
  149. package/spec/foundation-hardening.md +0 -178
  150. package/spec/hooks-infra-harness-hardening.md +0 -741
  151. package/spec/multi-config.md +0 -172
  152. package/spec/parallel-isolation.md +0 -268
  153. package/spec/template-version-namespace-fix.md +0 -653
  154. package/spec/workflow-pipeline.md +0 -101
@@ -0,0 +1,45 @@
1
+ import type { SyncOutputArtifactId } from "./output-artifact-registry.js";
2
+ export type SyncTarget = "claude_code" | "codex_cli";
3
+ export type SyncTargetMode = "write" | "remove" | "dry_run";
4
+ export type CapabilitySupportStatus = "supported" | "partial" | "unknown" | "unsupported";
5
+ export type EvidenceClassification = "enterprise_evidence" | "diagnostic_only" | "unsupported";
6
+ export interface PlannedArtifact {
7
+ kind: string;
8
+ path: string;
9
+ ownership: "intentdna_owned" | "owned_block" | "merge_preserve_user" | "diagnostic";
10
+ trackedByLock: boolean;
11
+ outputArtifact?: SyncOutputArtifactId;
12
+ }
13
+ export interface CapabilityReportEntry {
14
+ status: CapabilitySupportStatus;
15
+ surfaces: string[];
16
+ evidenceRefs: string[];
17
+ notes: string[];
18
+ }
19
+ export interface AdapterCapabilityReport {
20
+ adapter: SyncTarget;
21
+ runtime?: string;
22
+ capabilities: Record<string, CapabilityReportEntry>;
23
+ unsupported: string[];
24
+ }
25
+ export interface EvidencePlan {
26
+ primary: "RuntimeDecisionEvent" | "none";
27
+ diagnosticArtifacts: string[];
28
+ classification: EvidenceClassification;
29
+ notes: string[];
30
+ }
31
+ export interface SyncTargetPlan {
32
+ target: SyncTarget;
33
+ mode?: SyncTargetMode;
34
+ plannedArtifacts: PlannedArtifact[];
35
+ capabilityReport?: AdapterCapabilityReport;
36
+ evidencePlan: EvidencePlan;
37
+ warnings: string[];
38
+ unsupported: string[];
39
+ }
40
+ export interface SyncTargetTrackedOutput {
41
+ artifact: SyncOutputArtifactId;
42
+ path: string;
43
+ }
44
+ export declare function trackedOutputsForSyncTargetPlan(plan: SyncTargetPlan): SyncTargetTrackedOutput[];
45
+ export declare function formatSyncTargetPlanDiagnostics(plan: SyncTargetPlan): string;
@@ -0,0 +1,19 @@
1
+ export function trackedOutputsForSyncTargetPlan(plan) {
2
+ return plan.plannedArtifacts
3
+ .filter((artifact) => typeof artifact.outputArtifact === "string" && artifact.outputArtifact.length > 0)
4
+ .map((artifact) => ({ artifact: artifact.outputArtifact, path: artifact.path }));
5
+ }
6
+ export function formatSyncTargetPlanDiagnostics(plan) {
7
+ const lines = [];
8
+ if (plan.warnings.length > 0) {
9
+ lines.push(`${plan.target} warnings:`);
10
+ for (const warning of plan.warnings)
11
+ lines.push(` - ${warning}`);
12
+ }
13
+ if (plan.unsupported.length > 0) {
14
+ lines.push(`${plan.target} unsupported gaps:`);
15
+ for (const gap of plan.unsupported)
16
+ lines.push(` - ${gap}`);
17
+ }
18
+ return lines.length > 0 ? `${lines.join("\n")}\n` : "";
19
+ }
@@ -0,0 +1,51 @@
1
+ import type { CompletionCheck, ConstraintIR, StepCheckpoint, VerifierCommandPolicy } from "../schema/types.js";
2
+ export declare const DEFAULT_VERIFIER_COMMAND_TIMEOUT_MS = 30000;
3
+ export declare const MAX_VERIFIER_EVIDENCE_BYTES = 4096;
4
+ export interface VerifierRuntimeContext {
5
+ projectDir: string;
6
+ variables?: Record<string, string>;
7
+ commandTimeoutMs?: number;
8
+ }
9
+ export interface VerifierCheckResult {
10
+ passed: boolean;
11
+ target?: string;
12
+ evidence?: string;
13
+ exit_code?: number;
14
+ artifact?: string;
15
+ message?: string;
16
+ timedOut?: boolean;
17
+ }
18
+ export declare function resolveVerifierTemplate(template: string, variables?: Record<string, string>): string;
19
+ export declare function verifierTemplateVariableNames(template: string): string[];
20
+ export declare function validateVerifierTemplateVariables(template: string, variables?: Record<string, string>, mode?: "command" | "path"): {
21
+ valid: true;
22
+ } | {
23
+ valid: false;
24
+ variable: string;
25
+ evidence: string;
26
+ message: string;
27
+ };
28
+ export declare function hasUnresolvedVerifierTemplate(value: string): boolean;
29
+ export declare function unresolvedVerifierTemplateMessage(value: string): string;
30
+ export declare function hasUnsafeShellControl(command: string): boolean;
31
+ export declare function isVerifierCommandAllowed(policy: VerifierCommandPolicy | undefined, command: string): boolean;
32
+ export declare function verifierCommandPolicyMessage(command: string): string;
33
+ export declare function isBuiltinAssertAllowed(policy: VerifierCommandPolicy | undefined, assertName: string): boolean;
34
+ export declare function verifierAssertPolicyMessage(assertName: string): string;
35
+ export declare function trimVerifierEvidence(raw: string | undefined): string | undefined;
36
+ export declare function summarizeCommandEvidence(stdout: string, stderr: string): string | undefined;
37
+ export declare function execVerifierCommand(projectDir: string, command: string, timeoutMs?: number, env?: NodeJS.ProcessEnv): Promise<{
38
+ passed: boolean;
39
+ timedOut: boolean;
40
+ exitCode?: number;
41
+ evidence?: string;
42
+ }>;
43
+ export declare function resolveVerifierTarget(projectDir: string, targetPath: string): Promise<string | null>;
44
+ export declare function toProjectRelativePath(projectDir: string, filePath: string): Promise<string>;
45
+ export declare function getCompletionCheckFields(completion: CompletionCheck): string[];
46
+ export declare function validateCompletionVerifier(completion: CompletionCheck): {
47
+ message: string;
48
+ evidence: string;
49
+ } | null;
50
+ export declare function runCompletionVerifier(completion: CompletionCheck, ir: Pick<ConstraintIR, "verifier_policy">, context: VerifierRuntimeContext): Promise<VerifierCheckResult>;
51
+ export declare function runCheckpointVerifier(checkpoint: StepCheckpoint, ir: Pick<ConstraintIR, "verifier_policy">, context: VerifierRuntimeContext): Promise<VerifierCheckResult>;
@@ -0,0 +1,462 @@
1
+ import { spawn } from "node:child_process";
2
+ import { readFile, realpath, stat } from "node:fs/promises";
3
+ import { dirname, isAbsolute, relative, resolve } from "node:path";
4
+ export const DEFAULT_VERIFIER_COMMAND_TIMEOUT_MS = 30_000;
5
+ export const MAX_VERIFIER_EVIDENCE_BYTES = 4096;
6
+ const SAFE_COMMAND_VARIABLE_RE = /^[A-Za-z0-9_-]+$/;
7
+ const SAFE_PATH_VARIABLE_RE = /^[-A-Za-z0-9_./]+$/;
8
+ export function resolveVerifierTemplate(template, variables) {
9
+ let resolved = template;
10
+ if (variables?.ARGUMENTS !== undefined) {
11
+ resolved = resolved.replace(/(^|[^\\])\$ARGUMENTS/g, (_match, prefix) => `${prefix}${variables.ARGUMENTS}`);
12
+ }
13
+ resolved = resolved.replace(/\{\{(\w+)\}\}/g, (match, key) => variables?.[key] ?? match);
14
+ return resolved;
15
+ }
16
+ export function verifierTemplateVariableNames(template) {
17
+ const names = new Set();
18
+ if (/(^|[^\\])\$ARGUMENTS/.test(template))
19
+ names.add("ARGUMENTS");
20
+ for (const match of template.matchAll(/\{\{(\w+)\}\}/g))
21
+ names.add(match[1]);
22
+ return [...names];
23
+ }
24
+ function isWholeVerifierCommandTemplate(command) {
25
+ return /^\{\{\w+\}\}$/.test(command.trim()) || command.trim() === "$ARGUMENTS";
26
+ }
27
+ export function validateVerifierTemplateVariables(template, variables, mode = "command") {
28
+ for (const name of verifierTemplateVariableNames(template)) {
29
+ const value = variables?.[name];
30
+ if (value === undefined)
31
+ continue;
32
+ const matchesSafeCharacters = mode === "path"
33
+ ? SAFE_PATH_VARIABLE_RE.test(value) && !value.startsWith("/") && !value.includes("//") && !value.split(/[\\/]+/).includes("..")
34
+ : SAFE_COMMAND_VARIABLE_RE.test(value);
35
+ if (!matchesSafeCharacters) {
36
+ return {
37
+ valid: false,
38
+ variable: name,
39
+ evidence: `unsafe_variable:${name}`,
40
+ message: `Verifier template variable '${name}' contains unsafe ${mode} characters`,
41
+ };
42
+ }
43
+ }
44
+ return { valid: true };
45
+ }
46
+ function resolveVerifierCommand(rawCommand, policy, variables) {
47
+ const declaredCommand = rawCommand.trim();
48
+ if (isWholeVerifierCommandTemplate(declaredCommand)) {
49
+ const command = resolveVerifierTemplate(declaredCommand, variables).trim();
50
+ if (hasUnresolvedVerifierTemplate(command)) {
51
+ return { target: command, evidence: "unresolved_template", exit_code: 126, message: unresolvedVerifierTemplateMessage(command) };
52
+ }
53
+ if (!isVerifierCommandAllowed(policy, command)) {
54
+ return { target: command, evidence: "policy_denied", exit_code: 126, message: verifierCommandPolicyMessage(command) };
55
+ }
56
+ return { command };
57
+ }
58
+ if (!isVerifierCommandAllowed(policy, declaredCommand)) {
59
+ return { target: declaredCommand, evidence: "policy_denied", exit_code: 126, message: verifierCommandPolicyMessage(declaredCommand) };
60
+ }
61
+ const safeVariables = validateVerifierTemplateVariables(declaredCommand, variables, "command");
62
+ if (!safeVariables.valid) {
63
+ return { target: declaredCommand, evidence: safeVariables.evidence, exit_code: 126, message: safeVariables.message };
64
+ }
65
+ const command = resolveVerifierTemplate(declaredCommand, variables).trim();
66
+ if (hasUnresolvedVerifierTemplate(command)) {
67
+ return { target: command, evidence: "unresolved_template", exit_code: 126, message: unresolvedVerifierTemplateMessage(command) };
68
+ }
69
+ return { command };
70
+ }
71
+ function verifierCommandEnv(variables) {
72
+ const env = {};
73
+ for (const [name, value] of Object.entries(variables ?? {})) {
74
+ if (/^\w+$/.test(name) && SAFE_COMMAND_VARIABLE_RE.test(value)) {
75
+ env[name] = value;
76
+ }
77
+ }
78
+ return env;
79
+ }
80
+ export function hasUnresolvedVerifierTemplate(value) {
81
+ return /\{\{\w+\}\}/.test(value) || /(^|[^\\])\$ARGUMENTS/.test(value);
82
+ }
83
+ export function unresolvedVerifierTemplateMessage(value) {
84
+ return `Verifier template could not be fully resolved: ${value}`;
85
+ }
86
+ export function hasUnsafeShellControl(command) {
87
+ let inSingle = false;
88
+ let inDouble = false;
89
+ let escaped = false;
90
+ for (let i = 0; i < command.length; i++) {
91
+ const ch = command[i];
92
+ const next = command[i + 1] ?? "";
93
+ if (escaped) {
94
+ escaped = false;
95
+ continue;
96
+ }
97
+ if (ch === "\\" && !inSingle) {
98
+ escaped = true;
99
+ continue;
100
+ }
101
+ if (ch === "'" && !inDouble) {
102
+ inSingle = !inSingle;
103
+ continue;
104
+ }
105
+ if (ch === '"' && !inSingle) {
106
+ inDouble = !inDouble;
107
+ continue;
108
+ }
109
+ if (!inSingle && !inDouble) {
110
+ if (ch === ";" || ch === "`" || ch === "\n" || ch === "\r")
111
+ return true;
112
+ if ((ch === "&" || ch === "|" || ch === "<" || ch === ">") && next === ch)
113
+ return true;
114
+ if (ch === "$" && next === "(")
115
+ return true;
116
+ if ((ch === "<" || ch === ">") && next === "(")
117
+ return true;
118
+ if (ch === "|" || ch === "&" || ch === "<" || ch === ">")
119
+ return true;
120
+ }
121
+ if (inDouble) {
122
+ if (ch === "`")
123
+ return true;
124
+ if (ch === "$" && next === "(")
125
+ return true;
126
+ }
127
+ }
128
+ return false;
129
+ }
130
+ export function isVerifierCommandAllowed(policy, command) {
131
+ const normalized = command.trim();
132
+ if (!policy)
133
+ return false;
134
+ if (policy.allow_commands?.includes(normalized))
135
+ return true;
136
+ if (hasUnsafeShellControl(normalized))
137
+ return false;
138
+ return policy.allow_command_prefixes?.some((prefix) => normalized.startsWith(prefix)) ?? false;
139
+ }
140
+ export function verifierCommandPolicyMessage(command) {
141
+ return `Verifier command not allowed by verifier_policy: ${command}`;
142
+ }
143
+ export function isBuiltinAssertAllowed(policy, assertName) {
144
+ return policy?.allow_builtin_asserts?.includes(assertName) ?? false;
145
+ }
146
+ export function verifierAssertPolicyMessage(assertName) {
147
+ return `Verifier assert not allowed by verifier_policy: ${assertName}`;
148
+ }
149
+ export function trimVerifierEvidence(raw) {
150
+ if (!raw)
151
+ return undefined;
152
+ const trimmed = raw.trim();
153
+ if (!trimmed)
154
+ return undefined;
155
+ return trimmed.length > MAX_VERIFIER_EVIDENCE_BYTES
156
+ ? trimmed.slice(0, MAX_VERIFIER_EVIDENCE_BYTES)
157
+ : trimmed;
158
+ }
159
+ export function summarizeCommandEvidence(stdout, stderr) {
160
+ const stdoutBytes = Buffer.byteLength(stdout, "utf8");
161
+ const stderrBytes = Buffer.byteLength(stderr, "utf8");
162
+ if (stdoutBytes === 0 && stderrBytes === 0)
163
+ return undefined;
164
+ return trimVerifierEvidence(`stdout_bytes=${stdoutBytes} stderr_bytes=${stderrBytes}`);
165
+ }
166
+ export async function execVerifierCommand(projectDir, command, timeoutMs = DEFAULT_VERIFIER_COMMAND_TIMEOUT_MS, env) {
167
+ return new Promise((resolvePromise) => {
168
+ const child = spawn("bash", ["-lc", command], {
169
+ cwd: projectDir,
170
+ stdio: ["ignore", "pipe", "pipe"],
171
+ env: { ...process.env, ...(env ?? {}) },
172
+ });
173
+ let stdout = "";
174
+ let stderr = "";
175
+ let settled = false;
176
+ const settle = (result) => {
177
+ if (settled)
178
+ return;
179
+ settled = true;
180
+ clearTimeout(timeoutId);
181
+ resolvePromise(result);
182
+ };
183
+ const timeoutId = setTimeout(() => {
184
+ child.kill("SIGTERM");
185
+ setTimeout(() => child.kill("SIGKILL"), 1000).unref();
186
+ settle({ passed: false, timedOut: true, exitCode: 124, evidence: summarizeCommandEvidence(stdout, stderr) });
187
+ }, timeoutMs);
188
+ child.stdout.on("data", (chunk) => {
189
+ stdout += String(chunk);
190
+ if (stdout.length > MAX_VERIFIER_EVIDENCE_BYTES)
191
+ stdout = stdout.slice(0, MAX_VERIFIER_EVIDENCE_BYTES);
192
+ });
193
+ child.stderr.on("data", (chunk) => {
194
+ stderr += String(chunk);
195
+ if (stderr.length > MAX_VERIFIER_EVIDENCE_BYTES)
196
+ stderr = stderr.slice(0, MAX_VERIFIER_EVIDENCE_BYTES);
197
+ });
198
+ child.on("error", () => settle({
199
+ passed: false,
200
+ timedOut: false,
201
+ exitCode: 1,
202
+ evidence: summarizeCommandEvidence(stdout, stderr),
203
+ }));
204
+ child.on("close", (code) => settle({
205
+ passed: code === 0,
206
+ timedOut: false,
207
+ exitCode: code ?? 1,
208
+ evidence: summarizeCommandEvidence(stdout, stderr),
209
+ }));
210
+ });
211
+ }
212
+ async function execVerifierStdout(projectDir, command, args) {
213
+ return new Promise((resolvePromise, rejectPromise) => {
214
+ const child = spawn(command, args, { cwd: projectDir, stdio: ["ignore", "pipe", "ignore"] });
215
+ let stdout = "";
216
+ child.stdout.on("data", (chunk) => { stdout += String(chunk); });
217
+ child.on("error", rejectPromise);
218
+ child.on("close", (code) => resolvePromise({ stdout, exitCode: code ?? 1 }));
219
+ });
220
+ }
221
+ function isWithinProjectRoot(projectRoot, targetPath) {
222
+ const relPath = relative(projectRoot, targetPath);
223
+ return relPath === "" || (!relPath.startsWith("..") && !isAbsolute(relPath));
224
+ }
225
+ export async function resolveVerifierTarget(projectDir, targetPath) {
226
+ const projectRoot = await realpath(projectDir).catch(() => resolve(projectDir));
227
+ const resolvedPath = resolve(projectRoot, targetPath);
228
+ if (!isWithinProjectRoot(projectRoot, resolvedPath))
229
+ return null;
230
+ const canonicalParent = await realpath(dirname(resolvedPath)).catch(() => null);
231
+ if (canonicalParent && !isWithinProjectRoot(projectRoot, canonicalParent))
232
+ return null;
233
+ const canonicalTarget = await realpath(resolvedPath).catch(() => null);
234
+ if (canonicalTarget && !isWithinProjectRoot(projectRoot, canonicalTarget))
235
+ return null;
236
+ return canonicalTarget ?? resolvedPath;
237
+ }
238
+ export async function toProjectRelativePath(projectDir, filePath) {
239
+ const projectRoot = await realpath(projectDir).catch(() => resolve(projectDir));
240
+ const resolvedPath = resolve(projectRoot, filePath);
241
+ const canonicalPath = await realpath(resolvedPath).catch(() => resolvedPath);
242
+ const targetPath = isWithinProjectRoot(projectRoot, canonicalPath) ? canonicalPath : resolvedPath;
243
+ const relPath = relative(projectRoot, targetPath);
244
+ return relPath === "" ? "." : relPath;
245
+ }
246
+ export function getCompletionCheckFields(completion) {
247
+ if (typeof completion !== "object" || completion === null || Array.isArray(completion))
248
+ return [];
249
+ const fields = [];
250
+ if (completion.file_exists !== undefined)
251
+ fields.push("file_exists");
252
+ if (completion.file_not_empty !== undefined)
253
+ fields.push("file_not_empty");
254
+ if (completion.file_contains !== undefined)
255
+ fields.push("file_contains");
256
+ if (completion.command_success !== undefined)
257
+ fields.push("command_success");
258
+ return fields;
259
+ }
260
+ export function validateCompletionVerifier(completion) {
261
+ const fields = getCompletionCheckFields(completion);
262
+ if (fields.length !== 1) {
263
+ return {
264
+ message: "Completion verifier must define exactly one check",
265
+ evidence: `fields:${fields.join(",") || "none"}`,
266
+ };
267
+ }
268
+ const field = fields[0];
269
+ if (field === "file_exists" && (typeof completion.file_exists !== "string" || !completion.file_exists)) {
270
+ return { message: "Completion verifier file_exists must be a non-empty string", evidence: "invalid:file_exists" };
271
+ }
272
+ if (field === "file_not_empty" && (typeof completion.file_not_empty !== "string" || !completion.file_not_empty)) {
273
+ return { message: "Completion verifier file_not_empty must be a non-empty string", evidence: "invalid:file_not_empty" };
274
+ }
275
+ if (field === "command_success" && (typeof completion.command_success !== "string" || !completion.command_success)) {
276
+ return { message: "Completion verifier command_success must be a non-empty string", evidence: "invalid:command_success" };
277
+ }
278
+ if (field === "file_contains") {
279
+ const fileContains = completion.file_contains;
280
+ if (typeof fileContains !== "object" || fileContains === null) {
281
+ return { message: "Completion verifier file_contains must define path and pattern", evidence: "invalid:file_contains" };
282
+ }
283
+ if (typeof fileContains.path !== "string" || !fileContains.path) {
284
+ return { message: "Completion verifier file_contains.path must be a non-empty string", evidence: "invalid:file_contains.path" };
285
+ }
286
+ if (typeof fileContains.pattern !== "string" || !fileContains.pattern) {
287
+ return { message: "Completion verifier file_contains.pattern must be a non-empty string", evidence: "invalid:file_contains.pattern" };
288
+ }
289
+ }
290
+ return null;
291
+ }
292
+ export async function runCompletionVerifier(completion, ir, context) {
293
+ const invalidCompletion = validateCompletionVerifier(completion);
294
+ if (invalidCompletion) {
295
+ return { passed: false, evidence: invalidCompletion.evidence, message: invalidCompletion.message };
296
+ }
297
+ if (completion.file_exists) {
298
+ const safeVariables = validateVerifierTemplateVariables(completion.file_exists, context.variables, "path");
299
+ if (!safeVariables.valid)
300
+ return { passed: false, target: completion.file_exists, evidence: safeVariables.evidence, message: safeVariables.message };
301
+ const target = resolveVerifierTemplate(completion.file_exists, context.variables);
302
+ if (hasUnresolvedVerifierTemplate(target))
303
+ return { passed: false, target, evidence: "unresolved_template", message: unresolvedVerifierTemplateMessage(target) };
304
+ const resolvedTarget = await resolveVerifierTarget(context.projectDir, target);
305
+ const artifact = resolvedTarget ?? undefined;
306
+ const evidence = resolvedTarget ? `exists:${resolvedTarget}` : "path_escape";
307
+ if (!resolvedTarget)
308
+ return { passed: false, target, evidence, artifact, message: `Verifier target escapes project root: ${target}` };
309
+ try {
310
+ await stat(resolvedTarget);
311
+ return { passed: true, target, evidence, artifact };
312
+ }
313
+ catch {
314
+ return { passed: false, target, evidence, artifact, message: `Required file missing: ${target}` };
315
+ }
316
+ }
317
+ if (completion.file_not_empty) {
318
+ const safeVariables = validateVerifierTemplateVariables(completion.file_not_empty, context.variables, "path");
319
+ if (!safeVariables.valid)
320
+ return { passed: false, target: completion.file_not_empty, evidence: safeVariables.evidence, message: safeVariables.message };
321
+ const target = resolveVerifierTemplate(completion.file_not_empty, context.variables);
322
+ if (hasUnresolvedVerifierTemplate(target))
323
+ return { passed: false, target, evidence: "unresolved_template", message: unresolvedVerifierTemplateMessage(target) };
324
+ const resolvedTarget = await resolveVerifierTarget(context.projectDir, target);
325
+ const artifact = resolvedTarget ?? undefined;
326
+ if (!resolvedTarget)
327
+ return { passed: false, target, evidence: "path_escape", artifact, message: `Verifier target escapes project root: ${target}` };
328
+ try {
329
+ const raw = await readFile(resolvedTarget, "utf-8");
330
+ const evidence = `bytes=${Buffer.byteLength(raw, "utf8")}`;
331
+ return raw.trim().length === 0
332
+ ? { passed: false, target, evidence, artifact, message: `Required file is empty: ${target}` }
333
+ : { passed: true, target, evidence, artifact };
334
+ }
335
+ catch {
336
+ return { passed: false, target, evidence: `not_empty:${resolvedTarget}`, artifact, message: `Required file missing: ${target}` };
337
+ }
338
+ }
339
+ if (completion.file_contains) {
340
+ const safeTargetVariables = validateVerifierTemplateVariables(completion.file_contains.path, context.variables, "path");
341
+ if (!safeTargetVariables.valid)
342
+ return { passed: false, target: completion.file_contains.path, evidence: safeTargetVariables.evidence, message: safeTargetVariables.message };
343
+ const safePatternVariables = validateVerifierTemplateVariables(completion.file_contains.pattern, context.variables);
344
+ if (!safePatternVariables.valid)
345
+ return { passed: false, target: completion.file_contains.pattern, evidence: safePatternVariables.evidence, message: safePatternVariables.message };
346
+ const target = resolveVerifierTemplate(completion.file_contains.path, context.variables);
347
+ const pattern = resolveVerifierTemplate(completion.file_contains.pattern, context.variables);
348
+ if (hasUnresolvedVerifierTemplate(target) || hasUnresolvedVerifierTemplate(pattern)) {
349
+ return { passed: false, target, evidence: "unresolved_template", message: unresolvedVerifierTemplateMessage(hasUnresolvedVerifierTemplate(target) ? target : pattern) };
350
+ }
351
+ const resolvedTarget = await resolveVerifierTarget(context.projectDir, target);
352
+ const artifact = resolvedTarget ?? undefined;
353
+ const evidence = `pattern:${pattern}`;
354
+ if (!resolvedTarget)
355
+ return { passed: false, target, evidence: "path_escape", artifact, message: `Verifier target escapes project root: ${target}` };
356
+ try {
357
+ const raw = await readFile(resolvedTarget, "utf-8");
358
+ const re = new RegExp(pattern);
359
+ return re.test(raw)
360
+ ? { passed: true, target, evidence, artifact }
361
+ : { passed: false, target, evidence, artifact, message: `Required pattern missing in ${target}` };
362
+ }
363
+ catch {
364
+ return { passed: false, target, evidence, artifact, message: `Required file missing: ${target}` };
365
+ }
366
+ }
367
+ const rawCommand = completion.command_success;
368
+ const commandResolution = resolveVerifierCommand(rawCommand, ir.verifier_policy, context.variables);
369
+ if (!commandResolution.command)
370
+ return { passed: false, target: commandResolution.target, evidence: commandResolution.evidence, exit_code: commandResolution.exit_code, message: commandResolution.message };
371
+ const command = commandResolution.command;
372
+ const commandResult = await execVerifierCommand(context.projectDir, command, context.commandTimeoutMs, verifierCommandEnv(context.variables));
373
+ return {
374
+ passed: commandResult.passed,
375
+ target: command,
376
+ evidence: commandResult.evidence,
377
+ exit_code: commandResult.exitCode,
378
+ timedOut: commandResult.timedOut,
379
+ message: commandResult.passed
380
+ ? undefined
381
+ : commandResult.timedOut
382
+ ? `Verifier command timed out after ${context.commandTimeoutMs ?? DEFAULT_VERIFIER_COMMAND_TIMEOUT_MS}ms: ${command}`
383
+ : `Verifier command failed: ${command}`,
384
+ };
385
+ }
386
+ export async function runCheckpointVerifier(checkpoint, ir, context) {
387
+ const assertCommands = {
388
+ clean_working_tree: "git status --porcelain",
389
+ lint_passing: "npm run lint --silent 2>/dev/null",
390
+ build_passing: "npm run build --silent 2>/dev/null",
391
+ };
392
+ if (checkpoint.command) {
393
+ const rawCommand = checkpoint.command;
394
+ const commandResolution = resolveVerifierCommand(rawCommand, ir.verifier_policy, context.variables);
395
+ if (!commandResolution.command)
396
+ return { passed: false, target: commandResolution.target, evidence: commandResolution.evidence, exit_code: commandResolution.exit_code, message: commandResolution.message };
397
+ const command = commandResolution.command;
398
+ const commandResult = await execVerifierCommand(context.projectDir, command, context.commandTimeoutMs, verifierCommandEnv(context.variables));
399
+ return {
400
+ passed: commandResult.passed,
401
+ target: command,
402
+ evidence: commandResult.evidence,
403
+ exit_code: commandResult.exitCode,
404
+ timedOut: commandResult.timedOut,
405
+ message: commandResult.passed
406
+ ? checkpoint.message
407
+ : commandResult.timedOut
408
+ ? `Verifier command timed out after ${context.commandTimeoutMs ?? DEFAULT_VERIFIER_COMMAND_TIMEOUT_MS}ms: ${command}`
409
+ : `Verifier command failed: ${command}`,
410
+ };
411
+ }
412
+ if (checkpoint.assert === "clean_working_tree") {
413
+ if (!isBuiltinAssertAllowed(ir.verifier_policy, checkpoint.assert)) {
414
+ return { passed: false, target: checkpoint.assert, evidence: "policy_denied", exit_code: 126, message: verifierAssertPolicyMessage(checkpoint.assert) };
415
+ }
416
+ try {
417
+ const { stdout, exitCode } = await execVerifierStdout(context.projectDir, "git", ["status", "--porcelain"]);
418
+ const changedEntries = stdout.split("\n").filter(Boolean).length;
419
+ return {
420
+ passed: exitCode === 0 && stdout.trim() === "",
421
+ target: checkpoint.assert,
422
+ evidence: `changed_entries=${changedEntries}`,
423
+ exit_code: exitCode,
424
+ message: checkpoint.message,
425
+ };
426
+ }
427
+ catch {
428
+ return { passed: false, target: checkpoint.assert, exit_code: 1, message: checkpoint.message };
429
+ }
430
+ }
431
+ if (checkpoint.assert === "no_test_regression") {
432
+ if (!isBuiltinAssertAllowed(ir.verifier_policy, checkpoint.assert)) {
433
+ return { passed: false, target: checkpoint.assert, evidence: "policy_denied", exit_code: 126, message: verifierAssertPolicyMessage(checkpoint.assert) };
434
+ }
435
+ try {
436
+ const baseline = parseInt(await readFile(resolve(context.projectDir, ".dna/test-baseline"), "utf-8").catch(() => "0"), 10);
437
+ const current = parseInt(await readFile(resolve(context.projectDir, ".dna/test-current"), "utf-8").catch(() => "0"), 10);
438
+ return { passed: current <= baseline, target: checkpoint.assert, evidence: `baseline=${baseline} current=${current}`, exit_code: 0, message: checkpoint.message };
439
+ }
440
+ catch {
441
+ return { passed: false, target: checkpoint.assert, exit_code: 1, message: checkpoint.message };
442
+ }
443
+ }
444
+ if (assertCommands[checkpoint.assert]) {
445
+ if (!isBuiltinAssertAllowed(ir.verifier_policy, checkpoint.assert)) {
446
+ return { passed: false, target: checkpoint.assert, evidence: "policy_denied", exit_code: 126, message: verifierAssertPolicyMessage(checkpoint.assert) };
447
+ }
448
+ const commandResult = await execVerifierCommand(context.projectDir, assertCommands[checkpoint.assert], context.commandTimeoutMs);
449
+ return {
450
+ passed: commandResult.passed,
451
+ target: checkpoint.assert,
452
+ evidence: commandResult.evidence,
453
+ exit_code: commandResult.exitCode,
454
+ message: commandResult.passed
455
+ ? checkpoint.message
456
+ : commandResult.timedOut
457
+ ? `Verifier command timed out after ${context.commandTimeoutMs ?? DEFAULT_VERIFIER_COMMAND_TIMEOUT_MS}ms: ${assertCommands[checkpoint.assert]}`
458
+ : checkpoint.message,
459
+ };
460
+ }
461
+ return { passed: false, target: checkpoint.assert, exit_code: 1, message: checkpoint.message };
462
+ }
@@ -5,7 +5,7 @@ export interface ControllerKindMetadata {
5
5
  kind: ControllerKind;
6
6
  label: string;
7
7
  supportsSkillGeneration: boolean;
8
- requiredRoleKeys: readonly ControllerRoleKey[];
8
+ requiredRoleKeys: readonly string[];
9
9
  diagnosisRoleKeys: readonly ControllerRoleKey[];
10
10
  fixRoleKeys: readonly ControllerRoleKey[];
11
11
  }
@@ -23,6 +23,13 @@ export declare const CONTROLLER_KIND_REGISTRY: readonly [{
23
23
  readonly requiredRoleKeys: readonly ["analyzer", "analysis_reviewer", "surgeon", "fix_reviewer", "test_runner"];
24
24
  readonly diagnosisRoleKeys: readonly ["analyzer", "analysis_reviewer"];
25
25
  readonly fixRoleKeys: readonly ["surgeon", "fix_reviewer", "test_runner"];
26
+ }, {
27
+ readonly kind: "sequential";
28
+ readonly label: "Sequential workflow gate";
29
+ readonly supportsSkillGeneration: false;
30
+ readonly requiredRoleKeys: readonly [];
31
+ readonly diagnosisRoleKeys: readonly [];
32
+ readonly fixRoleKeys: readonly [];
26
33
  }];
27
34
  export declare function getControllerKindMetadata(kind: unknown): ControllerKindMetadata | undefined;
28
35
  export declare function isSupportedControllerKind(kind: unknown): kind is ControllerKind;
@@ -22,6 +22,14 @@ export const CONTROLLER_KIND_REGISTRY = [
22
22
  diagnosisRoleKeys: ["analyzer", "analysis_reviewer"],
23
23
  fixRoleKeys: ["surgeon", "fix_reviewer", "test_runner"],
24
24
  },
25
+ {
26
+ kind: "sequential",
27
+ label: "Sequential workflow gate",
28
+ supportsSkillGeneration: false,
29
+ requiredRoleKeys: [],
30
+ diagnosisRoleKeys: [],
31
+ fixRoleKeys: [],
32
+ },
25
33
  ];
26
34
  const CONTROLLER_KIND_BY_ID = new Map(CONTROLLER_KIND_REGISTRY.map((entry) => [entry.kind, entry]));
27
35
  export function getControllerKindMetadata(kind) {