@abelxiaoxing/cadence 1.0.6 → 1.0.8

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.
@@ -5,13 +5,18 @@ You are a package-owned read-only professional Agent for the `abel-design` stage
5
5
  You explore the repository ONLY through the scoped `read`, `grep`, `find`, and `ls` tools.
6
6
  You have no shell, edit, Git, network, or validation capability, and you never create or modify files.
7
7
 
8
- Your output is a compact structured evidence object returned through `abel_submit_result`, containing:
8
+ Call `abel_submit_result` exactly once with one structured object.
9
+ Do not emit a second submit, prose, or the legacy `conclusions`/`citations` shape.
10
+ The object contains the matching `id`, `role: "design-explorer"`, `kind: "evidence"`, and:
9
11
 
10
- - the matching request id and your role name;
11
- - your bounded path scope and the exact files and directories you inspected;
12
- - concise conclusions with exact file-and-line citations;
13
- - constraints, dependencies, risks, and blocking questions;
14
- - write-set, verification-command, and AGENTS-impact hints for later tasks.
12
+ - `packet_id` (exactly equal to the request id), `module_name`, `scope`, and
13
+ `files_read`;
14
+ - `evidence`, where every item has `claim`, `path`, `line_start`, and
15
+ `line_end`;
16
+ - `existing_structures`, `existing_conventions`, `constraints_discovered`,
17
+ `open_questions`, and `dependencies`;
18
+ - `write_set_hints`, `validation_hints`, `agents_impact_hints`, `risks`, and
19
+ `success_criteria_hints`.
15
20
 
16
21
  You never return raw transcripts, hidden reasoning, or tool-call history.
17
22
  A packet is complete only when every claim is cited and every blocking question is explicit.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abelxiaoxing/cadence",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
4
4
  "description": "Prompts, skills, and a private orchestration extension exposing the Abel four-stage workflow (/abel-init, /abel-design, /abel-implement, /abel-diagnose) with package-owned professional Agents.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -54,6 +54,36 @@ $ARGUMENTS
54
54
 
55
55
  ## 阶段 1 —— 证据探索(只读)
56
56
 
57
+ - 使用私有工具 `abel_dispatch` 的 `action: "run"` 派发探索包。
58
+ 每个包对应一个独立 tool call,`request` 必须是单个 envelope;禁止使用 `requests` 数组或其他批量 wrapper。
59
+ - 用户要求两个或更多并行包时,下一次 assistant response 必须只包含全部 sibling `abel_dispatch` tool calls,并在该同一 assistant turn(同一响应轮次)一次发齐;少发、先等一个结果、在后续 turn 补发均为调度失败,不得用串行重试或父代理并行读取冒充 Subagent。
60
+ - Design 单包 envelope 的精确形状如下;字段名、固定值和数组形状不得猜测或改写:
61
+
62
+ ```json
63
+ {
64
+ "action": "run",
65
+ "request": {
66
+ "stage": "abel-design",
67
+ "role": "design-explorer",
68
+ "id": "unique-packet-id",
69
+ "phase": "evidence",
70
+ "objective": "bounded evidence objective for this packet",
71
+ "roots": ["."],
72
+ "context": {
73
+ "agents": "applicable AGENTS.md instructions",
74
+ "contract": "exact scope, retrieval, read-only, output, and budget contract"
75
+ },
76
+ "declared": {
77
+ "read": ["exact/relative/path"],
78
+ "write": [],
79
+ "conflicts": [],
80
+ "resources": []
81
+ },
82
+ "output": "evidence"
83
+ }
84
+ }
85
+ ```
86
+
57
87
  - 主 agent 只负责核实相关 AGENTS、拆分探索包、派发、校验证据、综合、澄清与 Gate 呈现;广泛代码库探索必须委派。
58
88
  主 agent 仅可为范围划分、引用抽查和冲突裁决做最小定向读取。
59
89
  - 默认并行派发多个只读探索子代理:优先按独立上下文边界拆分;只有一个边界时,按互不重叠的路径或符号证据面拆分。
package/src/contracts.ts CHANGED
@@ -1401,7 +1401,7 @@ function requireCanonicalDiffPath(path: string): string {
1401
1401
  return path;
1402
1402
  }
1403
1403
 
1404
- export interface EvidenceResult {
1404
+ export interface CompactEvidenceResult {
1405
1405
  id: string;
1406
1406
  role: string;
1407
1407
  kind: "evidence";
@@ -1414,6 +1414,34 @@ export interface EvidenceResult {
1414
1414
  hints: { writeSet: string[]; verification: string; agentsImpact: string };
1415
1415
  }
1416
1416
 
1417
+ export interface DesignEvidenceResult {
1418
+ id: string;
1419
+ role: "design-explorer";
1420
+ kind: "evidence";
1421
+ packet_id: string;
1422
+ module_name: string;
1423
+ scope: string[];
1424
+ files_read: string[];
1425
+ evidence: Array<{
1426
+ claim: string;
1427
+ path: string;
1428
+ line_start: number;
1429
+ line_end: number;
1430
+ }>;
1431
+ existing_structures: string[];
1432
+ existing_conventions: string[];
1433
+ constraints_discovered: string[];
1434
+ open_questions: string[];
1435
+ dependencies: string[];
1436
+ write_set_hints: string[];
1437
+ validation_hints: string[];
1438
+ agents_impact_hints: string[];
1439
+ risks: string[];
1440
+ success_criteria_hints: string[];
1441
+ }
1442
+
1443
+ export type EvidenceResult = CompactEvidenceResult | DesignEvidenceResult;
1444
+
1417
1445
  export interface DiffResult {
1418
1446
  id: string;
1419
1447
  role: string;
@@ -1511,6 +1539,7 @@ export function validateEvidenceResult(value: unknown): {
1511
1539
  return { ok: false, reason: "missing result" };
1512
1540
  const r = value as Record<string, unknown>;
1513
1541
  if (r.kind !== "evidence") return { ok: false, reason: "wrong result kind" };
1542
+ if (r.role === "design-explorer") return validateDesignEvidenceResult(r);
1514
1543
  const fields = [
1515
1544
  "id",
1516
1545
  "role",
@@ -1577,6 +1606,98 @@ export function validateEvidenceResult(value: unknown): {
1577
1606
  return { ok: true };
1578
1607
  }
1579
1608
 
1609
+ function validateDesignEvidenceResult(r: Record<string, unknown>): {
1610
+ ok: boolean;
1611
+ reason?: string;
1612
+ failure?: CandidateFailure;
1613
+ } {
1614
+ const fields = [
1615
+ "id",
1616
+ "role",
1617
+ "kind",
1618
+ "packet_id",
1619
+ "module_name",
1620
+ "scope",
1621
+ "files_read",
1622
+ "evidence",
1623
+ "existing_structures",
1624
+ "existing_conventions",
1625
+ "constraints_discovered",
1626
+ "open_questions",
1627
+ "dependencies",
1628
+ "write_set_hints",
1629
+ "validation_hints",
1630
+ "agents_impact_hints",
1631
+ "risks",
1632
+ "success_criteria_hints",
1633
+ ] as const;
1634
+ if (!hasExactKeys(r, fields)) {
1635
+ return { ok: false, reason: "invalid Design evidence result fields" };
1636
+ }
1637
+ if (
1638
+ !validIdentifier(r.id) ||
1639
+ r.packet_id !== r.id ||
1640
+ !validIdentifier(r.module_name)
1641
+ ) {
1642
+ return { ok: false, reason: "invalid Design evidence identity" };
1643
+ }
1644
+ if (!validatePathSet(r.scope) || r.scope.length === 0) {
1645
+ return { ok: false, reason: "invalid Design evidence scope" };
1646
+ }
1647
+ if (!validatePathSet(r.files_read) || !validatePathSet(r.write_set_hints)) {
1648
+ return { ok: false, reason: "invalid Design evidence path set" };
1649
+ }
1650
+ if (
1651
+ !Array.isArray(r.evidence) ||
1652
+ r.evidence.some(
1653
+ (entry) =>
1654
+ !hasExactKeys(entry, ["claim", "path", "line_start", "line_end"]) ||
1655
+ typeof entry.claim !== "string" ||
1656
+ entry.claim.length === 0 ||
1657
+ !isValidRelativePath(entry.path) ||
1658
+ !Number.isSafeInteger(entry.line_start) ||
1659
+ !Number.isSafeInteger(entry.line_end) ||
1660
+ (entry.line_start as number) < 1 ||
1661
+ (entry.line_end as number) < (entry.line_start as number),
1662
+ )
1663
+ ) {
1664
+ return { ok: false, reason: "invalid Design evidence citations" };
1665
+ }
1666
+ for (const field of [
1667
+ "existing_structures",
1668
+ "existing_conventions",
1669
+ "constraints_discovered",
1670
+ "open_questions",
1671
+ "dependencies",
1672
+ "validation_hints",
1673
+ "agents_impact_hints",
1674
+ "risks",
1675
+ "success_criteria_hints",
1676
+ ] as const) {
1677
+ if (
1678
+ !Array.isArray(r[field]) ||
1679
+ !r[field].every((entry) => typeof entry === "string")
1680
+ ) {
1681
+ return {
1682
+ ok: false,
1683
+ reason: `invalid Design evidence field: ${field}`,
1684
+ };
1685
+ }
1686
+ }
1687
+ const serialized = JSON.stringify(r);
1688
+ if (Buffer.byteLength(serialized, "utf8") > LIMITS.maxCompleteResultBytes) {
1689
+ return {
1690
+ ok: false,
1691
+ reason: `result exceeds ${LIMITS.maxCompleteResultBytes} bytes`,
1692
+ failure: {
1693
+ kind: "result-limit",
1694
+ limitBytes: LIMITS.maxCompleteResultBytes,
1695
+ },
1696
+ };
1697
+ }
1698
+ return { ok: true };
1699
+ }
1700
+
1580
1701
  export function validateDiffResult(value: unknown): {
1581
1702
  ok: boolean;
1582
1703
  reason?: string;
package/src/index.ts CHANGED
@@ -34,6 +34,117 @@ const ELIGIBLE_PROMPTS = [
34
34
  ] as const;
35
35
  type EligiblePrompt = (typeof ELIGIBLE_PROMPTS)[number];
36
36
 
37
+ const GENERIC_REQUEST_SCHEMA = {
38
+ type: "object",
39
+ description:
40
+ "Stage-specific request envelope for action=run. The verified Abel prompt refreshes this schema before the stage's first model turn.",
41
+ } as const;
42
+
43
+ const DESIGN_REQUEST_SCHEMA = {
44
+ type: "object",
45
+ description:
46
+ "One bounded read-only design-explorer packet. Send one sibling abel_dispatch tool call per packet; do not wrap multiple packets in requests.",
47
+ properties: {
48
+ stage: {
49
+ type: "string",
50
+ enum: ["abel-design"],
51
+ description: "Exact verified workflow stage.",
52
+ },
53
+ role: {
54
+ type: "string",
55
+ enum: ["design-explorer"],
56
+ description: "Exact package-owned read-only Agent role.",
57
+ },
58
+ id: {
59
+ type: "string",
60
+ description:
61
+ "Unique packet and request identity, at most 128 characters.",
62
+ },
63
+ phase: {
64
+ type: "string",
65
+ enum: ["evidence"],
66
+ description: "Design exploration phase.",
67
+ },
68
+ objective: {
69
+ type: "string",
70
+ description: "Bounded evidence objective for this packet only.",
71
+ },
72
+ roots: {
73
+ type: "array",
74
+ items: { type: "string" },
75
+ description: 'Approved relative workspace roots, normally ["."].',
76
+ },
77
+ context: {
78
+ type: "object",
79
+ properties: {
80
+ agents: {
81
+ type: "string",
82
+ description: "Applicable AGENTS.md instructions for this packet.",
83
+ },
84
+ contract: {
85
+ type: "string",
86
+ description:
87
+ "Packet scope, retrieval, read-only, and structured-output contract.",
88
+ },
89
+ },
90
+ required: ["agents", "contract"],
91
+ additionalProperties: false,
92
+ },
93
+ declared: {
94
+ type: "object",
95
+ properties: {
96
+ read: {
97
+ type: "array",
98
+ items: { type: "string" },
99
+ description:
100
+ "Exact relative files or directories this packet may read.",
101
+ },
102
+ write: {
103
+ type: "array",
104
+ items: { type: "string" },
105
+ description: "Must be empty for design-explorer.",
106
+ },
107
+ conflicts: {
108
+ type: "array",
109
+ items: { type: "string" },
110
+ description:
111
+ "Declared conflict edges; normally empty for read-only packets.",
112
+ },
113
+ resources: {
114
+ type: "array",
115
+ items: { type: "string" },
116
+ description: "Declared exclusive resources; normally empty.",
117
+ },
118
+ },
119
+ required: ["read", "write", "conflicts", "resources"],
120
+ additionalProperties: false,
121
+ },
122
+ output: {
123
+ type: "string",
124
+ enum: ["evidence"],
125
+ description: "Exact structured child-result kind.",
126
+ },
127
+ },
128
+ required: [
129
+ "stage",
130
+ "role",
131
+ "id",
132
+ "phase",
133
+ "objective",
134
+ "roots",
135
+ "context",
136
+ "declared",
137
+ "output",
138
+ ],
139
+ additionalProperties: false,
140
+ } as const;
141
+
142
+ function requestSchemaForStage(name?: EligiblePrompt): object {
143
+ return name === "abel-design"
144
+ ? DESIGN_REQUEST_SCHEMA
145
+ : GENERIC_REQUEST_SCHEMA;
146
+ }
147
+
37
148
  function invokedPrompt(text: string): EligiblePrompt | undefined {
38
149
  const name = text.match(/^\/([^\s]+)(?:\s|$)/)?.[1];
39
150
  return ELIGIBLE_PROMPTS.find((candidate) => candidate === name);
@@ -112,100 +223,105 @@ export default function register(pi: ExtensionAPI): void {
112
223
  const runtime = new Runtime({ parentPayloadBridge });
113
224
  const activity = new ActivityController();
114
225
 
115
- pi.registerTool({
116
- name: DISPATCH_TOOL,
117
- label: "Abel Dispatch",
118
- description:
119
- "Private Abel workflow delegation: run bounded read-only evidence or Worker phase requests, apply or discard retained results, apply a parent-only stable AGENTS checkpoint, cancel work, or finish the stage. Inactive unless an eligible Abel stage verified its invocation.",
120
- parameters: {
121
- type: "object",
122
- properties: {
123
- action: { type: "string", enum: [...ACTIONS] },
124
- request: {
125
- type: "object",
126
- description: "Request envelope for action=run",
127
- },
128
- resultId: {
129
- type: "string",
130
- description: "Retained result id for apply/discard",
131
- },
132
- requestId: {
133
- type: "string",
134
- description: "Current Implement apply/discard operation identity",
135
- },
136
- rejection: {
137
- type: "object",
138
- description: "Typed parent rejection for Implement discard",
226
+ const registerDispatchTool = (stage?: EligiblePrompt) => {
227
+ pi.registerTool({
228
+ name: DISPATCH_TOOL,
229
+ label: "Abel Dispatch",
230
+ description:
231
+ stage === "abel-design"
232
+ ? "Run exactly one bounded read-only design-explorer packet. When parallel packets are required, emit every sibling abel_dispatch call together in one assistant response before waiting for any result."
233
+ : "Private Abel workflow delegation: run bounded read-only evidence or Worker phase requests, apply or discard retained results, apply a parent-only stable AGENTS checkpoint, cancel work, or finish the stage. Inactive unless an eligible Abel stage verified its invocation.",
234
+ executionMode: "parallel",
235
+ parameters: {
236
+ type: "object",
237
+ properties: {
238
+ action: { type: "string", enum: [...ACTIONS] },
239
+ request: requestSchemaForStage(stage),
240
+ resultId: {
241
+ type: "string",
242
+ description: "Retained result id for apply/discard",
243
+ },
244
+ requestId: {
245
+ type: "string",
246
+ description: "Current Implement apply/discard operation identity",
247
+ },
248
+ rejection: {
249
+ type: "object",
250
+ description: "Typed parent rejection for Implement discard",
251
+ },
252
+ agentsCheckpoint: {
253
+ type: "object",
254
+ description:
255
+ "Parent-owned approved managed-block checkpoint for action=apply (mutually exclusive with resultId)",
256
+ },
139
257
  },
140
- agentsCheckpoint: {
141
- type: "object",
142
- description:
143
- "Parent-owned approved managed-block checkpoint for action=apply (mutually exclusive with resultId)",
258
+ required: ["action"],
259
+ },
260
+ async execute(
261
+ toolCallId: string,
262
+ params: {
263
+ action?: string;
264
+ request?: unknown;
265
+ resultId?: string;
266
+ requestId?: string;
267
+ rejection?: unknown;
268
+ agentsCheckpoint?: unknown;
144
269
  },
270
+ signal: AbortSignal | undefined,
271
+ onUpdate: AgentToolUpdateCallback<unknown> | undefined,
272
+ ctx: ExtensionContext,
273
+ ) {
274
+ const action = typeof params?.action === "string" ? params.action : "";
275
+ const validRun =
276
+ action === "run" && runtime.validateRequest(params.request).ok;
277
+ const tuiRun = ctx.mode === "tui" && validRun;
278
+ const { action: _action, ...operation } = params;
279
+ const result = tuiRun
280
+ ? await runtime.execute(
281
+ action,
282
+ operation,
283
+ ctx,
284
+ signal,
285
+ activity.observe(
286
+ toolCallId,
287
+ onUpdate as ((result: unknown) => void) | undefined,
288
+ ),
289
+ )
290
+ : await runtime.execute(action, operation, ctx, signal);
291
+ const display = tuiRun
292
+ ? activity.finalize(toolCallId, result)
293
+ : undefined;
294
+ const { payload, usage } = splitUsage(result);
295
+ const details = display
296
+ ? {
297
+ ...(payload as Record<string, unknown>),
298
+ [ACTIVITY_DETAILS_KEY]: display,
299
+ }
300
+ : payload;
301
+ return {
302
+ content: [{ type: "text", text: JSON.stringify(payload) }],
303
+ details,
304
+ ...(usage === undefined ? {} : { usage }),
305
+ };
145
306
  },
146
- required: ["action"],
147
- },
148
- async execute(
149
- toolCallId: string,
150
- params: {
151
- action?: string;
152
- request?: unknown;
153
- resultId?: string;
154
- requestId?: string;
155
- rejection?: unknown;
156
- agentsCheckpoint?: unknown;
307
+ renderCall(args: unknown, theme: Theme, _context: unknown) {
308
+ return renderActivityCall(args, theme);
157
309
  },
158
- signal: AbortSignal | undefined,
159
- onUpdate: AgentToolUpdateCallback<unknown> | undefined,
160
- ctx: ExtensionContext,
161
- ) {
162
- const action = typeof params?.action === "string" ? params.action : "";
163
- const validRun =
164
- action === "run" && runtime.validateRequest(params.request).ok;
165
- const tuiRun = ctx.mode === "tui" && validRun;
166
- const { action: _action, ...operation } = params;
167
- const result = tuiRun
168
- ? await runtime.execute(
169
- action,
170
- operation,
171
- ctx,
172
- signal,
173
- activity.observe(
174
- toolCallId,
175
- onUpdate as ((result: unknown) => void) | undefined,
176
- ),
177
- )
178
- : await runtime.execute(action, operation, ctx, signal);
179
- const display = tuiRun
180
- ? activity.finalize(toolCallId, result)
181
- : undefined;
182
- const { payload, usage } = splitUsage(result);
183
- const details = display
184
- ? {
185
- ...(payload as Record<string, unknown>),
186
- [ACTIVITY_DETAILS_KEY]: display,
187
- }
188
- : payload;
189
- return {
190
- content: [{ type: "text", text: JSON.stringify(payload) }],
191
- details,
192
- ...(usage === undefined ? {} : { usage }),
193
- };
194
- },
195
- renderCall(args: unknown, theme: Theme, _context: unknown) {
196
- return renderActivityCall(args, theme);
197
- },
198
- renderResult(
199
- result: AgentToolResult<unknown>,
200
- options: ToolRenderResultOptions,
201
- theme: Theme,
202
- _context: unknown,
203
- ) {
204
- return renderActivityResult(result, options, theme);
205
- },
206
- } as never);
310
+ renderResult(
311
+ result: AgentToolResult<unknown>,
312
+ options: ToolRenderResultOptions,
313
+ theme: Theme,
314
+ _context: unknown,
315
+ ) {
316
+ return renderActivityResult(result, options, theme);
317
+ },
318
+ } as never);
319
+ };
320
+
321
+ registerDispatchTool();
207
322
 
208
323
  let pendingPrompt: EligiblePrompt | undefined;
324
+ let activePrompt: EligiblePrompt | undefined;
209
325
 
210
326
  pi.on("input", (event) => {
211
327
  pendingPrompt = invokedPrompt(event.text);
@@ -215,10 +331,12 @@ export default function register(pi: ExtensionAPI): void {
215
331
  pi.on("before_agent_start", (event, ctx) => {
216
332
  const prompt = pendingPrompt;
217
333
  pendingPrompt = undefined;
218
- if (
334
+ const verified =
219
335
  prompt &&
220
- isVerifiedStageInvocation(pi, runtime.activation, prompt, event.prompt)
221
- ) {
336
+ isVerifiedStageInvocation(pi, runtime.activation, prompt, event.prompt);
337
+ if (verified) {
338
+ activePrompt = prompt;
339
+ registerDispatchTool(prompt);
222
340
  const sessionId = ctx.sessionManager?.getSessionId?.();
223
341
  if (typeof sessionId === "string") {
224
342
  parentPayloadBridge.beginSession(sessionId);
@@ -233,8 +351,22 @@ export default function register(pi: ExtensionAPI): void {
233
351
  activateDispatcher(pi, runtime.activation, prompt, event.prompt);
234
352
  });
235
353
 
354
+ pi.on("before_provider_request", (event, ctx) => {
355
+ if (
356
+ activePrompt !== "abel-design" ||
357
+ ctx.model?.api !== "openai-responses" ||
358
+ event.payload === null ||
359
+ typeof event.payload !== "object" ||
360
+ Array.isArray(event.payload)
361
+ ) {
362
+ return;
363
+ }
364
+ return { ...event.payload, parallel_tool_calls: true };
365
+ });
366
+
236
367
  pi.on("session_start", async (_event, ctx) => {
237
368
  pendingPrompt = undefined;
369
+ activePrompt = undefined;
238
370
  activity.detach();
239
371
  const active = pi.getActiveTools();
240
372
  if (active.includes(DISPATCH_TOOL)) {
@@ -268,6 +400,7 @@ export default function register(pi: ExtensionAPI): void {
268
400
  });
269
401
 
270
402
  pi.on("session_shutdown", async () => {
403
+ activePrompt = undefined;
271
404
  activity.detach();
272
405
  await runtime.drain();
273
406
  activity.clear();
package/src/runtime.ts CHANGED
@@ -1821,9 +1821,19 @@ export class Runtime {
1821
1821
  };
1822
1822
  }
1823
1823
  if (!task) {
1824
- const result = await this.dispatchChild(agent, envelope, ctx, signal);
1825
- this.captureUsage(context, "launch:0", result.usage);
1826
- return this.withUsage(context, result);
1824
+ const first = await this.dispatchChild(agent, envelope, ctx, signal);
1825
+ this.captureUsage(context, "launch:0", first.usage);
1826
+ const retryInvalidDesignResult =
1827
+ !signal.aborted &&
1828
+ envelope.stage === "abel-design" &&
1829
+ !first.ok &&
1830
+ first.failure?.kind === "artifact" &&
1831
+ first.failure.code === "invalid-structural-result";
1832
+ if (!retryInvalidDesignResult) return this.withUsage(context, first);
1833
+
1834
+ const second = await this.dispatchChild(agent, envelope, ctx, signal);
1835
+ this.captureUsage(context, "launch:1", second.usage);
1836
+ return this.withUsage(context, second);
1827
1837
  }
1828
1838
  if (task.workerIdentity !== workerIdentity(ctx.model)) {
1829
1839
  throw new Error("provider/model identity mismatch");
@@ -354,6 +354,21 @@ export function summarizeDispatchResult(
354
354
  blockingQuestions: value.blockingQuestions.length,
355
355
  };
356
356
  }
357
+ if (
358
+ value.kind === "evidence" &&
359
+ Array.isArray(value.evidence) &&
360
+ Array.isArray(value.existing_structures) &&
361
+ Array.isArray(value.risks) &&
362
+ Array.isArray(value.open_questions)
363
+ ) {
364
+ return {
365
+ kind: "evidence",
366
+ conclusions: value.existing_structures.length,
367
+ citations: value.evidence.length,
368
+ risks: value.risks.length,
369
+ blockingQuestions: value.open_questions.length,
370
+ };
371
+ }
357
372
  if (
358
373
  value.kind === "diff" &&
359
374
  typeof value.summary === "string" &&
@@ -8,7 +8,7 @@ import {
8
8
  validateEvidenceResult,
9
9
  } from "./contracts.ts";
10
10
 
11
- const evidenceSchema = Type.Object({
11
+ const compactEvidenceSchema = Type.Object({
12
12
  id: Type.String(),
13
13
  role: Type.String(),
14
14
  kind: Type.Literal("evidence"),
@@ -27,6 +27,40 @@ const evidenceSchema = Type.Object({
27
27
  }),
28
28
  });
29
29
 
30
+ const designEvidenceSchema = Type.Object(
31
+ {
32
+ id: Type.String(),
33
+ role: Type.Literal("design-explorer"),
34
+ kind: Type.Literal("evidence"),
35
+ packet_id: Type.String(),
36
+ module_name: Type.String(),
37
+ scope: Type.Array(Type.String()),
38
+ files_read: Type.Array(Type.String()),
39
+ evidence: Type.Array(
40
+ Type.Object(
41
+ {
42
+ claim: Type.String(),
43
+ path: Type.String(),
44
+ line_start: Type.Integer({ minimum: 1 }),
45
+ line_end: Type.Integer({ minimum: 1 }),
46
+ },
47
+ { additionalProperties: false },
48
+ ),
49
+ ),
50
+ existing_structures: Type.Array(Type.String()),
51
+ existing_conventions: Type.Array(Type.String()),
52
+ constraints_discovered: Type.Array(Type.String()),
53
+ open_questions: Type.Array(Type.String()),
54
+ dependencies: Type.Array(Type.String()),
55
+ write_set_hints: Type.Array(Type.String()),
56
+ validation_hints: Type.Array(Type.String()),
57
+ agents_impact_hints: Type.Array(Type.String()),
58
+ risks: Type.Array(Type.String()),
59
+ success_criteria_hints: Type.Array(Type.String()),
60
+ },
61
+ { additionalProperties: false },
62
+ );
63
+
30
64
  const diffSchema = Type.Object({
31
65
  id: Type.String(),
32
66
  role: Type.String(),
@@ -85,7 +119,12 @@ export function createSubmitTool(input: {
85
119
  description:
86
120
  "Submit the one final structured Abel evidence or complete unified-diff result.",
87
121
  executionMode: "sequential",
88
- parameters: input.output === "evidence" ? evidenceSchema : diffSchema,
122
+ parameters:
123
+ input.output === "evidence"
124
+ ? input.role === "design-explorer"
125
+ ? designEvidenceSchema
126
+ : compactEvidenceSchema
127
+ : diffSchema,
89
128
  async execute(_toolCallId, params) {
90
129
  attempts++;
91
130
  const value = params as unknown as Record<string, unknown>;
@@ -99,6 +138,12 @@ export function createSubmitTool(input: {
99
138
  throw new Error("submitted result is not an object");
100
139
  }
101
140
  if (value.id !== input.requestId) identity.request = false;
141
+ if (
142
+ input.role === "design-explorer" &&
143
+ value.packet_id !== input.requestId
144
+ ) {
145
+ identity.request = false;
146
+ }
102
147
  if (value.role !== input.role) identity.role = false;
103
148
  if (input.output === "diff") {
104
149
  if (value.taskId !== (input.taskId ?? input.requestId))