intentdna 1.8.6 → 1.8.7

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 (60) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.md +1 -0
  4. package/dist/cli/commands/run-lifecycle.d.ts +73 -0
  5. package/dist/cli/commands/run-lifecycle.js +240 -0
  6. package/dist/cli/commands/run.d.ts +22 -40
  7. package/dist/cli/commands/run.js +674 -392
  8. package/dist/cli/index.js +87 -2
  9. package/dist/compiler/workflow.js +3 -2
  10. package/dist/hooks/cli.d.ts +1 -2
  11. package/dist/hooks/cli.js +119 -80
  12. package/dist/hooks/enforce.d.ts +2 -0
  13. package/dist/hooks/enforce.js +56 -27
  14. package/dist/hooks/enforcement-boundary.d.ts +13 -0
  15. package/dist/hooks/enforcement-boundary.js +33 -0
  16. package/dist/hooks/index.d.ts +3 -2
  17. package/dist/hooks/index.js +3 -2
  18. package/dist/hooks/protocol.d.ts +12 -4
  19. package/dist/hooks/protocol.js +20 -14
  20. package/dist/hooks/schema.d.ts +2 -1
  21. package/dist/hooks/schema.js +6 -2
  22. package/dist/hooks/state-manager.d.ts +5 -5
  23. package/dist/hooks/state-manager.js +26 -24
  24. package/dist/hooks/state.d.ts +19 -3
  25. package/dist/hooks/state.js +327 -80
  26. package/dist/mcp/index.js +0 -0
  27. package/dist/runtime/diagnosis-contract-verifier.d.ts +11 -0
  28. package/dist/runtime/diagnosis-contract-verifier.js +417 -0
  29. package/dist/runtime/execution-provider.d.ts +40 -0
  30. package/dist/runtime/execution-provider.js +138 -0
  31. package/dist/runtime/handoff-resolver.d.ts +61 -0
  32. package/dist/runtime/handoff-resolver.js +167 -0
  33. package/dist/runtime/index.d.ts +24 -0
  34. package/dist/runtime/index.js +13 -0
  35. package/dist/runtime/process-tree.d.ts +47 -0
  36. package/dist/runtime/process-tree.js +402 -0
  37. package/dist/runtime/providers/claude.d.ts +9 -0
  38. package/dist/runtime/providers/claude.js +64 -0
  39. package/dist/runtime/providers/codex.d.ts +8 -0
  40. package/dist/runtime/providers/codex.js +72 -0
  41. package/dist/runtime/result-store.d.ts +32 -0
  42. package/dist/runtime/result-store.js +130 -0
  43. package/dist/runtime/run-contracts.d.ts +290 -0
  44. package/dist/runtime/run-contracts.js +58 -0
  45. package/dist/runtime/run-controller.d.ts +149 -0
  46. package/dist/runtime/run-controller.js +1108 -0
  47. package/dist/runtime/run-store.d.ts +96 -0
  48. package/dist/runtime/run-store.js +725 -0
  49. package/dist/runtime/worker-executor.d.ts +19 -0
  50. package/dist/runtime/worker-executor.js +194 -0
  51. package/dist/runtime/workflow-plan-adapter.d.ts +26 -0
  52. package/dist/runtime/workflow-plan-adapter.js +416 -0
  53. package/dist/runtime/workflow-runner.d.ts +15 -3
  54. package/dist/runtime/workflow-runner.js +13 -1
  55. package/dist/runtime/workspace-isolation.d.ts +103 -0
  56. package/dist/runtime/workspace-isolation.js +373 -0
  57. package/dist/schema/types.d.ts +1 -0
  58. package/dist/schema/validate.js +64 -6
  59. package/dist/schema/yaml-parser.js +7 -2
  60. package/package.json +1 -1
@@ -0,0 +1,417 @@
1
+ import { createHash } from "node:crypto";
2
+ const DIAGNOSIS_ALLOWED_KINDS = ["BUG", "UNIMPLEMENTED", "INFRA", "REMOVED", "TEST_BUG"];
3
+ const DIAGNOSIS_ACTION_KINDS = ["BUG", "UNIMPLEMENTED", "INFRA", "TEST_BUG"];
4
+ const SAFE_MODULE_RE = /^[A-Za-z0-9_-]+$/;
5
+ function hashText(value) {
6
+ return createHash("sha256").update(value).digest("hex");
7
+ }
8
+ function canonicalJson(value) {
9
+ if (Array.isArray(value)) {
10
+ return `[${value.map((entry) => canonicalJson(entry)).join(",");
11
+ `;
12
+ }
13
+ if (value && typeof value === "object") {
14
+ const record = value as Record<string, unknown>;
15
+ const keys = Object.keys(record).sort();
16
+ return `;
17
+ {
18
+ $;
19
+ {
20
+ keys.map((key) => `${JSON.stringify(key)}:${canonicalJson(record[key])}`).join(",");
21
+ }
22
+ }
23
+ `;
24
+ }
25
+ return JSON.stringify(value);
26
+ }
27
+
28
+ function normalizedDiagnosisHash(content: string): string {
29
+ return hashText(content.replace(/\s+/g, " ").trim());
30
+ }
31
+
32
+ function safePathLike(value: unknown): value is string {
33
+ return typeof value === "string" && value.length > 0;
34
+ }
35
+
36
+ function fail(reason: string): DiagnosisContractVerifyResult {
37
+ return { passed: false, reason, evidence: `;
38
+ INTENTDNA_DIAGNOSTIC: $;
39
+ {
40
+ reason;
41
+ }
42
+ ` };
43
+ }
44
+
45
+ function sameStringArray(a: unknown[], b: string[]): boolean {
46
+ return Array.isArray(a) && a.length === b.length && a.every((value, index) => value === b[index]);
47
+ }
48
+
49
+ function sameKindList(a: unknown, b: string[]): boolean {
50
+ return Array.isArray(a) && sameStringArray(a, b);
51
+ }
52
+
53
+ function isLegacySnapshot(snapshot: unknown): snapshot is Omit<DiagnosisFailureSnapshot, "sidecar_semantic_sha256" | "diagnosis_semantic_sha256" | "allowed_kinds"> & {
54
+ baseline_source_sha256?: string;
55
+ diagnosis_artifact_sha256?: string;
56
+ observed_failure_count?: unknown;
57
+ work_item_count?: unknown;
58
+ allowed_kinds?: unknown;
59
+ } {
60
+ const candidate = snapshot as Record<string, unknown>;
61
+ return candidate ? typeof candidate === "object" : false;
62
+ }
63
+
64
+ function sameSummarySnapshot(a: unknown, b: unknown): boolean {
65
+ if (!a || !b || typeof a !== "object" || typeof b !== "object") return false;
66
+ const left = a as Record<string, unknown>;
67
+ const right = b as Record<string, unknown>;
68
+ return left.baseline_source_sha256 === right.baseline_source_sha256
69
+ && left.diagnosis_artifact_sha256 === right.diagnosis_artifact_sha256
70
+ && left.sidecar_semantic_sha256 === right.sidecar_semantic_sha256
71
+ && left.diagnosis_semantic_sha256 === right.diagnosis_semantic_sha256
72
+ && left.observed_failure_count === right.observed_failure_count
73
+ && left.work_item_count === right.work_item_count
74
+ && sameStringArray(Array.isArray(left.allowed_kinds) ? (left.allowed_kinds as unknown as string[]) : [],
75
+ Array.isArray(right.allowed_kinds) ? (right.allowed_kinds as unknown as string[]) : []);
76
+ }
77
+
78
+ async function resolveTargetPath(projectDir: string, rel: string): Promise<string | null> {
79
+ const resolved = await resolveVerifierTarget(projectDir, rel);
80
+ return resolved;
81
+ }
82
+
83
+ async function readTextFile(projectDir: string, rel: string): Promise<string> {
84
+ const target = await resolveTargetPath(projectDir, rel);
85
+ if (!target) return Promise.reject(new Error(`;
86
+ path_escape: $;
87
+ {
88
+ rel;
89
+ }
90
+ `));
91
+ return readFile(target, "utf8");
92
+ }
93
+
94
+ async function ensureInsideAndReadJson(projectDir: string, rel: string): Promise<unknown> {
95
+ const target = await resolveTargetPath(projectDir, rel);
96
+ if (!target) return Promise.reject(new Error(`;
97
+ path_escape: $;
98
+ {
99
+ rel;
100
+ }
101
+ `));
102
+ const raw = await readFile(target, "utf8");
103
+ try {
104
+ return JSON.parse(raw);
105
+ } catch {
106
+ throw new Error(`;
107
+ invalid_json: $;
108
+ {
109
+ rel;
110
+ }
111
+ `);
112
+ }
113
+ }
114
+
115
+ function validateEvidenceCount(value: unknown): value is { failed: number; skipped: number; hung: number } {
116
+ if (!value || typeof value !== "object") return false;
117
+ const { failed, skipped, hung } = value as { failed?: unknown; skipped?: unknown; hung?: unknown; };
118
+ return Number.isInteger(failed) && failed >= 0
119
+ && Number.isInteger(skipped) && skipped >= 0
120
+ && Number.isInteger(hung) && hung >= 0;
121
+ }
122
+
123
+ function safeId(value: unknown): value is string {
124
+ return typeof value === "string" && /^[A-Za-z0-9][A-Za-z0-9_.:-]*$/.test(value);
125
+ }
126
+
127
+ function sameText(value: unknown): boolean {
128
+ return typeof value === "string" && value.trim().length > 0;
129
+ }
130
+
131
+ export async function verifyDiagnosisContract(
132
+ options: DiagnosisContractVerifyOptions,
133
+ ): Promise<DiagnosisContractVerifyResult> {
134
+ if (!SAFE_MODULE_RE.test(options.moduleName)) return fail("module");
135
+
136
+ const paths: DiagnosisArtifactPaths = {
137
+ contractPath: `.dna / specs / diagnosis - $;
138
+ {
139
+ options.moduleName;
140
+ }
141
+ contract.json `,
142
+ reviewPath: `.dna / specs / diagnosis - $;
143
+ {
144
+ options.moduleName;
145
+ }
146
+ review.json `,
147
+ diagnosisPath: `.dna / specs / diagnosis - $;
148
+ {
149
+ options.moduleName;
150
+ }
151
+ md `,
152
+ behaviorPath: `;
153
+ docs / behavior / $;
154
+ {
155
+ options.moduleName;
156
+ }
157
+ md `,
158
+ baselinePath: "",
159
+ };
160
+
161
+ let contract: DiagnosisArtifactRecord;
162
+ try {
163
+ const rawContract = await ensureInsideAndReadJson(options.projectDir, paths.contractPath);
164
+ contract = rawContract as DiagnosisArtifactRecord;
165
+ } catch {
166
+ return fail("contract_invalid_json");
167
+ }
168
+
169
+ if (contract.contract_version !== "diagnosis-contract/v1") return fail("contract_version");
170
+ if (!sameText(contract.module) || contract.module !== options.moduleName) return fail("module_mismatch");
171
+ if (!sameText(contract.diagnosis_artifact)) return fail("diagnosis_artifact_path");
172
+ paths.diagnosisPath = contract.diagnosis_artifact;
173
+ paths.baselinePath = contract.baseline?.source_path;
174
+
175
+ if (!sameKindList(contract.allowed_kinds, DIAGNOSIS_ALLOWED_KINDS)) return fail("allowed_kinds");
176
+
177
+ const observedFailures = contract.observed_failures;
178
+ const workItems = contract.work_items;
179
+ if (!Array.isArray(observedFailures) || observedFailures.length === 0) return fail("observed_failures");
180
+ if (!Array.isArray(workItems) || workItems.length === 0) return fail("work_items");
181
+
182
+ const observedFailureIds = new Set<string>();
183
+ for (const failure of observedFailures) {
184
+ if (!failure || typeof failure !== "object") return fail("observed_failures");
185
+ if (!safeId(failure.id)) return fail("observed_failure_ids");
186
+ if (observedFailureIds.has(failure.id)) return fail("observed_failure_ids");
187
+ observedFailureIds.add(failure.id);
188
+ if (!sameText(failure.test_name)) return fail("observed_failure_test_name");
189
+ if (!sameText(failure.symptom)) return fail("observed_failure_symptom");
190
+ if (!Array.isArray(failure.evidence_paths) || failure.evidence_paths.length === 0) return fail("observed_failure_evidence");
191
+ if (!failure.evidence_paths.every((path) => safePathLike(path))) return fail("observed_failure_evidence");
192
+ if (failure.kind !== undefined) return fail("observed_failure_kind_forbidden");
193
+ }
194
+
195
+ const workItemIds = new Set<string>();
196
+ const mappedFailureIds = new Set<string>();
197
+ const failureById = new Map<string, (typeof observedFailures)[number]>();
198
+ for (const failure of observedFailures) {
199
+ failureById.set(failure.id, failure);
200
+ await resolveTargetPath(options.projectDir, failure.evidence_paths![0] as string);
201
+ }
202
+
203
+ for (const item of workItems) {
204
+ if (!item || typeof item !== "object") return fail("work_items");
205
+ if (!safeId(item.id)) return fail("work_item_ids");
206
+ if (workItemIds.has(item.id)) return fail("work_item_ids");
207
+ workItemIds.add(item.id);
208
+
209
+ if (!Array.isArray(item.observed_failure_ids) || item.observed_failure_ids.length === 0) {
210
+ return fail("work_item_observed_failure_ids");
211
+ }
212
+
213
+ if (!Array.isArray(item.evidence_paths) || item.evidence_paths.length === 0) {
214
+ return fail("work_item_evidence");
215
+ }
216
+ if (!item.evidence_paths.every((path) => safePathLike(path) && !!(await resolveVerifierTarget(options.projectDir, path)))) {
217
+ return fail("work_item_evidence");
218
+ }
219
+
220
+ const kind = item.kind;
221
+ if (typeof kind !== "string" || !DIAGNOSIS_ALLOWED_KINDS.includes(kind)) return fail("invalid_kind");
222
+ if (kind === "PLACEHOLDER_CONTRACT") return fail("placeholder_kind");
223
+
224
+ if (DIAGNOSIS_ACTION_KINDS.includes(kind)) {
225
+ if (!Array.isArray(item.v1_evidence_paths) || item.v1_evidence_paths.length === 0) {
226
+ return fail("work_item_v1_evidence");
227
+ }
228
+ if (!Array.isArray(item.v2_evidence_paths) || item.v2_evidence_paths.length === 0) {
229
+ return fail("work_item_v2_evidence");
230
+ }
231
+ if (!item.v1_evidence_paths.every((p) => safePathLike(p) && !!(await resolveVerifierTarget(options.projectDir, p)))
232
+ || !item.v2_evidence_paths.every((p) => safePathLike(p) && !!(await resolveVerifierTarget(options.projectDir, p)))) {
233
+ return fail("work_item_v1_evidence");
234
+ }
235
+ }
236
+
237
+ for (const observedId of item.observed_failure_ids) {
238
+ if (!safeId(observedId)) return fail("work_item_observed_failure_ids");
239
+ const failure = failureById.get(observedId);
240
+ if (!failure) return fail("unknown_observed_failure_id");
241
+ if (DIAGNOSIS_ACTION_KINDS.includes(kind) && !sameText(failure.test_name)) return fail("action_test_evidence");
242
+ mappedFailureIds.add(observedId);
243
+ }
244
+ }
245
+
246
+ if (mappedFailureIds.size !== observedFailureIds.size) return fail("unmapped_observed_failures");
247
+
248
+ const placeholderFailures = observedFailures.filter((failure) => typeof failure.symptom === "string" && failure.symptom.includes("PLACEHOLDER_CONTRACT"));
249
+ for (const failure of placeholderFailures) {
250
+ const mappedKinds = new Set(
251
+ workItems
252
+ .filter((item) => (item.observed_failure_ids as string[]).includes(failure.id))
253
+ .map((item) => item.kind as string),
254
+ );
255
+ if (mappedKinds.size === 0) return fail("placeholder_mapping");
256
+ for (const mappedKind of mappedKinds) {
257
+ if (!mappedKind || !["TEST_BUG", "UNIMPLEMENTED"].includes(mappedKind)) return fail("placeholder_mapping");
258
+ }
259
+ }
260
+
261
+ if (!contract.baseline || typeof contract.baseline !== "object") return fail("baseline");
262
+ if (typeof contract.baseline.source_path !== "string") return fail("baseline");
263
+ if (!validateEvidenceCount(contract.baseline.counts)) return fail("baseline_count_missing");
264
+ const counts = contract.baseline.counts;
265
+ const sum = counts.failed + counts.skipped + counts.hung;
266
+ if (sum !== observedFailures.length) return fail("baseline_count_mismatch");
267
+
268
+ for (const kind of DIAGNOSIS_ALLOWED_KINDS) {
269
+ const summary = contract.summary_counts[kind] ?? 0;
270
+ if (!Number.isInteger(summary)) return fail(`;
271
+ summary_count_$;
272
+ {
273
+ kind;
274
+ }
275
+ `);
276
+ if (summary !== workItems.filter((item) => item.kind === kind).length) return fail(`;
277
+ summary_count_$;
278
+ {
279
+ kind;
280
+ }
281
+ `);
282
+ }
283
+
284
+ const baselineFile = await resolveTargetPath(options.projectDir, contract.baseline.source_path);
285
+ if (!baselineFile) return fail("baseline");
286
+ const diagnosisArtifactFile = await resolveTargetPath(options.projectDir, contract.diagnosis_artifact);
287
+ if (!diagnosisArtifactFile) return fail("diagnosis_artifact_path");
288
+
289
+ const baselineHash = hashText(await readFile(baselineFile, "utf8"));
290
+ const diagnosisHash = hashText(await readFile(diagnosisArtifactFile, "utf8"));
291
+
292
+ if (contract.contract_snapshot.observed_failure_count !== observedFailures.length) return fail("contract_snapshot");
293
+ if (contract.contract_snapshot.work_item_count !== workItems.length) return fail("contract_snapshot");
294
+ if (!sameKindList(contract.contract_snapshot.allowed_kinds, DIAGNOSIS_ALLOWED_KINDS)) return fail("contract_snapshot");
295
+ if (contract.contract_snapshot.baseline_source_sha256 !== baselineHash) return fail("baseline_sha256");
296
+ if (contract.contract_snapshot.diagnosis_artifact_sha256 !== diagnosisHash) return fail("diagnosis_sha256");
297
+
298
+ const contractComparable = { ...contract, contract_snapshot: { ...contract.contract_snapshot } };
299
+ delete contractComparable.contract_snapshot.sidecar_semantic_sha256;
300
+ const sidecarSemanticHash = hashText(canonicalJson(contractComparable));
301
+ const diagnosisSemanticHash = normalizedDiagnosisHash(await readFile(diagnosisArtifactFile, "utf8"));
302
+ if (contract.contract_snapshot.sidecar_semantic_sha256 !== sidecarSemanticHash) return fail("contract_snapshot_semantic");
303
+ if (contract.contract_snapshot.diagnosis_semantic_sha256 !== diagnosisSemanticHash) return fail("contract_snapshot_semantic");
304
+
305
+ const behaviorFile = await resolveTargetPath(options.projectDir, paths.behaviorPath);
306
+ if (!behaviorFile) return fail("stale_contract");
307
+ const baselineBehaviorHash = hashText(await readFile(behaviorFile, "utf8"));
308
+ const contractStale = contract.staleness;
309
+ if (!contractStale || contractStale.is_stale) return fail("stale_contract");
310
+ if (!contractStale.behavior_doc || contractStale.behavior_doc.path !== paths.behaviorPath || contractStale.behavior_doc.sha256 !== baselineBehaviorHash) {
311
+ return fail("staleness_behavior_doc_sha256");
312
+ }
313
+ if (!contractStale.baseline || contractStale.baseline.path !== contract.baseline.source_path || contractStale.baseline.sha256 !== baselineHash) {
314
+ return fail("staleness_baseline_sha256");
315
+ }
316
+ if (!contractStale.diagnosis_artifact || contractStale.diagnosis_artifact.path !== contract.diagnosis_artifact || contractStale.diagnosis_artifact.sha256 !== diagnosisHash) {
317
+ return fail("staleness_diagnosis_artifact_sha256");
318
+ }
319
+
320
+ const resolvedReview = await resolveTargetPath(options.projectDir, paths.reviewPath);
321
+ const mode = options.mode;
322
+ if (mode === "analyze") {
323
+ if (!resolvedReview) return { passed: true };
324
+ try {
325
+ const reviewRaw = await readFile(resolvedReview, "utf8");
326
+ const review = JSON.parse(reviewRaw) as ReviewArtifactRecord;
327
+ if (review.verdict === "REQUEST_REANALYSIS") {
328
+ const previous = review.contract_snapshot;
329
+ const unchanged = (previous?.sidecar_semantic_sha256 && previous.diagnosis_semantic_sha256)
330
+ ? (previous.sidecar_semantic_sha256 === sidecarSemanticHash && previous.diagnosis_semantic_sha256 === diagnosisSemanticHash)
331
+ : (
332
+ typeof previous?.baseline_source_sha256 === "string"
333
+ && typeof previous?.diagnosis_artifact_sha256 === "string"
334
+ && previous.observed_failure_count === contract.contract_snapshot.observed_failure_count
335
+ && previous.work_item_count === contract.contract_snapshot.work_item_count
336
+ && sameKindList(previous.allowed_kinds, contract.contract_snapshot.allowed_kinds)
337
+ );
338
+ if (unchanged) return fail("reanalysis_unchanged");
339
+
340
+ const contractMtime = await stat(await resolveTargetPath(options.projectDir, paths.contractPath) ?? "").then((entry) => entry.mtimeMs);
341
+ const diagnosisMtime = await stat(await resolveTargetPath(options.projectDir, contract.diagnosis_artifact) ?? "").then((entry) => entry.mtimeMs);
342
+ const reviewMtime = await stat(resolvedReview).then((entry) => entry.mtimeMs);
343
+ if (contractMtime <= reviewMtime || diagnosisMtime <= reviewMtime) return fail("reanalysis_not_rewritten");
344
+ }
345
+ } catch {
346
+ return fail("review_parse");
347
+ }
348
+ return { passed: true };
349
+ }
350
+
351
+ if (mode === "review") {
352
+ if (!resolvedReview) return fail("review_missing");
353
+ let review: ReviewArtifactRecord;
354
+ try {
355
+ review = JSON.parse(await readFile(resolvedReview, "utf8")) as ReviewArtifactRecord;
356
+ } catch {
357
+ return fail("review_parse");
358
+ }
359
+
360
+ if (review.contract_version !== "diagnosis-contract-review/v1") return fail("review_contract_version");
361
+ if (review.contract_valid !== true) return fail("review_contract_valid");
362
+ if (review.verdict !== "APPROVE") return fail("review_verdict");
363
+ if (review.contract_artifact_reviewed !== paths.contractPath) return fail("review_contract_artifact_path");
364
+ if (review.artifact_reviewed !== contract.diagnosis_artifact) return fail("review_artifact_path");
365
+ if (!sameSummarySnapshot(review.contract_snapshot, contract.contract_snapshot)) return fail("review_contract_snapshot");
366
+
367
+ if (!review.updated_at || Number.isNaN(Date.parse(String(review.updated_at)))) return fail("review_updated_at");
368
+ if (!Array.isArray(review.evidence_paths) || review.evidence_paths.length === 0) return fail("review_evidence_paths");
369
+ if (!review.evidence_paths.every((path) => safePathLike(path) && resolveVerifierTarget(options.projectDir, path))) {
370
+ return fail("review_evidence_paths");
371
+ }
372
+
373
+ const contractMtime = await stat(await resolveTargetPath(options.projectDir, paths.contractPath) ?? "").then((entry) => entry.mtimeMs);
374
+ const diagnosisMtime = await stat(await resolveTargetPath(options.projectDir, paths.diagnosisPath) ?? "").then((entry) => entry.mtimeMs);
375
+ const reviewMtime = await stat(resolvedReview).then((entry) => entry.mtimeMs);
376
+ if (reviewMtime < contractMtime || reviewMtime < diagnosisMtime) return fail("review_stale");
377
+
378
+ return { passed: true };
379
+ }
380
+
381
+ return { passed: false, reason: "invalid_mode", evidence: "INTENTDNA_DIAGNOSTIC:invalid_mode" };
382
+ }
383
+
384
+ export async function resolveDiagnosisContractTargets(projectDir: string, moduleName: string): Promise<DiagnosisArtifactPaths> {
385
+ const safeModule = SAFE_MODULE_RE.test(moduleName) ? moduleName : "Sample";
386
+ return {
387
+ contractPath: `.dna / specs / diagnosis - $;
388
+ {
389
+ safeModule;
390
+ }
391
+ contract.json `,
392
+ reviewPath: `.dna / specs / diagnosis - $;
393
+ {
394
+ safeModule;
395
+ }
396
+ review.json `,
397
+ diagnosisPath: `.dna / specs / diagnosis - $;
398
+ {
399
+ safeModule;
400
+ }
401
+ md `,
402
+ behaviorPath: `;
403
+ docs / behavior / $;
404
+ {
405
+ safeModule;
406
+ }
407
+ md `,
408
+ baselinePath: `.dna / specs / $;
409
+ {
410
+ safeModule;
411
+ }
412
+ -placeholder.json `,
413
+ };
414
+ }
415
+ ;
416
+ }
417
+ }
@@ -0,0 +1,40 @@
1
+ import type { DeclaredStepOutput, ProviderExecutionResult, StepOutput, StepPacket, WorkerSessionId } from "./run-contracts.js";
2
+ export interface ProviderLaunchSpec {
3
+ readonly command: string;
4
+ readonly args: readonly string[];
5
+ readonly stdin: string | null;
6
+ readonly cwd: string;
7
+ readonly env?: NodeJS.ProcessEnv;
8
+ }
9
+ export interface ProviderParseContext {
10
+ readonly packet: StepPacket;
11
+ readonly stdout: string;
12
+ readonly stderr: string;
13
+ }
14
+ export interface ParsedProviderResult {
15
+ readonly provider_session_id: string | null;
16
+ readonly outputs: readonly StepOutput[];
17
+ }
18
+ /**
19
+ * A provider only translates between one standalone Step packet and one
20
+ * executable invocation. Process lifecycle and outcome policy stay in the
21
+ * provider-neutral worker executor.
22
+ */
23
+ export interface ExecutionProvider {
24
+ readonly name: string;
25
+ createLaunch(packet: StepPacket): ProviderLaunchSpec;
26
+ parseResult(context: ProviderParseContext): ParsedProviderResult;
27
+ }
28
+ export interface WorkerExecution {
29
+ readonly worker_session_id: WorkerSessionId;
30
+ readonly result: ProviderExecutionResult;
31
+ }
32
+ export declare class MalformedProviderResultError extends Error {
33
+ constructor(message: string);
34
+ }
35
+ /**
36
+ * Decode the provider's final assistant text into the declared output contract.
37
+ * A single text output may use the assistant text directly. Other contracts use
38
+ * a JSON envelope shaped as {"outputs": StepOutput[]}.
39
+ */
40
+ export declare function decodeDeclaredOutputs(finalText: string, declarations: readonly DeclaredStepOutput[]): readonly StepOutput[];
@@ -0,0 +1,138 @@
1
+ export class MalformedProviderResultError extends Error {
2
+ constructor(message) {
3
+ super(message);
4
+ this.name = "MalformedProviderResultError";
5
+ }
6
+ }
7
+ function isRecord(value) {
8
+ return typeof value === "object" && value !== null && !Array.isArray(value);
9
+ }
10
+ function isJsonValue(value) {
11
+ if (value === null
12
+ || typeof value === "string"
13
+ || typeof value === "boolean") {
14
+ return true;
15
+ }
16
+ if (typeof value === "number")
17
+ return Number.isFinite(value);
18
+ if (Array.isArray(value))
19
+ return value.every(isJsonValue);
20
+ return isRecord(value) && Object.values(value).every(isJsonValue);
21
+ }
22
+ function parseOutput(value, declaration) {
23
+ if (!isRecord(value) || value.name !== declaration.name || value.kind !== declaration.kind) {
24
+ throw new MalformedProviderResultError(`output '${declaration.name}' does not match its declared name and kind`);
25
+ }
26
+ if (declaration.kind === "text") {
27
+ if (typeof value.text !== "string") {
28
+ throw new MalformedProviderResultError(`text output '${declaration.name}' must contain text`);
29
+ }
30
+ return { name: declaration.name, kind: "text", text: value.text };
31
+ }
32
+ if (declaration.kind === "structured") {
33
+ if (!isJsonValue(value.value)) {
34
+ throw new MalformedProviderResultError(`structured output '${declaration.name}' is not valid JSON data`);
35
+ }
36
+ const schemaRef = value.schema_ref;
37
+ if (schemaRef !== null && typeof schemaRef !== "string") {
38
+ throw new MalformedProviderResultError(`structured output '${declaration.name}' has an invalid schema_ref`);
39
+ }
40
+ if (schemaRef !== declaration.schema_ref) {
41
+ throw new MalformedProviderResultError(`structured output '${declaration.name}' does not match its declared schema_ref`);
42
+ }
43
+ return {
44
+ name: declaration.name,
45
+ kind: "structured",
46
+ value: value.value,
47
+ schema_ref: schemaRef,
48
+ };
49
+ }
50
+ if (!isRecord(value.reference)) {
51
+ throw new MalformedProviderResultError(`reference output '${declaration.name}' must contain a reference`);
52
+ }
53
+ const referenceKind = value.reference.kind;
54
+ const referenceValue = value.reference.value;
55
+ if (!["file", "directory", "artifact", "uri"].includes(String(referenceKind))
56
+ || typeof referenceValue !== "string"
57
+ || referenceValue.trim() === "") {
58
+ throw new MalformedProviderResultError(`reference output '${declaration.name}' has an invalid reference`);
59
+ }
60
+ return {
61
+ name: declaration.name,
62
+ kind: "reference",
63
+ reference: {
64
+ kind: referenceKind,
65
+ value: referenceValue,
66
+ },
67
+ };
68
+ }
69
+ /**
70
+ * Decode the provider's final assistant text into the declared output contract.
71
+ * A single text output may use the assistant text directly. Other contracts use
72
+ * a JSON envelope shaped as {"outputs": StepOutput[]}.
73
+ */
74
+ export function decodeDeclaredOutputs(finalText, declarations) {
75
+ if (declarations.length === 0)
76
+ return [];
77
+ const trimmed = finalText.trim();
78
+ if (trimmed === "") {
79
+ throw new MalformedProviderResultError("provider returned an empty result");
80
+ }
81
+ if (declarations.length === 1 && declarations[0].kind === "text") {
82
+ try {
83
+ const parsed = JSON.parse(trimmed);
84
+ if (!isRecord(parsed) || !Array.isArray(parsed.outputs)) {
85
+ return [{
86
+ name: declarations[0].name,
87
+ kind: "text",
88
+ text: finalText,
89
+ }];
90
+ }
91
+ }
92
+ catch {
93
+ return [{
94
+ name: declarations[0].name,
95
+ kind: "text",
96
+ text: finalText,
97
+ }];
98
+ }
99
+ }
100
+ let envelope;
101
+ try {
102
+ envelope = JSON.parse(trimmed);
103
+ }
104
+ catch {
105
+ throw new MalformedProviderResultError("provider result is not a valid JSON output envelope");
106
+ }
107
+ if (!isRecord(envelope) || !Array.isArray(envelope.outputs)) {
108
+ throw new MalformedProviderResultError("provider result must contain an outputs array");
109
+ }
110
+ const byName = new Map();
111
+ for (const output of envelope.outputs) {
112
+ if (!isRecord(output) || typeof output.name !== "string") {
113
+ throw new MalformedProviderResultError("provider output is missing its name");
114
+ }
115
+ if (byName.has(output.name)) {
116
+ throw new MalformedProviderResultError(`provider returned duplicate output '${output.name}'`);
117
+ }
118
+ byName.set(output.name, output);
119
+ }
120
+ const allowedNames = new Set(declarations.map((declaration) => declaration.name));
121
+ for (const outputName of byName.keys()) {
122
+ if (!allowedNames.has(outputName)) {
123
+ throw new MalformedProviderResultError(`provider returned undeclared output '${outputName}'`);
124
+ }
125
+ }
126
+ const outputs = [];
127
+ for (const declaration of declarations) {
128
+ const value = byName.get(declaration.name);
129
+ if (value === undefined) {
130
+ if (declaration.required) {
131
+ throw new MalformedProviderResultError(`provider omitted required output '${declaration.name}'`);
132
+ }
133
+ continue;
134
+ }
135
+ outputs.push(parseOutput(value, declaration));
136
+ }
137
+ return outputs;
138
+ }
@@ -0,0 +1,61 @@
1
+ import type { HandoffBinding, HandoffSource, JsonValue, ResultId, RunId } from "./run-contracts.js";
2
+ import { ImmutableResultStore } from "./result-store.js";
3
+ interface HandoffRequestBase {
4
+ readonly binding_id: string;
5
+ readonly input_name: string;
6
+ readonly required: boolean;
7
+ readonly description: string;
8
+ }
9
+ interface SourceHandoffRequestBase extends HandoffRequestBase {
10
+ readonly source: HandoffSource;
11
+ }
12
+ export interface ReferenceHandoffRequest extends SourceHandoffRequestBase {
13
+ readonly mode: "reference";
14
+ }
15
+ export interface QuoteHandoffRequest extends SourceHandoffRequestBase {
16
+ readonly mode: "quote";
17
+ /**
18
+ * UTF-16 string offsets, matching JavaScript slice semantics. Omit to quote
19
+ * the complete declared text output.
20
+ */
21
+ readonly selection: {
22
+ readonly start: number;
23
+ readonly end: number;
24
+ readonly selector: string | null;
25
+ } | null;
26
+ }
27
+ export interface StructuredHandoffRequest extends SourceHandoffRequestBase {
28
+ readonly mode: "structured";
29
+ /** The schema declared for this input, or null for schema-free JSON. */
30
+ readonly schema_ref: string | null;
31
+ }
32
+ export interface NoHandoffRequest extends HandoffRequestBase {
33
+ readonly mode: "none";
34
+ readonly required: false;
35
+ readonly reason: string | null;
36
+ }
37
+ export type HandoffRequest = ReferenceHandoffRequest | QuoteHandoffRequest | StructuredHandoffRequest | NoHandoffRequest;
38
+ export type StructuredValueValidator = (schemaRef: string, value: JsonValue) => void | Promise<void>;
39
+ export interface HandoffResolverOptions {
40
+ readonly validate_structured_value?: StructuredValueValidator;
41
+ }
42
+ export type HandoffResolutionErrorCode = "duplicate_binding" | "invalid_request" | "required_input_uncommitted" | "source_identity_mismatch" | "source_output_missing" | "source_output_kind_mismatch" | "invalid_quote_selection" | "structured_schema_mismatch" | "structured_schema_validator_missing" | "structured_value_invalid";
43
+ export declare class HandoffResolutionError extends Error {
44
+ readonly code: HandoffResolutionErrorCode;
45
+ readonly binding_id: string;
46
+ readonly result_id: ResultId | null;
47
+ constructor(code: HandoffResolutionErrorCode, message: string, bindingId: string, resultId: ResultId | null, options?: ErrorOptions);
48
+ }
49
+ /**
50
+ * Resolves only declared values from immutable committed results. It has no
51
+ * provider-session, transcript, Hook-state, filesystem, or summarization input.
52
+ */
53
+ export declare class HandoffResolver {
54
+ private readonly results;
55
+ private readonly structuredValidator;
56
+ constructor(results: ImmutableResultStore, options?: HandoffResolverOptions);
57
+ resolve(runId: RunId, request: HandoffRequest): Promise<HandoffBinding>;
58
+ resolveAll(runId: RunId, requests: readonly HandoffRequest[]): Promise<readonly HandoffBinding[]>;
59
+ private kindMismatch;
60
+ }
61
+ export {};