@kisev/skills-opencode 1.2.0 → 2.0.0

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 (153) hide show
  1. package/README.md +134 -112
  2. package/README.ru.md +242 -0
  3. package/dist/agent-profiles.d.ts +1 -1
  4. package/dist/agent-profiles.js +18 -9
  5. package/dist/assets/agents/architect.md +67 -0
  6. package/{assets → dist/assets}/agents/critic.md +17 -2
  7. package/dist/assets/agents/manager.md +36 -0
  8. package/{assets → dist/assets}/agents/mapper.md +2 -2
  9. package/dist/assets/agents/review.md +25 -0
  10. package/{assets → dist/assets}/agents/worker.md +7 -5
  11. package/dist/assets/commands/agent-profiles.md +9 -0
  12. package/dist/assets/commands/agents-md.md +10 -0
  13. package/dist/assets/commands/askme.md +10 -0
  14. package/dist/assets/commands/ast-grep.md +10 -0
  15. package/dist/assets/commands/capabilities.md +9 -0
  16. package/dist/assets/commands/code-explain.md +10 -0
  17. package/dist/assets/commands/code-review.md +10 -0
  18. package/dist/assets/commands/commit-msg.md +10 -0
  19. package/dist/assets/commands/docs-prepare.md +10 -0
  20. package/dist/assets/commands/docs-review.md +10 -0
  21. package/dist/assets/commands/doctor.md +9 -0
  22. package/dist/assets/commands/doit.md +10 -0
  23. package/dist/assets/commands/goal.md +10 -0
  24. package/dist/assets/commands/humanize.md +10 -0
  25. package/dist/assets/commands/lsp-report.md +10 -0
  26. package/dist/assets/commands/mattermost.md +10 -0
  27. package/dist/assets/commands/mr-prepare.md +10 -0
  28. package/dist/assets/commands/reconcile.md +9 -0
  29. package/dist/assets/commands/release-prepare.md +10 -0
  30. package/dist/assets/commands/release-review.md +10 -0
  31. package/dist/assets/commands/rtk.md +10 -0
  32. package/dist/assets/commands/skill-improve.md +10 -0
  33. package/dist/assets/commands/slides-prompts-prepare.md +10 -0
  34. package/dist/assets/commands/spec-manage.md +10 -0
  35. package/dist/assets/commands/stopit.md +10 -0
  36. package/dist/assets/commands/summary.md +10 -0
  37. package/dist/assets/commands/task-prepare.md +10 -0
  38. package/dist/assets/commands/task-review.md +10 -0
  39. package/dist/assets/commands/task-triage.md +10 -0
  40. package/dist/assets/commands/team-retro.md +10 -0
  41. package/dist/assets/commands/team-roadmap.md +10 -0
  42. package/dist/assets/commands/team-sprint-close.md +10 -0
  43. package/dist/assets/commands/team-sprint-start.md +10 -0
  44. package/dist/assets/lsp-catalog.json +30 -0
  45. package/dist/assets/migration-inventory.json +294 -0
  46. package/dist/catalog.d.ts +8 -0
  47. package/dist/catalog.js +38 -0
  48. package/dist/cli-output.d.ts +7 -0
  49. package/dist/cli-output.js +68 -1
  50. package/dist/cli.js +136 -10
  51. package/dist/contracts.d.ts +52 -0
  52. package/dist/contracts.js +98 -0
  53. package/dist/doctor.d.ts +44 -0
  54. package/dist/doctor.js +595 -0
  55. package/dist/generate-assets.js +17 -2
  56. package/dist/index.d.ts +80 -56
  57. package/dist/index.js +162 -37
  58. package/dist/installer.d.ts +33 -5
  59. package/dist/installer.js +252 -28
  60. package/dist/lifecycle.d.ts +3 -0
  61. package/dist/lifecycle.js +49 -17
  62. package/dist/plugins/rtk.js +4 -1
  63. package/dist/reconcile.d.ts +43 -0
  64. package/dist/reconcile.js +507 -0
  65. package/dist/registry.d.ts +7 -4
  66. package/dist/registry.js +28 -88
  67. package/dist/routing.d.ts +39 -2
  68. package/dist/routing.js +339 -27
  69. package/dist/runtime/worktree.d.ts +33 -0
  70. package/dist/runtime/worktree.js +220 -0
  71. package/package.json +10 -26
  72. package/assets/agents/architect.md +0 -33
  73. package/assets/agents/manager.md +0 -82
  74. package/assets/agents/review.md +0 -25
  75. package/assets/commands/agent-list.md +0 -9
  76. package/assets/commands/agent-model-set.md +0 -9
  77. package/assets/commands/askme.md +0 -10
  78. package/assets/commands/ast-grep-rewrite.md +0 -10
  79. package/assets/commands/ast-grep-search.md +0 -10
  80. package/assets/commands/attempt-cancel.md +0 -10
  81. package/assets/commands/attempt-list.md +0 -10
  82. package/assets/commands/attempt-result.md +0 -10
  83. package/assets/commands/attempt-show.md +0 -10
  84. package/assets/commands/capabilities.md +0 -9
  85. package/assets/commands/code-review.md +0 -10
  86. package/assets/commands/commit-msg.md +0 -10
  87. package/assets/commands/critic-add.md +0 -9
  88. package/assets/commands/critic-remove.md +0 -9
  89. package/assets/commands/docs-prepare.md +0 -10
  90. package/assets/commands/docs-review.md +0 -10
  91. package/assets/commands/doctor.md +0 -9
  92. package/assets/commands/doit.md +0 -10
  93. package/assets/commands/goal-list.md +0 -10
  94. package/assets/commands/goal-pause.md +0 -10
  95. package/assets/commands/goal-prepare.md +0 -10
  96. package/assets/commands/goal-remove.md +0 -10
  97. package/assets/commands/goal-show.md +0 -10
  98. package/assets/commands/goal-start.md +0 -10
  99. package/assets/commands/lsp-report.md +0 -10
  100. package/assets/commands/mattermost.md +0 -10
  101. package/assets/commands/mr-prepare.md +0 -10
  102. package/assets/commands/mr-review.md +0 -10
  103. package/assets/commands/multi-run-cancel.md +0 -10
  104. package/assets/commands/multi-run-compare.md +0 -10
  105. package/assets/commands/multi-run-fusion.md +0 -10
  106. package/assets/commands/multi-run-start.md +0 -10
  107. package/assets/commands/multi-run-status.md +0 -10
  108. package/assets/commands/overview.md +0 -10
  109. package/assets/commands/release-prepare.md +0 -10
  110. package/assets/commands/release-review.md +0 -10
  111. package/assets/commands/route.md +0 -9
  112. package/assets/commands/schedule-add.md +0 -10
  113. package/assets/commands/schedule-disable.md +0 -10
  114. package/assets/commands/schedule-enable.md +0 -10
  115. package/assets/commands/schedule-list.md +0 -10
  116. package/assets/commands/schedule-remove.md +0 -10
  117. package/assets/commands/schedule-status.md +0 -10
  118. package/assets/commands/skill-improver.md +0 -10
  119. package/assets/commands/spec-audit.md +0 -10
  120. package/assets/commands/spec-init.md +0 -10
  121. package/assets/commands/spec-onboard.md +0 -10
  122. package/assets/commands/spec-update.md +0 -10
  123. package/assets/commands/stopit.md +0 -10
  124. package/assets/commands/summary.md +0 -10
  125. package/assets/commands/task-prepare-batch.md +0 -10
  126. package/assets/commands/task-prepare.md +0 -10
  127. package/assets/commands/task-review.md +0 -10
  128. package/assets/commands/task-triage.md +0 -10
  129. package/assets/commands/team-planning.md +0 -10
  130. package/assets/commands/team-retro.md +0 -10
  131. package/assets/commands/team-roadmap.md +0 -10
  132. package/assets/commands/team-slides-prompts.md +0 -10
  133. package/assets/commands/team-sprint-close.md +0 -10
  134. package/assets/commands/team-sprint-status.md +0 -10
  135. package/assets/commands/walkthrough.md +0 -10
  136. package/assets/plugins/autonomy-policy.js +0 -3
  137. package/assets/plugins/background-attempts.js +0 -3
  138. package/assets/plugins/goal-loop.js +0 -3
  139. package/assets/plugins/schedule.js +0 -3
  140. package/assets/plugins/zed-clickable-paths.js +0 -3
  141. package/dist/plugins/autonomy-policy.d.ts +0 -21
  142. package/dist/plugins/autonomy-policy.js +0 -71
  143. package/dist/plugins/background-attempts.d.ts +0 -76
  144. package/dist/plugins/background-attempts.js +0 -124
  145. package/dist/plugins/goal-loop.d.ts +0 -62
  146. package/dist/plugins/goal-loop.js +0 -99
  147. package/dist/plugins/schedule.d.ts +0 -36
  148. package/dist/plugins/schedule.js +0 -100
  149. package/dist/plugins/zed-clickable-paths.d.ts +0 -11
  150. package/dist/plugins/zed-clickable-paths.js +0 -10
  151. /package/{assets → dist/assets}/plugins/rtk.js +0 -0
  152. /package/{assets → dist/assets}/plugins/rules-injector.js +0 -0
  153. /package/{assets → dist/assets}/plugins/zed-bell.js +0 -0
package/dist/routing.js CHANGED
@@ -1,15 +1,38 @@
1
1
  import { createHash } from "node:crypto";
2
- export const CATEGORIES = ["exploration", "architecture", "implementation", "review", "documentation", "quick"];
2
+ import { validateAgentReport } from "./contracts.js";
3
+ export const CATEGORIES = ["exploration", "architecture", "implementation", "review"];
3
4
  const MATRIX = {
4
5
  schema_version: 1,
5
6
  categories: {
6
- exploration: { profiles: ["mapper", "architect"], capabilities: ["read", "search"], tools: ["read", "glob", "grep"], cost: "low", latency: "fast" },
7
- architecture: { profiles: ["architect", "mapper"], capabilities: ["read", "architecture"], tools: ["read", "glob", "grep"], cost: "medium", latency: "standard" },
8
- implementation: { profiles: ["worker"], capabilities: ["read", "write", "verify"], tools: ["read", "edit", "bash"], cost: "medium", latency: "standard" },
9
- review: { profiles: ["review", "critic"], capabilities: ["read", "review"], tools: ["read", "glob", "grep"], cost: "medium", latency: "standard" },
10
- documentation: { profiles: ["worker", "review"], capabilities: ["read", "write", "documentation"], tools: ["read", "edit"], cost: "low", latency: "standard" },
11
- quick: { profiles: ["mapper", "worker"], capabilities: ["read"], tools: ["read"], cost: "low", latency: "fast" }
12
- }
7
+ exploration: {
8
+ profiles: ["mapper", "architect"],
9
+ capabilities: ["read", "search"],
10
+ tools: ["read", "glob", "grep"],
11
+ cost: "low",
12
+ latency: "fast",
13
+ },
14
+ architecture: {
15
+ profiles: ["architect", "mapper"],
16
+ capabilities: ["read", "architecture"],
17
+ tools: ["read", "glob", "grep"],
18
+ cost: "medium",
19
+ latency: "standard",
20
+ },
21
+ implementation: {
22
+ profiles: ["worker"],
23
+ capabilities: ["read", "write", "verify"],
24
+ tools: ["read", "edit", "bash"],
25
+ cost: "medium",
26
+ latency: "standard",
27
+ },
28
+ review: {
29
+ profiles: ["review", "critic"],
30
+ capabilities: ["read", "review"],
31
+ tools: ["read", "glob", "grep"],
32
+ cost: "medium",
33
+ latency: "standard",
34
+ },
35
+ },
13
36
  };
14
37
  function stable(value) {
15
38
  if (value === null || typeof value !== "object")
@@ -17,11 +40,66 @@ function stable(value) {
17
40
  if (Array.isArray(value))
18
41
  return `[${value.map(stable).join(",")}]`;
19
42
  const object = value;
20
- return `{${Object.keys(object).sort().map((key) => `${JSON.stringify(key)}:${stable(object[key])}`).join(",")}}`;
43
+ return `{${Object.keys(object)
44
+ .sort()
45
+ .map((key) => `${JSON.stringify(key)}:${stable(object[key])}`)
46
+ .join(",")}}`;
21
47
  }
22
48
  function digest(value) {
23
49
  return createHash("sha256").update(stable(value), "utf8").digest("hex");
24
50
  }
51
+ export function validateRoutingReceipt(value, context) {
52
+ if (!value || typeof value !== "object" || Array.isArray(value))
53
+ throw new Error("routing receipt is not an object");
54
+ const decision = value;
55
+ if ("destination" in decision) {
56
+ const receipt = value;
57
+ if (receipt.schema_version !== 1 ||
58
+ !CATEGORIES.includes(receipt.destination) ||
59
+ !receipt.agent ||
60
+ !receipt.nonce ||
61
+ !/^[a-f0-9]{64}$/.test(receipt.host_inventory_revision) ||
62
+ !/^[a-f0-9]{64}$/.test(receipt.receipt_digest) ||
63
+ !Number.isFinite(Date.parse(receipt.expires_at)) ||
64
+ (receipt.card_revision !== null &&
65
+ (!Number.isInteger(receipt.card_revision) || receipt.card_revision < 1)))
66
+ throw new Error("routing receipt is malformed");
67
+ if (Date.parse(receipt.expires_at) < Date.now())
68
+ throw new Error("routing receipt has expired");
69
+ const { receipt_digest: _receiptDigest, ...receiptBase } = receipt;
70
+ if (digest(receiptBase) !== receipt.receipt_digest)
71
+ throw new Error("routing receipt integrity is invalid");
72
+ if (context?.category !== undefined && receipt.destination !== context.category)
73
+ throw new Error("routing receipt destination does not match");
74
+ if (context?.task !== undefined && receipt.task_digest !== digest(context.task))
75
+ throw new Error("routing receipt task does not match");
76
+ if (context?.requirements !== undefined &&
77
+ receipt.requirements_digest !== digest(context.requirements))
78
+ throw new Error("routing receipt requirements do not match");
79
+ if (context?.card !== undefined && receipt.card_digest !== digest(context.card))
80
+ throw new Error("routing receipt card does not match");
81
+ return receipt;
82
+ }
83
+ if (decision.schema_version !== 1 ||
84
+ decision.status !== "selected" ||
85
+ !decision.agent ||
86
+ !CATEGORIES.includes(decision.category))
87
+ throw new Error("routing receipt is not selected");
88
+ if (!decision.matrix_revision ||
89
+ !decision.decision_digest ||
90
+ !/^[a-f0-9]{64}$/.test(decision.decision_digest))
91
+ throw new Error("routing receipt is incomplete");
92
+ const { decision_digest: _digest, ...base } = decision;
93
+ if (digest(base) !== decision.decision_digest)
94
+ throw new Error("routing receipt digest is forged or stale");
95
+ if (decision.matrix_revision !== digest(MATRIX))
96
+ throw new Error("routing receipt matrix is stale");
97
+ if (context?.category !== undefined && decision.category !== context.category)
98
+ throw new Error("routing receipt category does not match");
99
+ if (context?.task !== undefined && decision.task_digest !== digest(context.task))
100
+ throw new Error("routing receipt task does not match");
101
+ return decision;
102
+ }
25
103
  function missing(required, actual) {
26
104
  const values = new Set(actual ?? []);
27
105
  return required.some((item) => !values.has(item));
@@ -32,10 +110,20 @@ function exceeds(value, limit, order) {
32
110
  export function resolveRouting(input) {
33
111
  const category = MATRIX.categories[input.category];
34
112
  const revision = digest(MATRIX);
113
+ const hostInventoryRevision = input.inventory_revision ?? digest(input.agents);
35
114
  const inventory = new Map(input.agents.map((agent) => [agent.agent, agent]));
36
115
  const alternatives = [];
37
116
  const eligible = [];
38
- for (const profile of category.profiles) {
117
+ const dynamicCritics = input.category === "review"
118
+ ? input.agents
119
+ .map(({ agent }) => agent)
120
+ .filter((agent) => /^critic-[a-z0-9]+(?:-[a-z0-9]+)*$/.test(agent))
121
+ .sort()
122
+ : [];
123
+ const profiles = input.trusted_override && input.override && !category.profiles.includes(input.override)
124
+ ? [...category.profiles, ...dynamicCritics, input.override]
125
+ : [...category.profiles, ...dynamicCritics];
126
+ for (const profile of profiles) {
39
127
  const agent = inventory.get(profile);
40
128
  const reasons = [];
41
129
  if (!agent)
@@ -57,17 +145,65 @@ export function resolveRouting(input) {
57
145
  else if (agent)
58
146
  eligible.push(agent);
59
147
  }
60
- const selected = input.override ? eligible.find((agent) => agent.agent === input.override) : eligible[0];
148
+ if (input.override &&
149
+ !input.trusted_override &&
150
+ !category.profiles.includes(input.override))
151
+ throw new Error("Unknown or user-owned profile requires a trusted override");
152
+ const selected = input.override
153
+ ? eligible.find((agent) => agent.agent === input.override)
154
+ : eligible[0];
61
155
  const card = input.execution_card === undefined ? undefined : validateExecutionCard(input.execution_card);
62
156
  if (card && !card.valid)
63
157
  throw new Error(`Invalid execution card: ${card.failedField}`);
64
158
  const base = selected
65
- ? { schema_version: 1, status: "selected", category: input.category, agent: selected.agent, reason_codes: [input.override ? "explicit_override" : selected.agent === category.profiles[0] ? "primary_available" : "fallback_selected", "capabilities_match"], alternatives, matrix_revision: revision, task_digest: digest(input.task ?? ""), requirements_digest: digest(input.requirements), ...(card?.valid ? { execution_card_digest: digest(card.card), execution_card_revision: card.card.revision } : {}) }
66
- : { schema_version: 1, status: "escalate", category: input.category, reason_codes: [input.override ? "override_unavailable" : "no_eligible_profile"], alternatives, matrix_revision: revision, task_digest: digest(input.task ?? ""), requirements_digest: digest(input.requirements), ...(card?.valid ? { execution_card_digest: digest(card.card), execution_card_revision: card.card.revision } : {}) };
159
+ ? {
160
+ schema_version: 1,
161
+ status: "selected",
162
+ category: input.category,
163
+ agent: selected.agent,
164
+ reason_codes: [
165
+ input.override
166
+ ? "explicit_override"
167
+ : selected.agent === category.profiles[0]
168
+ ? "primary_available"
169
+ : "fallback_selected",
170
+ "capabilities_match",
171
+ ],
172
+ alternatives,
173
+ matrix_revision: revision,
174
+ host_inventory_revision: hostInventoryRevision,
175
+ task_digest: digest(input.task ?? ""),
176
+ requirements_digest: digest(input.requirements),
177
+ ...(card?.valid
178
+ ? {
179
+ execution_card_digest: digest(card.card),
180
+ execution_card_revision: card.card.revision,
181
+ }
182
+ : {}),
183
+ }
184
+ : {
185
+ schema_version: 1,
186
+ status: "escalate",
187
+ category: input.category,
188
+ reason_codes: [input.override ? "override_unavailable" : "no_eligible_profile"],
189
+ alternatives,
190
+ matrix_revision: revision,
191
+ host_inventory_revision: hostInventoryRevision,
192
+ task_digest: digest(input.task ?? ""),
193
+ requirements_digest: digest(input.requirements),
194
+ ...(card?.valid
195
+ ? {
196
+ execution_card_digest: digest(card.card),
197
+ execution_card_revision: card.card.revision,
198
+ }
199
+ : {}),
200
+ };
67
201
  return { ...base, decision_digest: digest(base) };
68
202
  }
69
203
  function isNonEmptyStringArray(value) {
70
- return Array.isArray(value) && value.length > 0 && value.every((item) => typeof item === "string" && item.length > 0);
204
+ return (Array.isArray(value) &&
205
+ value.length > 0 &&
206
+ value.every((item) => typeof item === "string" && item.length > 0));
71
207
  }
72
208
  export function validateExecutionCard(card) {
73
209
  if (!card || typeof card !== "object" || Array.isArray(card)) {
@@ -75,12 +211,28 @@ export function validateExecutionCard(card) {
75
211
  }
76
212
  const c = card;
77
213
  const expectedFields = [
78
- "status", "card_id", "revision", "objective", "changed_behavior", "risks",
79
- "write_set", "control_markers", "decisions", "steps", "acceptance_criteria",
80
- "checks", "boundaries",
214
+ "schema_version",
215
+ "status",
216
+ "card_id",
217
+ "revision",
218
+ "objective",
219
+ "evidence",
220
+ "changed_behavior",
221
+ "risks",
222
+ "write_set",
223
+ "control_markers",
224
+ "decisions",
225
+ "steps",
226
+ "acceptance_criteria",
227
+ "checks",
228
+ "operations",
229
+ "confirmations",
230
+ "boundaries",
81
231
  ];
82
232
  if (Object.keys(c).sort().join(",") !== expectedFields.slice().sort().join(","))
83
233
  return { valid: false, failedField: "schema" };
234
+ if (c.schema_version !== 1)
235
+ return { valid: false, failedField: "schema_version" };
84
236
  if (c.status !== "READY")
85
237
  return { valid: false, failedField: "status" };
86
238
  if (typeof c.card_id !== "string" || !c.card_id)
@@ -89,6 +241,8 @@ export function validateExecutionCard(card) {
89
241
  return { valid: false, failedField: "revision" };
90
242
  if (typeof c.objective !== "string" || !c.objective)
91
243
  return { valid: false, failedField: "objective" };
244
+ if (!isNonEmptyStringArray(c.evidence))
245
+ return { valid: false, failedField: "evidence" };
92
246
  if (!isNonEmptyStringArray(c.changed_behavior))
93
247
  return { valid: false, failedField: "changed_behavior" };
94
248
  if (!isNonEmptyStringArray(c.risks))
@@ -131,22 +285,95 @@ export function validateExecutionCard(card) {
131
285
  return { valid: false, failedField: "acceptance_criteria" };
132
286
  if (!isNonEmptyStringArray(c.checks))
133
287
  return { valid: false, failedField: "checks" };
288
+ if (!Array.isArray(c.operations) || c.operations.length === 0)
289
+ return { valid: false, failedField: "operations" };
290
+ const operationKinds = new Set([
291
+ "write",
292
+ "check",
293
+ "commit",
294
+ "rebase",
295
+ "push",
296
+ "merge",
297
+ "tag",
298
+ "release",
299
+ ]);
300
+ for (const operation of c.operations) {
301
+ if (!operation || typeof operation !== "object" || Array.isArray(operation))
302
+ return { valid: false, failedField: "operations" };
303
+ const item = operation;
304
+ if (typeof item.kind !== "string" ||
305
+ !operationKinds.has(item.kind) ||
306
+ typeof item.command !== "string" ||
307
+ !item.command)
308
+ return { valid: false, failedField: "operations" };
309
+ if (item.path !== undefined && (typeof item.path !== "string" || !item.path))
310
+ return { valid: false, failedField: "operations" };
311
+ if (item.confirmation_ref !== undefined &&
312
+ (typeof item.confirmation_ref !== "string" || !item.confirmation_ref))
313
+ return { valid: false, failedField: "operations" };
314
+ if (Object.keys(item).some((key) => !["kind", "command", "path", "confirmation_ref"].includes(key)))
315
+ return { valid: false, failedField: "operations" };
316
+ if ((item.kind === "write" || item.kind === "check") && typeof item.path !== "string")
317
+ return { valid: false, failedField: "operations" };
318
+ }
319
+ const operationSet = new Set(c.operations.map(({ kind }) => kind));
320
+ for (const required of ["commit", "rebase", "push", "merge", "tag", "release"])
321
+ if (!operationSet.has(required))
322
+ return { valid: false, failedField: "operations" };
323
+ if (!c.confirmations || typeof c.confirmations !== "object" || Array.isArray(c.confirmations))
324
+ return { valid: false, failedField: "confirmations" };
325
+ const confirmations = c.confirmations;
326
+ if (typeof confirmations.execution !== "string" || !confirmations.execution)
327
+ return { valid: false, failedField: "confirmations" };
328
+ if (operationSet.has("push") ||
329
+ operationSet.has("merge") ||
330
+ operationSet.has("tag") ||
331
+ operationSet.has("release"))
332
+ if (typeof confirmations.publication !== "string" || !confirmations.publication)
333
+ return { valid: false, failedField: "confirmations" };
334
+ if (operationSet.has("rebase"))
335
+ if (typeof confirmations.history_rewrite !== "string" || !confirmations.history_rewrite)
336
+ return { valid: false, failedField: "confirmations" };
337
+ if (Object.keys(confirmations).some((key) => !["execution", "publication", "history_rewrite"].includes(key)))
338
+ return { valid: false, failedField: "confirmations" };
134
339
  if (!c.boundaries || typeof c.boundaries !== "object" || Array.isArray(c.boundaries))
135
340
  return { valid: false, failedField: "boundaries" };
136
341
  const b = c.boundaries;
137
- if (!isNonEmptyStringArray(b.forbidden_paths))
342
+ if (!isNonEmptyStringArray(b.forbidden_paths) || typeof b.scope !== "string" || !b.scope)
138
343
  return { valid: false, failedField: "boundaries" };
139
- if (Object.keys(b).some((k) => k !== "forbidden_paths"))
344
+ if (Object.keys(b).some((k) => k !== "forbidden_paths" && k !== "scope"))
140
345
  return { valid: false, failedField: "boundaries" };
141
346
  const writeSet = c.write_set;
142
347
  const forbiddenPaths = b.forbidden_paths;
348
+ const safePath = (path) => !path.startsWith("/") &&
349
+ !path.includes("\\") &&
350
+ !path.split("/").some((part) => !part || part === "." || part === "..");
351
+ if (![...writeSet, ...forbiddenPaths].every(safePath))
352
+ return { valid: false, failedField: "boundaries" };
143
353
  const stepPaths = c.steps.map((s) => s.path);
144
354
  const markerPaths = c.control_markers.map((m) => m.path);
145
355
  const referenced = new Set([...stepPaths, ...markerPaths]);
146
356
  if (![...referenced].every((p) => writeSet.includes(p)))
147
357
  return { valid: false, failedField: "steps" };
148
- if (writeSet.some((p) => forbiddenPaths.includes(p)))
358
+ const overlaps = (left, right) => left === right || left.startsWith(`${right}/`) || right.startsWith(`${left}/`);
359
+ if (writeSet.some((p) => forbiddenPaths.some((forbidden) => overlaps(p, forbidden))))
149
360
  return { valid: false, failedField: "boundaries" };
361
+ if (!writeSet.every((path) => stepPaths.includes(path)))
362
+ return { valid: false, failedField: "steps" };
363
+ const operations = c.operations;
364
+ if (!writeSet.every((path) => operations.some((operation) => operation.kind === "write" && operation.path === path) &&
365
+ operations.some((operation) => operation.kind === "check" && operation.path === path)))
366
+ return { valid: false, failedField: "operations" };
367
+ for (const operation of operations) {
368
+ if (["commit", "rebase", "push", "merge", "tag", "release"].includes(operation.kind) &&
369
+ !operation.confirmation_ref)
370
+ return { valid: false, failedField: "operations" };
371
+ if (operation.kind === "rebase" && operation.confirmation_ref !== confirmations.history_rewrite)
372
+ return { valid: false, failedField: "confirmations" };
373
+ if (["push", "merge", "tag", "release"].includes(operation.kind) &&
374
+ operation.confirmation_ref !== confirmations.publication)
375
+ return { valid: false, failedField: "confirmations" };
376
+ }
150
377
  return { valid: true, card: c };
151
378
  }
152
379
  export class ExecutionCardLifecycle {
@@ -160,9 +387,15 @@ export class ExecutionCardLifecycle {
160
387
  this.#cardID = result.card.card_id;
161
388
  this.#revision = result.card.revision;
162
389
  }
163
- get status() { return this.#status; }
164
- get card_id() { return this.#cardID; }
165
- get revision() { return this.#revision; }
390
+ get status() {
391
+ return this.#status;
392
+ }
393
+ get card_id() {
394
+ return this.#cardID;
395
+ }
396
+ get revision() {
397
+ return this.#revision;
398
+ }
166
399
  transition(status, identity) {
167
400
  if (identity.card_id !== this.#cardID || identity.revision !== this.#revision)
168
401
  throw new Error("Execution card identity is stale");
@@ -170,7 +403,12 @@ export class ExecutionCardLifecycle {
170
403
  READY: ["RUNNING", "CANCELLED"],
171
404
  RUNNING: ["COMPLETED", "BLOCKED", "FAILED", "REJECTED_PLAN", "CANCELLED"],
172
405
  COMPLETED: ["APPROVED", "CHANGES_REQUIRED"],
173
- BLOCKED: [], FAILED: [], REJECTED_PLAN: [], APPROVED: [], CHANGES_REQUIRED: [], CANCELLED: [],
406
+ BLOCKED: [],
407
+ FAILED: [],
408
+ REJECTED_PLAN: [],
409
+ APPROVED: [],
410
+ CHANGES_REQUIRED: [],
411
+ CANCELLED: [],
174
412
  };
175
413
  if (!allowed[this.#status].includes(status))
176
414
  throw new Error(`Invalid execution-card transition: ${this.#status} -> ${status}`);
@@ -180,6 +418,7 @@ export class ExecutionCardLifecycle {
180
418
  }
181
419
  export class RoutingGate {
182
420
  #receipts = new Map();
421
+ #active = new Map();
183
422
  #ttlMs = 10 * 60 * 1000;
184
423
  preview(input) {
185
424
  return resolveRouting(input);
@@ -195,17 +434,38 @@ export class RoutingGate {
195
434
  grant(sessionID, decision, context) {
196
435
  const now = Date.now();
197
436
  const taskDigest = context?.task === undefined ? decision.task_digest : digest(context.task);
198
- const requirementsDigest = context?.requirements === undefined ? decision.requirements_digest : digest(context.requirements);
437
+ const requirementsDigest = context?.requirements === undefined
438
+ ? decision.requirements_digest
439
+ : digest(context.requirements);
199
440
  const cardDigest = context?.card === undefined ? decision.execution_card_digest : digest(context.card);
441
+ const receiptBase = {
442
+ schema_version: 1,
443
+ task_digest: taskDigest,
444
+ requirements_digest: requirementsDigest,
445
+ destination: decision.category,
446
+ agent: decision.agent,
447
+ card_digest: cardDigest ?? null,
448
+ card_revision: decision.execution_card_revision ?? null,
449
+ host_inventory_revision: decision.host_inventory_revision,
450
+ expires_at: new Date(now + this.#ttlMs).toISOString(),
451
+ nonce: createHash("sha256")
452
+ .update(`${sessionID}:${now}:${decision.decision_digest}`)
453
+ .digest("hex"),
454
+ };
455
+ const receipt = { ...receiptBase, receipt_digest: digest(receiptBase) };
200
456
  this.#receipts.set(sessionID, {
201
457
  decision,
202
458
  taskDigest,
203
459
  requirementsDigest,
204
460
  cardDigest,
205
- expiresAt: now + this.#ttlMs,
461
+ expiresAt: Date.parse(receipt.expires_at),
206
462
  });
463
+ return receipt;
464
+ }
465
+ cancel(sessionID) {
466
+ this.#receipts.delete(sessionID);
467
+ this.#active.delete(sessionID);
207
468
  }
208
- cancel(sessionID) { this.#receipts.delete(sessionID); }
209
469
  consume(sessionID, agent, context) {
210
470
  const receipt = this.#receipts.get(sessionID);
211
471
  if (!receipt)
@@ -226,5 +486,57 @@ export class RoutingGate {
226
486
  throw new Error("Routing receipt execution card does not match the active routing receipt");
227
487
  }
228
488
  this.#receipts.delete(sessionID);
489
+ const validatedCard = context?.card === undefined ? undefined : validateExecutionCard(context.card);
490
+ const card = validatedCard?.valid ? validatedCard.card : undefined;
491
+ const lifecycle = card ? new ExecutionCardLifecycle(card) : undefined;
492
+ if (lifecycle)
493
+ lifecycle.transition("RUNNING", { card_id: card.card_id, revision: card.revision });
494
+ this.#active.set(sessionID, { agent, card, lifecycle, expiresAt: receipt.expiresAt });
495
+ }
496
+ complete(sessionID, agent, output) {
497
+ const active = this.#active.get(sessionID);
498
+ if (!active || active.agent !== agent)
499
+ throw new Error("Task result has no matching active routing receipt");
500
+ if (Date.now() > active.expiresAt)
501
+ throw new Error("Task result routing receipt has expired");
502
+ const report = output && typeof output === "object" ? output : undefined;
503
+ const value = report?.[agent === "architect" ? "execution_card" : `${agent}_report`] ?? report?.report;
504
+ if (!value || typeof value !== "object" || Array.isArray(value))
505
+ throw new Error("Task result is missing a structured agent report");
506
+ const result = value;
507
+ validateAgentReport(agent, report, active.card);
508
+ if (active.card &&
509
+ (result.card_id !== active.card.card_id || result.revision !== active.card.revision))
510
+ throw new Error("Task result does not match the active execution card");
511
+ const status = result.status;
512
+ const allowed = agent === "worker"
513
+ ? ["COMPLETED", "BLOCKED", "FAILED", "REJECTED_PLAN"]
514
+ : agent === "critic"
515
+ ? ["APPROVED", "CHANGES_REQUIRED"]
516
+ : undefined;
517
+ if (allowed && (typeof status !== "string" || !allowed.includes(status)))
518
+ throw new Error("Task result has an invalid machine transition");
519
+ if (active.lifecycle && status === "COMPLETED")
520
+ active.lifecycle.transition("COMPLETED", {
521
+ card_id: active.card.card_id,
522
+ revision: active.card.revision,
523
+ });
524
+ if (active.lifecycle &&
525
+ (status === "BLOCKED" || status === "FAILED" || status === "REJECTED_PLAN"))
526
+ active.lifecycle.transition(status, {
527
+ card_id: active.card.card_id,
528
+ revision: active.card.revision,
529
+ });
530
+ if (active.lifecycle && (status === "APPROVED" || status === "CHANGES_REQUIRED")) {
531
+ active.lifecycle.transition("COMPLETED", {
532
+ card_id: active.card.card_id,
533
+ revision: active.card.revision,
534
+ });
535
+ active.lifecycle.transition(status, {
536
+ card_id: active.card.card_id,
537
+ revision: active.card.revision,
538
+ });
539
+ }
540
+ this.#active.delete(sessionID);
229
541
  }
230
542
  }
@@ -0,0 +1,33 @@
1
+ export type WorktreeStatus = "managed" | "missing" | "blocked" | "unknown";
2
+ export type WorktreeRecord = {
3
+ schema_version: 1;
4
+ workspace_id: string;
5
+ project: string;
6
+ path: string;
7
+ branch: string;
8
+ start_ref: string;
9
+ repository_fingerprint: string;
10
+ marker: string;
11
+ status: WorktreeStatus;
12
+ created_at: string;
13
+ updated_at: string;
14
+ };
15
+ export declare function worktreePlan(project: string, workspaceID?: `${string}-${string}-${string}-${string}-${string}`): Promise<Record<string, unknown>>;
16
+ export declare function worktreeCreate(input: {
17
+ project: string;
18
+ workspace_id: string;
19
+ path?: string;
20
+ branch?: string;
21
+ start_ref?: string;
22
+ }): Promise<WorktreeRecord>;
23
+ export declare function worktreeStatus(project: string, workspaceID: string): Promise<WorktreeRecord | undefined>;
24
+ export declare function worktreeList(project: string): Promise<WorktreeRecord[]>;
25
+ export declare function worktreeRelease(project: string, workspaceID: string): Promise<{
26
+ status: "released" | "blocked";
27
+ workspace_id: string;
28
+ reason?: string;
29
+ }>;
30
+ export declare function worktreeRecover(project: string): Promise<{
31
+ recovered: number;
32
+ blocked: number;
33
+ }>;