@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.
- package/README.md +134 -112
- package/README.ru.md +242 -0
- package/dist/agent-profiles.d.ts +1 -1
- package/dist/agent-profiles.js +18 -9
- package/dist/assets/agents/architect.md +67 -0
- package/{assets → dist/assets}/agents/critic.md +17 -2
- package/dist/assets/agents/manager.md +36 -0
- package/{assets → dist/assets}/agents/mapper.md +2 -2
- package/dist/assets/agents/review.md +25 -0
- package/{assets → dist/assets}/agents/worker.md +7 -5
- package/dist/assets/commands/agent-profiles.md +9 -0
- package/dist/assets/commands/agents-md.md +10 -0
- package/dist/assets/commands/askme.md +10 -0
- package/dist/assets/commands/ast-grep.md +10 -0
- package/dist/assets/commands/capabilities.md +9 -0
- package/dist/assets/commands/code-explain.md +10 -0
- package/dist/assets/commands/code-review.md +10 -0
- package/dist/assets/commands/commit-msg.md +10 -0
- package/dist/assets/commands/docs-prepare.md +10 -0
- package/dist/assets/commands/docs-review.md +10 -0
- package/dist/assets/commands/doctor.md +9 -0
- package/dist/assets/commands/doit.md +10 -0
- package/dist/assets/commands/goal.md +10 -0
- package/dist/assets/commands/humanize.md +10 -0
- package/dist/assets/commands/lsp-report.md +10 -0
- package/dist/assets/commands/mattermost.md +10 -0
- package/dist/assets/commands/mr-prepare.md +10 -0
- package/dist/assets/commands/reconcile.md +9 -0
- package/dist/assets/commands/release-prepare.md +10 -0
- package/dist/assets/commands/release-review.md +10 -0
- package/dist/assets/commands/rtk.md +10 -0
- package/dist/assets/commands/skill-improve.md +10 -0
- package/dist/assets/commands/slides-prompts-prepare.md +10 -0
- package/dist/assets/commands/spec-manage.md +10 -0
- package/dist/assets/commands/stopit.md +10 -0
- package/dist/assets/commands/summary.md +10 -0
- package/dist/assets/commands/task-prepare.md +10 -0
- package/dist/assets/commands/task-review.md +10 -0
- package/dist/assets/commands/task-triage.md +10 -0
- package/dist/assets/commands/team-retro.md +10 -0
- package/dist/assets/commands/team-roadmap.md +10 -0
- package/dist/assets/commands/team-sprint-close.md +10 -0
- package/dist/assets/commands/team-sprint-start.md +10 -0
- package/dist/assets/lsp-catalog.json +30 -0
- package/dist/assets/migration-inventory.json +294 -0
- package/dist/catalog.d.ts +8 -0
- package/dist/catalog.js +38 -0
- package/dist/cli-output.d.ts +7 -0
- package/dist/cli-output.js +68 -1
- package/dist/cli.js +136 -10
- package/dist/contracts.d.ts +52 -0
- package/dist/contracts.js +98 -0
- package/dist/doctor.d.ts +44 -0
- package/dist/doctor.js +595 -0
- package/dist/generate-assets.js +17 -2
- package/dist/index.d.ts +80 -56
- package/dist/index.js +162 -37
- package/dist/installer.d.ts +33 -5
- package/dist/installer.js +252 -28
- package/dist/lifecycle.d.ts +3 -0
- package/dist/lifecycle.js +49 -17
- package/dist/plugins/rtk.js +4 -1
- package/dist/reconcile.d.ts +43 -0
- package/dist/reconcile.js +507 -0
- package/dist/registry.d.ts +7 -4
- package/dist/registry.js +28 -88
- package/dist/routing.d.ts +39 -2
- package/dist/routing.js +339 -27
- package/dist/runtime/worktree.d.ts +33 -0
- package/dist/runtime/worktree.js +220 -0
- package/package.json +10 -26
- package/assets/agents/architect.md +0 -33
- package/assets/agents/manager.md +0 -82
- package/assets/agents/review.md +0 -25
- package/assets/commands/agent-list.md +0 -9
- package/assets/commands/agent-model-set.md +0 -9
- package/assets/commands/askme.md +0 -10
- package/assets/commands/ast-grep-rewrite.md +0 -10
- package/assets/commands/ast-grep-search.md +0 -10
- package/assets/commands/attempt-cancel.md +0 -10
- package/assets/commands/attempt-list.md +0 -10
- package/assets/commands/attempt-result.md +0 -10
- package/assets/commands/attempt-show.md +0 -10
- package/assets/commands/capabilities.md +0 -9
- package/assets/commands/code-review.md +0 -10
- package/assets/commands/commit-msg.md +0 -10
- package/assets/commands/critic-add.md +0 -9
- package/assets/commands/critic-remove.md +0 -9
- package/assets/commands/docs-prepare.md +0 -10
- package/assets/commands/docs-review.md +0 -10
- package/assets/commands/doctor.md +0 -9
- package/assets/commands/doit.md +0 -10
- package/assets/commands/goal-list.md +0 -10
- package/assets/commands/goal-pause.md +0 -10
- package/assets/commands/goal-prepare.md +0 -10
- package/assets/commands/goal-remove.md +0 -10
- package/assets/commands/goal-show.md +0 -10
- package/assets/commands/goal-start.md +0 -10
- package/assets/commands/lsp-report.md +0 -10
- package/assets/commands/mattermost.md +0 -10
- package/assets/commands/mr-prepare.md +0 -10
- package/assets/commands/mr-review.md +0 -10
- package/assets/commands/multi-run-cancel.md +0 -10
- package/assets/commands/multi-run-compare.md +0 -10
- package/assets/commands/multi-run-fusion.md +0 -10
- package/assets/commands/multi-run-start.md +0 -10
- package/assets/commands/multi-run-status.md +0 -10
- package/assets/commands/overview.md +0 -10
- package/assets/commands/release-prepare.md +0 -10
- package/assets/commands/release-review.md +0 -10
- package/assets/commands/route.md +0 -9
- package/assets/commands/schedule-add.md +0 -10
- package/assets/commands/schedule-disable.md +0 -10
- package/assets/commands/schedule-enable.md +0 -10
- package/assets/commands/schedule-list.md +0 -10
- package/assets/commands/schedule-remove.md +0 -10
- package/assets/commands/schedule-status.md +0 -10
- package/assets/commands/skill-improver.md +0 -10
- package/assets/commands/spec-audit.md +0 -10
- package/assets/commands/spec-init.md +0 -10
- package/assets/commands/spec-onboard.md +0 -10
- package/assets/commands/spec-update.md +0 -10
- package/assets/commands/stopit.md +0 -10
- package/assets/commands/summary.md +0 -10
- package/assets/commands/task-prepare-batch.md +0 -10
- package/assets/commands/task-prepare.md +0 -10
- package/assets/commands/task-review.md +0 -10
- package/assets/commands/task-triage.md +0 -10
- package/assets/commands/team-planning.md +0 -10
- package/assets/commands/team-retro.md +0 -10
- package/assets/commands/team-roadmap.md +0 -10
- package/assets/commands/team-slides-prompts.md +0 -10
- package/assets/commands/team-sprint-close.md +0 -10
- package/assets/commands/team-sprint-status.md +0 -10
- package/assets/commands/walkthrough.md +0 -10
- package/assets/plugins/autonomy-policy.js +0 -3
- package/assets/plugins/background-attempts.js +0 -3
- package/assets/plugins/goal-loop.js +0 -3
- package/assets/plugins/schedule.js +0 -3
- package/assets/plugins/zed-clickable-paths.js +0 -3
- package/dist/plugins/autonomy-policy.d.ts +0 -21
- package/dist/plugins/autonomy-policy.js +0 -71
- package/dist/plugins/background-attempts.d.ts +0 -76
- package/dist/plugins/background-attempts.js +0 -124
- package/dist/plugins/goal-loop.d.ts +0 -62
- package/dist/plugins/goal-loop.js +0 -99
- package/dist/plugins/schedule.d.ts +0 -36
- package/dist/plugins/schedule.js +0 -100
- package/dist/plugins/zed-clickable-paths.d.ts +0 -11
- package/dist/plugins/zed-clickable-paths.js +0 -10
- /package/{assets → dist/assets}/plugins/rtk.js +0 -0
- /package/{assets → dist/assets}/plugins/rules-injector.js +0 -0
- /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
|
-
|
|
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: {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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)
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
? {
|
|
66
|
-
|
|
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) &&
|
|
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
|
-
"
|
|
79
|
-
"
|
|
80
|
-
"
|
|
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
|
-
|
|
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() {
|
|
164
|
-
|
|
165
|
-
|
|
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: [],
|
|
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
|
|
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:
|
|
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
|
+
}>;
|