@abelxiaoxing/cadence 1.4.0 → 1.4.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 (49) hide show
  1. package/README.md +58 -4
  2. package/agents/design-explorer.md +17 -11
  3. package/agents/diagnosis-worker.md +9 -1
  4. package/agents/implementation-worker.md +3 -1
  5. package/config/plan-draft.example.json +122 -0
  6. package/config/plan-draft.multiple-tasks.example.json +345 -0
  7. package/package.json +5 -3
  8. package/prompts/abel-design.md +93 -44
  9. package/src/activity-contracts.ts +82 -0
  10. package/src/candidate-artifact.ts +143 -0
  11. package/src/candidate-patch.ts +31 -11
  12. package/src/candidate-workspace.ts +74 -0
  13. package/src/change-contract-schema.ts +191 -0
  14. package/src/change-contract.ts +5 -0
  15. package/src/change-verification.ts +896 -0
  16. package/src/child-model.ts +129 -0
  17. package/src/child-session.ts +293 -326
  18. package/src/contracts.ts +184 -76
  19. package/src/delivery-compiler.ts +103 -120
  20. package/src/delivery-revision.ts +42 -0
  21. package/src/design-control.ts +113 -4
  22. package/src/design-diagnostics.ts +150 -0
  23. package/src/durable-contracts.ts +210 -0
  24. package/src/durable-workflow.ts +148 -3570
  25. package/src/evidence-draft.ts +122 -0
  26. package/src/implement-graph.ts +3 -0
  27. package/src/implement-plan.ts +70 -0
  28. package/src/index.ts +59 -522
  29. package/src/model-source.ts +22 -0
  30. package/src/package-candidate.ts +224 -0
  31. package/src/package-workflow.ts +253 -0
  32. package/src/packet-runtime.ts +27 -48
  33. package/src/parent-provider.ts +86 -198
  34. package/src/phase-execution.ts +2248 -0
  35. package/src/pi-adapter.ts +19 -0
  36. package/src/plan-draft-summary.ts +146 -0
  37. package/src/plan-draft.ts +493 -0
  38. package/src/subagent-activity.ts +32 -66
  39. package/src/submit-tool.ts +20 -59
  40. package/src/transport-budget.ts +32 -0
  41. package/src/worker-broker.ts +37 -57
  42. package/src/workflow-policy.ts +14 -53
  43. package/src/workflow-recovery-policy.ts +95 -0
  44. package/src/workflow-scheduling.ts +105 -0
  45. package/src/workflow-state-machine.ts +105 -497
  46. package/src/workflow-status.ts +384 -0
  47. package/src/workspace-io-worker.mjs +7 -0
  48. package/src/empty-resource-loader.ts +0 -38
  49. package/src/parent-payload-bridge.ts +0 -523
package/src/contracts.ts CHANGED
@@ -52,6 +52,14 @@ export const LIMITS = {
52
52
  maxCompleteResultBytes: 64 * 1024,
53
53
  } as const;
54
54
 
55
+ /** Shared collection bounds for runtime verification and author-facing schemas. */
56
+ export const VERIFICATION_LIMITS = Object.freeze({
57
+ minSteps: 1,
58
+ maxSteps: 8,
59
+ minTestFiles: 1,
60
+ maxTestFiles: 64,
61
+ });
62
+
55
63
  export const ARTIFACT_FAILURE_CODES = [
56
64
  "agents-impact-mismatch",
57
65
  "agents-target-mismatch",
@@ -117,7 +125,6 @@ export const ENVIRONMENT_FAILURE_CODES = [
117
125
  "checkout-failed",
118
126
  "clone-alternates",
119
127
  "clone-failed",
120
- "child-session-create-failed",
121
128
  "dependency-path-unsafe",
122
129
  "git-apply-check-unavailable",
123
130
  "git-apply-failed",
@@ -125,11 +132,6 @@ export const ENVIRONMENT_FAILURE_CODES = [
125
132
  "git-ignore-check-failed",
126
133
  "git-index-unavailable",
127
134
  "invalid-subagent-endpoint",
128
- "parent-bridge-capture-not-ready",
129
- "parent-bridge-generation-invalidated",
130
- "parent-bridge-model-key-mismatch",
131
- "parent-bridge-provider-not-installed",
132
- "parent-bridge-session-unavailable",
133
135
  "root-unavailable",
134
136
  "sandbox-runtime-unavailable",
135
137
  ] as const;
@@ -155,6 +157,9 @@ export const VERIFICATION_ADAPTER_CODES = [
155
157
  "verification-config-unsafe",
156
158
  ] as const;
157
159
  export const CHILD_TRANSPORT_CODES = [
160
+ "first-progress-timeout",
161
+ "stream-idle-timeout",
162
+ "attempt-timeout",
158
163
  "child-no-final-assistant",
159
164
  "child-provider-stream-aborted",
160
165
  "child-provider-stream-error",
@@ -237,6 +242,10 @@ export type CandidateFailure =
237
242
  | { kind: "result-limit"; limitBytes: number };
238
243
 
239
244
  export type ChildFailure =
245
+ | {
246
+ kind: "execution-limit";
247
+ code: "child-turn-limit" | "child-context-limit";
248
+ }
240
249
  | CandidateFailure
241
250
  | {
242
251
  kind: "transport";
@@ -437,6 +446,9 @@ type ImplementGraphDiagnosticDetails = Partial<{
437
446
  producerTaskId: string;
438
447
  producerPhase: ImplementationPhase;
439
448
  dependencyTaskId: string;
449
+ field: string;
450
+ expectedPaths: string[];
451
+ actualPaths: string[];
440
452
  }>;
441
453
 
442
454
  export type ImplementGraphReadinessDiagnostic =
@@ -478,6 +490,10 @@ export interface PacketEnvelope {
478
490
  output: "evidence" | "diff";
479
491
  }
480
492
 
493
+ // JSON-schema counterpart for model-visible path arguments; keep its behavior
494
+ // checked against isValidRelativePath rather than hiding constraints in prose.
495
+ export const RELATIVE_PATH_PATTERN = String.raw`^(?:\.|(?!/)(?![a-zA-Z]:/)(?!\./)(?![\s\S]*\.\.)(?![\s\S]*[\\\u0000])(?![\s\S]*//)(?![\s\S]*/\.(?:/|$))(?![\s\S]*/$)[\s\S]+)$`;
496
+
481
497
  export function isValidRelativePath(p: unknown): p is string {
482
498
  return (
483
499
  typeof p === "string" &&
@@ -655,8 +671,8 @@ function validateAtomicVerification(
655
671
  validateExecutableRunner(contract.runner) &&
656
672
  validateVitestRunner(executableRunner) &&
657
673
  Array.isArray(contract.testFiles) &&
658
- contract.testFiles.length > 0 &&
659
- contract.testFiles.length <= 64 &&
674
+ contract.testFiles.length >= VERIFICATION_LIMITS.minTestFiles &&
675
+ contract.testFiles.length <= VERIFICATION_LIMITS.maxTestFiles &&
660
676
  contract.testFiles.every(isValidRelativePath) &&
661
677
  new Set(contract.testFiles).size === contract.testFiles.length &&
662
678
  validVerificationArgs(contract.args) &&
@@ -724,8 +740,8 @@ function validateStructuredVerification(
724
740
  String(contract.classification),
725
741
  ) ||
726
742
  !Array.isArray(contract.steps) ||
727
- contract.steps.length === 0 ||
728
- contract.steps.length > 8 ||
743
+ contract.steps.length < VERIFICATION_LIMITS.minSteps ||
744
+ contract.steps.length > VERIFICATION_LIMITS.maxSteps ||
729
745
  !contract.steps.every((step) => validateAtomicVerification(step))
730
746
  ) {
731
747
  return false;
@@ -1198,6 +1214,9 @@ export type ImplementGraphContractDiagnostic =
1198
1214
  field?: string;
1199
1215
  category?: string;
1200
1216
  outputId?: string;
1217
+ path?: string;
1218
+ expectedPaths?: string[];
1219
+ actualPaths?: string[];
1201
1220
  }
1202
1221
  | {
1203
1222
  code: "invalid-output-path";
@@ -1456,6 +1475,26 @@ function taskBoundaryContractDiagnostic(
1456
1475
  snapshot,
1457
1476
  );
1458
1477
  if (impactReason !== null) {
1478
+ if (impactReason === "current-task related test is outside the write set") {
1479
+ const related = (value.impactClosure as ImpactClosureContract)
1480
+ .relatedTests;
1481
+ const index = related.findIndex(
1482
+ (test) =>
1483
+ test.disposition === "current-task" &&
1484
+ !declared.write.includes(test.path),
1485
+ );
1486
+ const test = related[index];
1487
+ if (test)
1488
+ return {
1489
+ ...diagnostic(
1490
+ `impactClosure.relatedTests.${index}.disposition`,
1491
+ "current-task-outside-write-set",
1492
+ ),
1493
+ path: test.path,
1494
+ expectedPaths: [...declared.write].sort(),
1495
+ actualPaths: [test.path],
1496
+ };
1497
+ }
1459
1498
  return diagnostic("impactClosure", impactClosureCategory(impactReason));
1460
1499
  }
1461
1500
  return validateTaskBoundary(value, snapshot)
@@ -1469,6 +1508,7 @@ export function validateImplementGraphBoundary(value: unknown):
1469
1508
  ok: false;
1470
1509
  reason: string;
1471
1510
  diagnostic: ImplementGraphContractDiagnostic;
1511
+ diagnostics?: ImplementGraphContractDiagnostic[];
1472
1512
  } {
1473
1513
  if (
1474
1514
  !hasExactKeys(value, ["changeId", "tasks", "outputs"]) ||
@@ -1478,29 +1518,33 @@ export function validateImplementGraphBoundary(value: unknown):
1478
1518
  value.tasks.length > 128 ||
1479
1519
  !Array.isArray(value.outputs) ||
1480
1520
  value.outputs.length > 512
1481
- ) {
1521
+ )
1482
1522
  return {
1483
1523
  ok: false,
1484
1524
  reason: "invalid Implement graph boundary",
1485
1525
  diagnostic: { code: "invalid-implement-graph" },
1486
1526
  };
1487
- }
1488
1527
 
1528
+ // Collect independent task/output failures without admitting a partial graph.
1529
+ const failures: Array<{
1530
+ reason: string;
1531
+ diagnostic: ImplementGraphContractDiagnostic;
1532
+ }> = [];
1489
1533
  for (const candidate of value.tasks) {
1490
1534
  if (
1491
1535
  !candidate ||
1492
1536
  typeof candidate !== "object" ||
1493
1537
  Array.isArray(candidate)
1494
1538
  ) {
1495
- return {
1496
- ok: false,
1539
+ failures.push({
1497
1540
  reason: "invalid Implement task boundary",
1498
1541
  diagnostic: {
1499
1542
  code: "invalid-implement-graph",
1500
1543
  field: "tasks",
1501
1544
  category: "shape",
1502
1545
  },
1503
- };
1546
+ });
1547
+ continue;
1504
1548
  }
1505
1549
  const task = candidate as Record<string, unknown>;
1506
1550
  const diagnostic = Object.hasOwn(task, "changeId")
@@ -1513,31 +1557,27 @@ export function validateImplementGraphBoundary(value: unknown):
1513
1557
  { changeId: value.changeId, ...task },
1514
1558
  undefined,
1515
1559
  );
1516
- if (diagnostic) {
1517
- return {
1518
- ok: false,
1560
+ if (diagnostic)
1561
+ failures.push({
1519
1562
  reason: "invalid Implement task boundary",
1520
1563
  diagnostic: { code: "invalid-implement-graph", ...diagnostic },
1521
- };
1522
- }
1564
+ });
1523
1565
  }
1524
-
1525
1566
  for (const candidate of value.outputs) {
1526
1567
  if (
1527
1568
  !candidate ||
1528
1569
  typeof candidate !== "object" ||
1529
1570
  Array.isArray(candidate)
1530
1571
  ) {
1531
- return {
1532
- ok: false,
1572
+ failures.push({
1533
1573
  reason: "invalid Implement graph output",
1534
1574
  diagnostic: { code: "invalid-implement-graph" },
1535
- };
1575
+ });
1576
+ continue;
1536
1577
  }
1537
1578
  const output = candidate as Record<string, unknown>;
1538
1579
  if (!isValidRelativePath(output.path)) {
1539
- return {
1540
- ok: false,
1580
+ failures.push({
1541
1581
  reason: "invalid Implement graph output path",
1542
1582
  diagnostic: {
1543
1583
  code: "invalid-output-path",
@@ -1545,7 +1585,8 @@ export function validateImplementGraphBoundary(value: unknown):
1545
1585
  field: "outputs.path",
1546
1586
  category: "path",
1547
1587
  },
1548
- };
1588
+ });
1589
+ continue;
1549
1590
  }
1550
1591
  if (
1551
1592
  !hasExactKeys(output, ["id", "path", "producer", "postcondition"]) ||
@@ -1557,8 +1598,7 @@ export function validateImplementGraphBoundary(value: unknown):
1557
1598
  ) ||
1558
1599
  output.postcondition !== "regular-file"
1559
1600
  ) {
1560
- return {
1561
- ok: false,
1601
+ failures.push({
1562
1602
  reason: "invalid Implement graph output",
1563
1603
  diagnostic: {
1564
1604
  code: "invalid-implement-graph",
@@ -1566,10 +1606,18 @@ export function validateImplementGraphBoundary(value: unknown):
1566
1606
  field: "outputs",
1567
1607
  category: "shape",
1568
1608
  },
1569
- };
1609
+ });
1570
1610
  }
1571
1611
  }
1572
-
1612
+ const first = failures[0];
1613
+ if (first)
1614
+ return {
1615
+ ok: false,
1616
+ ...first,
1617
+ ...(failures.length > 1
1618
+ ? { diagnostics: failures.map((failure) => failure.diagnostic) }
1619
+ : {}),
1620
+ };
1573
1621
  return {
1574
1622
  ok: true,
1575
1623
  value: structuredClone(value) as unknown as ImplementGraphBoundary,
@@ -1976,6 +2024,24 @@ export interface DiffResult {
1976
2024
  contractCompliant: boolean;
1977
2025
  }
1978
2026
 
2027
+ // Diagnostics contain only code-owned field names, never unexpected keys or values.
2028
+ function evidenceFieldsFailure(
2029
+ value: unknown,
2030
+ fields: readonly string[],
2031
+ location: string,
2032
+ ): { ok: false; reason: string } {
2033
+ if (!value || typeof value !== "object" || Array.isArray(value))
2034
+ return { ok: false, reason: `${location}: expected an object` };
2035
+ const missing = fields.filter((field) => !Object.hasOwn(value, field));
2036
+ return {
2037
+ ok: false,
2038
+ reason:
2039
+ missing.length > 0
2040
+ ? `${location}: missing fields ${missing.join(", ")}`
2041
+ : `${location}: unexpected fields; allowed fields ${fields.join(", ")}`,
2042
+ };
2043
+ }
2044
+
1979
2045
  export function validateEvidenceResult(value: unknown): {
1980
2046
  ok: boolean;
1981
2047
  reason?: string;
@@ -1999,7 +2065,7 @@ export function validateEvidenceResult(value: unknown): {
1999
2065
  "hints",
2000
2066
  ] as const;
2001
2067
  if (!hasExactKeys(r, fields)) {
2002
- return { ok: false, reason: "invalid evidence result fields" };
2068
+ return evidenceFieldsFailure(r, fields, "result");
2003
2069
  }
2004
2070
  if (!validIdentifier(r.id) || typeof r.role !== "string")
2005
2071
  return { ok: false, reason: "invalid identity" };
@@ -2019,25 +2085,38 @@ export function validateEvidenceResult(value: unknown): {
2019
2085
  return { ok: false, reason: `invalid evidence field: ${field}` };
2020
2086
  }
2021
2087
  }
2022
- if (
2023
- !Array.isArray(r.citations) ||
2024
- r.citations.some(
2025
- (citation) =>
2026
- !hasExactKeys(citation, ["path", "lines"]) ||
2027
- !isValidRelativePath(citation.path) ||
2028
- typeof citation.lines !== "string",
2029
- )
2030
- ) {
2031
- return { ok: false, reason: "invalid evidence citations" };
2032
- }
2033
- if (
2034
- !hasExactKeys(r.hints, ["writeSet", "verification", "agentsImpact"]) ||
2035
- !validatePathSet(r.hints.writeSet) ||
2036
- typeof r.hints.verification !== "string" ||
2037
- !(AGENTS_IMPACTS as readonly unknown[]).includes(r.hints.agentsImpact)
2038
- ) {
2039
- return { ok: false, reason: "invalid evidence hints" };
2040
- }
2088
+ if (!Array.isArray(r.citations))
2089
+ return { ok: false, reason: "citations: expected an array" };
2090
+ for (const [index, citation] of r.citations.entries()) {
2091
+ const field = `citations[${index}]`;
2092
+ if (!hasExactKeys(citation, ["path", "lines"]))
2093
+ return evidenceFieldsFailure(citation, ["path", "lines"], field);
2094
+ if (!isValidRelativePath(citation.path))
2095
+ return {
2096
+ ok: false,
2097
+ reason: `${field}.path: expected a canonical relative path`,
2098
+ };
2099
+ if (typeof citation.lines !== "string")
2100
+ return { ok: false, reason: `${field}.lines: expected a string` };
2101
+ }
2102
+ if (!hasExactKeys(r.hints, ["writeSet", "verification", "agentsImpact"]))
2103
+ return evidenceFieldsFailure(
2104
+ r.hints,
2105
+ ["writeSet", "verification", "agentsImpact"],
2106
+ "hints",
2107
+ );
2108
+ if (!validatePathSet(r.hints.writeSet))
2109
+ return {
2110
+ ok: false,
2111
+ reason: "hints.writeSet: expected unique canonical relative paths",
2112
+ };
2113
+ if (typeof r.hints.verification !== "string")
2114
+ return { ok: false, reason: "hints.verification: expected a string" };
2115
+ if (!(AGENTS_IMPACTS as readonly unknown[]).includes(r.hints.agentsImpact))
2116
+ return {
2117
+ ok: false,
2118
+ reason: `hints.agentsImpact: expected one of ${AGENTS_IMPACTS.join(", ")}`,
2119
+ };
2041
2120
  const serialized = JSON.stringify(r);
2042
2121
  if (Buffer.byteLength(serialized, "utf8") > LIMITS.maxCompleteResultBytes) {
2043
2122
  return {
@@ -2078,36 +2157,65 @@ function validateDesignEvidenceResult(r: Record<string, unknown>): {
2078
2157
  "success_criteria_hints",
2079
2158
  ] as const;
2080
2159
  if (!hasExactKeys(r, fields)) {
2081
- return { ok: false, reason: "invalid Design evidence result fields" };
2082
- }
2083
- if (
2084
- !validIdentifier(r.id) ||
2085
- r.packet_id !== r.id ||
2086
- !isValidRelativePath(r.module_name)
2087
- ) {
2088
- return { ok: false, reason: "invalid Design evidence identity" };
2160
+ return evidenceFieldsFailure(r, fields, "result");
2089
2161
  }
2162
+ if (!validIdentifier(r.id))
2163
+ return { ok: false, reason: "id: expected a valid identifier" };
2164
+ if (r.packet_id !== r.id)
2165
+ return { ok: false, reason: "packet_id: must equal id" };
2166
+ if (!isValidRelativePath(r.module_name))
2167
+ return {
2168
+ ok: false,
2169
+ reason: "module_name: expected a canonical relative path or module slug",
2170
+ };
2090
2171
  if (!validatePathSet(r.scope) || r.scope.length === 0) {
2091
- return { ok: false, reason: "invalid Design evidence scope" };
2172
+ return {
2173
+ ok: false,
2174
+ reason:
2175
+ "scope: expected a nonempty set of unique canonical relative paths",
2176
+ };
2092
2177
  }
2093
- if (!validatePathSet(r.files_read) || !validatePathSet(r.write_set_hints)) {
2094
- return { ok: false, reason: "invalid Design evidence path set" };
2178
+ for (const field of ["files_read", "write_set_hints"] as const) {
2179
+ if (!validatePathSet(r[field]))
2180
+ return {
2181
+ ok: false,
2182
+ reason: `${field}: expected unique canonical relative paths`,
2183
+ };
2095
2184
  }
2096
- if (
2097
- !Array.isArray(r.evidence) ||
2098
- r.evidence.some(
2099
- (entry) =>
2100
- !hasExactKeys(entry, ["claim", "path", "line_start", "line_end"]) ||
2101
- typeof entry.claim !== "string" ||
2102
- entry.claim.length === 0 ||
2103
- !isValidRelativePath(entry.path) ||
2104
- !Number.isSafeInteger(entry.line_start) ||
2105
- !Number.isSafeInteger(entry.line_end) ||
2106
- (entry.line_start as number) < 1 ||
2107
- (entry.line_end as number) < (entry.line_start as number),
2185
+ if (!Array.isArray(r.evidence))
2186
+ return { ok: false, reason: "evidence: expected an array" };
2187
+ for (const [index, entry] of r.evidence.entries()) {
2188
+ const field = `evidence[${index}]`;
2189
+ if (!hasExactKeys(entry, ["claim", "path", "line_start", "line_end"])) {
2190
+ return evidenceFieldsFailure(
2191
+ entry,
2192
+ ["claim", "path", "line_start", "line_end"],
2193
+ field,
2194
+ );
2195
+ }
2196
+ if (typeof entry.claim !== "string" || entry.claim.length === 0)
2197
+ return { ok: false, reason: `${field}.claim: expected nonempty text` };
2198
+ if (!isValidRelativePath(entry.path))
2199
+ return {
2200
+ ok: false,
2201
+ reason: `${field}.path: expected a canonical relative path`,
2202
+ };
2203
+ if (
2204
+ !Number.isSafeInteger(entry.line_start) ||
2205
+ (entry.line_start as number) < 1
2108
2206
  )
2109
- ) {
2110
- return { ok: false, reason: "invalid Design evidence citations" };
2207
+ return {
2208
+ ok: false,
2209
+ reason: `${field}.line_start: expected a positive safe integer`,
2210
+ };
2211
+ if (
2212
+ !Number.isSafeInteger(entry.line_end) ||
2213
+ (entry.line_end as number) < (entry.line_start as number)
2214
+ )
2215
+ return {
2216
+ ok: false,
2217
+ reason: `${field}.line_end: expected a safe integer >= line_start`,
2218
+ };
2111
2219
  }
2112
2220
  for (const field of [
2113
2221
  "existing_structures",