@astrosheep/keiyaku 2.9.0 → 2.9.2
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/build/.tsbuildinfo +1 -1
- package/build/cli/commands/akuma/list/handler.js +1 -1
- package/build/cli/commands/akuma/view/handler.js +1 -1
- package/build/cli/commands/contract/amend/handler.js +1 -1
- package/build/cli/commands/contract/arc/handler.js +1 -1
- package/build/cli/commands/contract/bind/handler.js +1 -1
- package/build/cli/commands/contract/forfeit/handler.js +1 -1
- package/build/cli/commands/contract/log/handler.js +1 -1
- package/build/cli/commands/contract/petition/handler.js +1 -1
- package/build/cli/commands/contract/renew/handler.js +1 -1
- package/build/cli/commands/projection/call/handler.js +5 -2
- package/build/cli/commands/projection/kill/handler.js +3 -1
- package/build/cli/commands/projection/revive/handler.js +6 -3
- package/build/cli/commands/projection/status/handler.js +4 -2
- package/build/cli/commands/projection/tell/handler.js +3 -1
- package/build/cli/commands/projection/wait/handler.js +3 -1
- package/build/cli/commands/shared.js +5 -5
- package/build/cli/commands/system/completion/handler.js +1 -1
- package/build/cli/parse-flags.js +197 -0
- package/build/cli/parse-metadata.js +129 -0
- package/build/cli/parse-selectors.js +65 -0
- package/build/cli/parse.js +19 -373
- package/build/cli/skills-install.js +1 -1
- package/build/config/akuma-loader.js +5 -2
- package/build/core/amend.js +2 -5
- package/build/core/arc.js +2 -4
- package/build/core/call/call.js +7 -2
- package/build/core/call/context.js +27 -14
- package/build/core/call-persist.js +9 -2
- package/build/core/path-coordinate.js +27 -0
- package/build/core/petition-run.js +2 -5
- package/build/core/projection-coordinate.js +14 -2
- package/build/core/renew.js +2 -4
- package/build/core/status/board.js +6 -3
- package/build/core/task-contract.js +13 -0
- package/build/core/task-git-runtime.js +5 -2
- package/build/core/task-git-store.js +31 -21
- package/build/core/worktree-path.js +9 -2
- package/build/flow-error.js +2 -0
- package/build/generated/version.js +1 -1
- package/build/git/branches.js +33 -9
- package/build/git/core.js +9 -0
- package/package.json +1 -1
- package/skills/keiyaku/SKILL.md +30 -13
- package/skills/keiyaku-akuma/SKILL.md +30 -62
- package/skills/keiyaku-task/SKILL.md +51 -0
- package/skills/keiyaku-workflow/SKILL.md +71 -0
package/build/cli/parse.js
CHANGED
|
@@ -1,342 +1,11 @@
|
|
|
1
1
|
import { FlowError } from "../flow-error.js";
|
|
2
2
|
import { agentNameSchema } from "../config/settings/schema.js";
|
|
3
3
|
import { isResponseArtifactId } from "../core/response-artifact-id.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { TASK_SUBCOMMANDS } from "./
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
"arc",
|
|
10
|
-
"amend",
|
|
11
|
-
"call",
|
|
12
|
-
"wait",
|
|
13
|
-
"tell",
|
|
14
|
-
"kill",
|
|
15
|
-
"log",
|
|
16
|
-
"renew",
|
|
17
|
-
"petition",
|
|
18
|
-
"forfeit",
|
|
19
|
-
]);
|
|
20
|
-
/** Parse-time allowed flags; prefers meta.parseFlags when help flags omit a mouth-only flag. */
|
|
21
|
-
function allowedFlagsForParse(command) {
|
|
22
|
-
const meta = getCommandMetadata(command);
|
|
23
|
-
return meta.parseFlags ?? meta.flags;
|
|
24
|
-
}
|
|
25
|
-
export function normalizeContractAddress(raw) {
|
|
26
|
-
const value = raw.startsWith("@") ? raw.slice(1) : raw;
|
|
27
|
-
if (!value.trim()) {
|
|
28
|
-
throw new FlowError("EMPTY_PARAM", "contract address cannot be empty");
|
|
29
|
-
}
|
|
30
|
-
return value.trim();
|
|
31
|
-
}
|
|
32
|
-
export function isCallCommand(command) {
|
|
33
|
-
return command === "call";
|
|
34
|
-
}
|
|
35
|
-
/** Identify petition --dry-run before parsing without mistaking flag values or literal payload. */
|
|
36
|
-
export function isPetitionDryRunInvocation(tokens) {
|
|
37
|
-
const boundary = tokens.indexOf("--");
|
|
38
|
-
const head = boundary === -1 ? tokens : tokens.slice(0, boundary);
|
|
39
|
-
if (!head.some((token) => token === "--dry-run" || token.startsWith("--dry-run=")))
|
|
40
|
-
return false;
|
|
41
|
-
for (let index = 0; index < head.length; index += 1) {
|
|
42
|
-
const token = head[index];
|
|
43
|
-
if (HELP_FLAGS.has(token) || token.startsWith("@"))
|
|
44
|
-
continue;
|
|
45
|
-
if (/^-[A-Za-z]$/.test(token)) {
|
|
46
|
-
const name = SHORT_FLAG_ALIASES[token.slice(1)];
|
|
47
|
-
if (name && VALUE_FLAGS.has(name))
|
|
48
|
-
index += 1;
|
|
49
|
-
continue;
|
|
50
|
-
}
|
|
51
|
-
if (token.startsWith("--")) {
|
|
52
|
-
const eq = token.indexOf("=");
|
|
53
|
-
const name = parseFlagName(eq === -1 ? token : token.slice(0, eq));
|
|
54
|
-
if (eq === -1 && VALUE_FLAGS.has(name))
|
|
55
|
-
index += 1;
|
|
56
|
-
continue;
|
|
57
|
-
}
|
|
58
|
-
return token === "petition";
|
|
59
|
-
}
|
|
60
|
-
return false;
|
|
61
|
-
}
|
|
62
|
-
export function commandUsesPromptInput(command) {
|
|
63
|
-
return isCallCommand(command) || command === "tell" || command === "revive";
|
|
64
|
-
}
|
|
65
|
-
export function parseWaitTimeout(value) {
|
|
66
|
-
const matched = /^(?<amount>[1-9][0-9]*)(?<unit>[smh])$/.exec(value);
|
|
67
|
-
if (!matched?.groups) {
|
|
68
|
-
throw new FlowError("EMPTY_PARAM", "wait timeout must use <positive integer><s|m|h>, for example 45s, 5m, or 2h (maximum 24h)");
|
|
69
|
-
}
|
|
70
|
-
const amount = Number(matched.groups.amount);
|
|
71
|
-
const multiplier = matched.groups.unit === "s" ? 1_000 : matched.groups.unit === "m" ? 60_000 : 3_600_000;
|
|
72
|
-
const timeoutMs = amount * multiplier;
|
|
73
|
-
if (!Number.isSafeInteger(timeoutMs) || timeoutMs > 24 * 3_600_000) {
|
|
74
|
-
throw new FlowError("EMPTY_PARAM", "wait timeout must use <positive integer><s|m|h>, for example 45s, 5m, or 2h (maximum 24h)");
|
|
75
|
-
}
|
|
76
|
-
return timeoutMs;
|
|
77
|
-
}
|
|
78
|
-
export function commandPositionalRange(command) {
|
|
79
|
-
if (command === "akuma view")
|
|
80
|
-
return { min: 1, max: 1, label: "<name>" };
|
|
81
|
-
if (command === "log")
|
|
82
|
-
return { min: 0, max: 0, label: "" };
|
|
83
|
-
if (isCallCommand(command))
|
|
84
|
-
return { min: 1, max: 1, label: "<BODY|->" };
|
|
85
|
-
if (command === "tell")
|
|
86
|
-
return { min: 2, max: 2, label: "<PROJECTION> <BODY|->" };
|
|
87
|
-
if (command === "revive")
|
|
88
|
-
return { min: 1, max: 2, label: "<ARTIFACT> [BODY|-]" };
|
|
89
|
-
if (command === "wait")
|
|
90
|
-
return { min: 1, max: 1, label: "<projection-address>" };
|
|
91
|
-
if (command === "kill")
|
|
92
|
-
return { min: 1, max: 1, label: "<projection-address>" };
|
|
93
|
-
if (command === "status")
|
|
94
|
-
return { min: 0, max: 1, label: "[projection-id]" };
|
|
95
|
-
if (command === "task add")
|
|
96
|
-
return { min: 1, max: 1, label: "<BODY|->" };
|
|
97
|
-
if (command === "task view" || command === "task start" || command === "task drop")
|
|
98
|
-
return { min: 1, max: 1, label: "<ID>" };
|
|
99
|
-
if (command === "task update")
|
|
100
|
-
return { min: 1, max: 1, label: "<ID>" };
|
|
101
|
-
if (command === "task stop" || command === "task done")
|
|
102
|
-
return { min: 0, max: 1, label: "[ID]" };
|
|
103
|
-
return { min: 0, max: 0 };
|
|
104
|
-
}
|
|
105
|
-
function commandRequiresStdin(command) {
|
|
106
|
-
return getCommandMetadata(command).stdin === "required";
|
|
107
|
-
}
|
|
108
|
-
function commandTokens(command) {
|
|
109
|
-
return command.split(" ");
|
|
110
|
-
}
|
|
111
|
-
function parseFlagName(raw) {
|
|
112
|
-
return raw.slice(2);
|
|
113
|
-
}
|
|
114
|
-
function assignFlag(flags, name, value) {
|
|
115
|
-
switch (name) {
|
|
116
|
-
case "cwd":
|
|
117
|
-
flags.cwd = String(value);
|
|
118
|
-
return;
|
|
119
|
-
case "repo":
|
|
120
|
-
flags.repo = String(value);
|
|
121
|
-
return;
|
|
122
|
-
case "contract":
|
|
123
|
-
flags.contractId = normalizeContractAddress(String(value));
|
|
124
|
-
flags.contractAddressSource = "explicit-flag";
|
|
125
|
-
return;
|
|
126
|
-
case "akuma":
|
|
127
|
-
flags.akuma = String(value);
|
|
128
|
-
return;
|
|
129
|
-
case "projection":
|
|
130
|
-
flags.projection = String(value);
|
|
131
|
-
return;
|
|
132
|
-
case "alias":
|
|
133
|
-
flags.alias = String(value);
|
|
134
|
-
return;
|
|
135
|
-
case "timeout":
|
|
136
|
-
case "wait":
|
|
137
|
-
flags.timeoutMs = parseWaitTimeout(String(value));
|
|
138
|
-
return;
|
|
139
|
-
case "model":
|
|
140
|
-
flags.model = String(value);
|
|
141
|
-
return;
|
|
142
|
-
case "effort": {
|
|
143
|
-
const effort = String(value);
|
|
144
|
-
flags.effort = effort;
|
|
145
|
-
return;
|
|
146
|
-
}
|
|
147
|
-
case "incognito":
|
|
148
|
-
flags.incognito = Boolean(value);
|
|
149
|
-
return;
|
|
150
|
-
case "bare":
|
|
151
|
-
flags.bare = Boolean(value);
|
|
152
|
-
return;
|
|
153
|
-
case "detach":
|
|
154
|
-
flags.detach = Boolean(value);
|
|
155
|
-
return;
|
|
156
|
-
case "draft-only":
|
|
157
|
-
flags.draftOnly = Boolean(value);
|
|
158
|
-
return;
|
|
159
|
-
case "exclusive":
|
|
160
|
-
flags.exclusive = Boolean(value);
|
|
161
|
-
return;
|
|
162
|
-
case "place":
|
|
163
|
-
if (flags.bindPlace !== undefined) {
|
|
164
|
-
throw new FlowError("EMPTY_PARAM", "option --place may only be specified once");
|
|
165
|
-
}
|
|
166
|
-
flags.bindPlace = String(value);
|
|
167
|
-
return;
|
|
168
|
-
case "objective":
|
|
169
|
-
if (flags.bindObjective !== undefined) {
|
|
170
|
-
throw new FlowError("EMPTY_PARAM", "option --objective may only be specified once");
|
|
171
|
-
}
|
|
172
|
-
flags.bindObjective = String(value);
|
|
173
|
-
return;
|
|
174
|
-
case "scope":
|
|
175
|
-
if (flags.bindScope !== undefined) {
|
|
176
|
-
throw new FlowError("EMPTY_PARAM", "option --scope may only be specified once");
|
|
177
|
-
}
|
|
178
|
-
flags.bindScope = String(value);
|
|
179
|
-
return;
|
|
180
|
-
case "checks":
|
|
181
|
-
if (flags.bindChecks !== undefined) {
|
|
182
|
-
throw new FlowError("EMPTY_PARAM", "option --checks may only be specified once");
|
|
183
|
-
}
|
|
184
|
-
flags.bindChecks = String(value);
|
|
185
|
-
return;
|
|
186
|
-
case "task":
|
|
187
|
-
if (flags.bindTaskId !== undefined)
|
|
188
|
-
throw new FlowError("EMPTY_PARAM", "option --task may only be specified once");
|
|
189
|
-
flags.bindTaskId = String(value);
|
|
190
|
-
return;
|
|
191
|
-
case "after":
|
|
192
|
-
flags.after = [...(flags.after ?? []), String(value)];
|
|
193
|
-
return;
|
|
194
|
-
case "force":
|
|
195
|
-
flags.force = Boolean(value);
|
|
196
|
-
return;
|
|
197
|
-
case "reason":
|
|
198
|
-
flags.reason = String(value);
|
|
199
|
-
return;
|
|
200
|
-
case "dry-run":
|
|
201
|
-
flags.dryRun = Boolean(value);
|
|
202
|
-
return;
|
|
203
|
-
case "waive":
|
|
204
|
-
flags.waivePaths = [...(flags.waivePaths ?? []), String(value)];
|
|
205
|
-
return;
|
|
206
|
-
case "shell":
|
|
207
|
-
flags.shell = String(value);
|
|
208
|
-
return;
|
|
209
|
-
case "word":
|
|
210
|
-
flags.word = String(value);
|
|
211
|
-
return;
|
|
212
|
-
case "previous":
|
|
213
|
-
flags.previous = String(value);
|
|
214
|
-
return;
|
|
215
|
-
case "complete":
|
|
216
|
-
flags.complete = Boolean(value);
|
|
217
|
-
return;
|
|
218
|
-
case "all":
|
|
219
|
-
flags.all = Boolean(value);
|
|
220
|
-
return;
|
|
221
|
-
case "target":
|
|
222
|
-
if (flags.target !== undefined) {
|
|
223
|
-
throw new FlowError("EMPTY_PARAM", "option --target may only be specified once");
|
|
224
|
-
}
|
|
225
|
-
flags.target = String(value);
|
|
226
|
-
return;
|
|
227
|
-
case "json":
|
|
228
|
-
flags.json = Boolean(value);
|
|
229
|
-
return;
|
|
230
|
-
case "done":
|
|
231
|
-
flags.done = Boolean(value);
|
|
232
|
-
return;
|
|
233
|
-
case "pri": {
|
|
234
|
-
const raw = String(value);
|
|
235
|
-
if (!/^[0-3]$/.test(raw))
|
|
236
|
-
throw new FlowError("EMPTY_PARAM", "task priority must be an integer from 0 through 3");
|
|
237
|
-
if (flags.taskPri !== undefined)
|
|
238
|
-
throw new FlowError("EMPTY_PARAM", "option --pri may only be specified once");
|
|
239
|
-
flags.taskPri = Number(raw);
|
|
240
|
-
return;
|
|
241
|
-
}
|
|
242
|
-
case "needs":
|
|
243
|
-
flags.taskNeeds = [...(flags.taskNeeds ?? []), String(value)];
|
|
244
|
-
return;
|
|
245
|
-
case "drop-needs":
|
|
246
|
-
flags.taskDropNeeds = [...(flags.taskDropNeeds ?? []), String(value)];
|
|
247
|
-
return;
|
|
248
|
-
case "from":
|
|
249
|
-
flags.taskFrom = [...(flags.taskFrom ?? []), String(value)];
|
|
250
|
-
return;
|
|
251
|
-
case "parent":
|
|
252
|
-
if (flags.taskParent !== undefined)
|
|
253
|
-
throw new FlowError("EMPTY_PARAM", "option --parent may only be specified once");
|
|
254
|
-
flags.taskParent = String(value);
|
|
255
|
-
return;
|
|
256
|
-
case "no-parent":
|
|
257
|
-
flags.taskNoParent = Boolean(value);
|
|
258
|
-
return;
|
|
259
|
-
case "title":
|
|
260
|
-
if (flags.taskTitle !== undefined)
|
|
261
|
-
throw new FlowError("EMPTY_PARAM", "option --title may only be specified once");
|
|
262
|
-
flags.taskTitle = String(value);
|
|
263
|
-
return;
|
|
264
|
-
case "body":
|
|
265
|
-
if (flags.taskBody !== undefined)
|
|
266
|
-
throw new FlowError("EMPTY_PARAM", "option --body may only be specified once");
|
|
267
|
-
flags.taskBody = String(value);
|
|
268
|
-
return;
|
|
269
|
-
default:
|
|
270
|
-
throw new FlowError("EMPTY_PARAM", `unknown option: --${name}`);
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
function assertAllowedFlag(command, name) {
|
|
274
|
-
if (!allowedFlagsForParse(command).includes(name)) {
|
|
275
|
-
throw new FlowError("EMPTY_PARAM", `option --${name} is not valid for ${command}`);
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
function resolveCommand(tokens) {
|
|
279
|
-
if (tokens[0] === "petition" && (tokens[1] === "claim" || tokens[1] === "forfeit")) {
|
|
280
|
-
throw new FlowError("UNKNOWN_COMMAND", `unknown command: petition ${tokens[1]}`);
|
|
281
|
-
}
|
|
282
|
-
for (const command of commandParseOrder()) {
|
|
283
|
-
const parts = commandTokens(command);
|
|
284
|
-
if (parts.every((part, index) => tokens[index] === part)) {
|
|
285
|
-
return { command, rest: tokens.slice(parts.length) };
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
const [head] = tokens;
|
|
289
|
-
if (head === "skills") {
|
|
290
|
-
return { command: "skills", rest: tokens.slice(1) };
|
|
291
|
-
}
|
|
292
|
-
if (!head)
|
|
293
|
-
throw new FlowError("EMPTY_PARAM", "command cannot be empty");
|
|
294
|
-
throw new FlowError("UNKNOWN_COMMAND", `unknown command: ${head ?? ""}`.trim());
|
|
295
|
-
}
|
|
296
|
-
function splitAtLiteralBoundary(tokens) {
|
|
297
|
-
const boundary = tokens.indexOf("--");
|
|
298
|
-
if (boundary === -1)
|
|
299
|
-
return { head: tokens, payload: [] };
|
|
300
|
-
return { head: tokens.slice(0, boundary), payload: tokens.slice(boundary + 1) };
|
|
301
|
-
}
|
|
302
|
-
function readValueFlag(name, token, eq, tokens, index) {
|
|
303
|
-
// --target and task --body own emptiness in core; empty remains supplied.
|
|
304
|
-
// Other value flags keep the historical requires-a-value rejection.
|
|
305
|
-
if (eq !== -1) {
|
|
306
|
-
const value = token.slice(eq + 1);
|
|
307
|
-
if (value === "" && name !== "target" && name !== "body") {
|
|
308
|
-
throw new FlowError("EMPTY_PARAM", `option --${name} requires a value`);
|
|
309
|
-
}
|
|
310
|
-
return { value, nextIndex: index };
|
|
311
|
-
}
|
|
312
|
-
const value = tokens[index + 1];
|
|
313
|
-
if (value === undefined || value === "--" || value.startsWith("--")) {
|
|
314
|
-
throw new FlowError("EMPTY_PARAM", `option --${name} requires a value`);
|
|
315
|
-
}
|
|
316
|
-
if (value === "" && name !== "target" && name !== "body") {
|
|
317
|
-
throw new FlowError("EMPTY_PARAM", `option --${name} requires a value`);
|
|
318
|
-
}
|
|
319
|
-
// Short-flag values may begin with @; they are flag values, not commission tokens.
|
|
320
|
-
return { value, nextIndex: index + 1 };
|
|
321
|
-
}
|
|
322
|
-
function commissionConflictMessage(atForm, contractForm) {
|
|
323
|
-
return `commission address supplied twice: ${atForm} and --contract ${contractForm}; use exactly one form`;
|
|
324
|
-
}
|
|
325
|
-
function formatSelectorList(items) {
|
|
326
|
-
if (items.length <= 1)
|
|
327
|
-
return items[0] ?? "";
|
|
328
|
-
if (items.length === 2)
|
|
329
|
-
return `${items[0]} and ${items[1]}`;
|
|
330
|
-
return `${items.slice(0, -1).join(", ")}, and ${items[items.length - 1]}`;
|
|
331
|
-
}
|
|
332
|
-
function multiCommissionSelectorMessage(command, spellings) {
|
|
333
|
-
return `${command} accepts one commission address; got ${formatSelectorList(spellings)}`;
|
|
334
|
-
}
|
|
335
|
-
function commissionSelectorSpelling(source, value, rawToken) {
|
|
336
|
-
if (source === "at-address")
|
|
337
|
-
return rawToken ?? `@${value}`;
|
|
338
|
-
return `--contract ${rawToken ?? value}`;
|
|
339
|
-
}
|
|
4
|
+
import { HELP_FLAGS } from "./flags.js";
|
|
5
|
+
import { assertAllowedFlag, assignFlag, BOOLEAN_FLAGS, CONTROL_FLAGS, parseFlagName, readValueFlag, SHORT_FLAG_ALIASES, VALUE_FLAGS, } from "./parse-flags.js";
|
|
6
|
+
import { commandPositionalRange, commandRequiresStdin, isCallCommand, resolveCommand, splitAtLiteralBoundary, TASK_SUBCOMMANDS, } from "./parse-metadata.js";
|
|
7
|
+
import { applyCommissionSelector, assertCommissionSelectorAllowed, assertProjectionSelectorPresent, } from "./parse-selectors.js";
|
|
8
|
+
export { commandPositionalRange, commandUsesPromptInput, isCallCommand, isPetitionDryRunInvocation, normalizeContractAddress, parseWaitTimeout, } from "./parse-metadata.js";
|
|
340
9
|
export function parseCliArgs(tokens) {
|
|
341
10
|
const { head, payload } = splitAtLiteralBoundary(tokens);
|
|
342
11
|
const flags = {};
|
|
@@ -411,29 +80,21 @@ export function parseCliArgs(tokens) {
|
|
|
411
80
|
}
|
|
412
81
|
}
|
|
413
82
|
const { command, rest } = resolveCommand(free);
|
|
414
|
-
|
|
415
|
-
throw new FlowError("EMPTY_PARAM", commissionConflictMessage(atTokens[0], contractFlagRaws[0]));
|
|
416
|
-
}
|
|
417
|
-
if (atTokens.length > 1) {
|
|
418
|
-
throw new FlowError("EMPTY_PARAM", multiCommissionSelectorMessage(command, atTokens));
|
|
419
|
-
}
|
|
420
|
-
if (contractFlagRaws.length > 1) {
|
|
421
|
-
throw new FlowError("EMPTY_PARAM", multiCommissionSelectorMessage(command, contractFlagRaws.map((raw) => `--contract ${raw}`)));
|
|
422
|
-
}
|
|
423
|
-
if (atTokens.length === 1) {
|
|
424
|
-
flags.contractId = normalizeContractAddress(atTokens[0]);
|
|
425
|
-
flags.contractAddressSource = "at-address";
|
|
426
|
-
}
|
|
427
|
-
else if (contractFlagRaws.length === 1) {
|
|
428
|
-
assignFlag(flags, "contract", contractFlagRaws[0]);
|
|
429
|
-
}
|
|
83
|
+
applyCommissionSelector(command, flags, atTokens, contractFlagRaws);
|
|
430
84
|
if (helpRequested) {
|
|
431
85
|
return {
|
|
432
86
|
command,
|
|
433
|
-
flags: flags.contractId
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
87
|
+
flags: flags.contractId
|
|
88
|
+
? {
|
|
89
|
+
contractId: flags.contractId,
|
|
90
|
+
contractAddressSource: flags.contractAddressSource,
|
|
91
|
+
...(flags.cwd ? { cwd: flags.cwd } : {}),
|
|
92
|
+
...(flags.repo ? { repo: flags.repo } : {}),
|
|
93
|
+
}
|
|
94
|
+
: {
|
|
95
|
+
...(flags.cwd ? { cwd: flags.cwd } : {}),
|
|
96
|
+
...(flags.repo ? { repo: flags.repo } : {}),
|
|
97
|
+
},
|
|
437
98
|
positional: [],
|
|
438
99
|
helpRequested: true,
|
|
439
100
|
};
|
|
@@ -447,7 +108,6 @@ export function parseCliArgs(tokens) {
|
|
|
447
108
|
if (command === "task") {
|
|
448
109
|
throw new FlowError("EMPTY_PARAM", `task requires subcommand: ${TASK_SUBCOMMANDS.join(", ")}`);
|
|
449
110
|
}
|
|
450
|
-
// --place is bind's H1 domain flag; every other command keeps the retired-selector mouth.
|
|
451
111
|
if (command !== "bind" && seenFlagNames.has("place")) {
|
|
452
112
|
throw new FlowError("EMPTY_PARAM", "unknown option --place; use @addr or --contract <addr>");
|
|
453
113
|
}
|
|
@@ -471,22 +131,8 @@ export function parseCliArgs(tokens) {
|
|
|
471
131
|
if (command === "petition" && flags.json && !flags.dryRun) {
|
|
472
132
|
throw new FlowError("EMPTY_PARAM", "petition --json requires --dry-run");
|
|
473
133
|
}
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
? `bind creates a commission and does not accept @${flags.contractId}; use -C/--cwd to select the repository`
|
|
477
|
-
: `contract address @${flags.contractId} is not valid for ${command}`);
|
|
478
|
-
}
|
|
479
|
-
if ((command === "wait" || command === "tell") && flags.contractId !== undefined) {
|
|
480
|
-
const selectorSpelling = commissionSelectorSpelling(flags.contractAddressSource === "at-address" ? "at-address" : "explicit-flag", flags.contractId, flags.contractAddressSource === "at-address" ? atTokens[0] : contractFlagRaws[0]);
|
|
481
|
-
const scopeOnlyMessage = `${command} requires a projection id or alias; ${selectorSpelling} only limits projection lookup, so add the projection address`;
|
|
482
|
-
if (command === "wait" && rest.length + payload.length === 0) {
|
|
483
|
-
throw new FlowError("EMPTY_PARAM", scopeOnlyMessage);
|
|
484
|
-
}
|
|
485
|
-
if (command === "tell" && rest.length === 0) {
|
|
486
|
-
// Payload after -- is the message body, never the projection address.
|
|
487
|
-
throw new FlowError("EMPTY_PARAM", scopeOnlyMessage);
|
|
488
|
-
}
|
|
489
|
-
}
|
|
134
|
+
assertCommissionSelectorAllowed(command, flags);
|
|
135
|
+
assertProjectionSelectorPresent(command, flags, atTokens, contractFlagRaws, rest.length, payload.length);
|
|
490
136
|
const positional = [...rest, ...payload];
|
|
491
137
|
if (isCallCommand(command)) {
|
|
492
138
|
if (flags.akuma) {
|
|
@@ -7,7 +7,7 @@ import { FlowError, asMessage } from "../flow-error.js";
|
|
|
7
7
|
import { VERSION } from "../generated/version.js";
|
|
8
8
|
const MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
9
9
|
const PACKAGE_ROOT = path.resolve(MODULE_DIR, "../..");
|
|
10
|
-
const OFFICIAL_SKILLS = ["keiyaku", "keiyaku-akuma"];
|
|
10
|
+
const OFFICIAL_SKILLS = ["keiyaku", "keiyaku-akuma", "keiyaku-workflow", "keiyaku-task"];
|
|
11
11
|
const SKILL_ROOT = path.join(PACKAGE_ROOT, "skills");
|
|
12
12
|
const INSTALL_MARKER = ".keiyaku-skill-owned.json";
|
|
13
13
|
function skillSource(skill) {
|
|
@@ -2,7 +2,7 @@ import * as fs from "node:fs/promises";
|
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { parseToAST } from "../core/markdown/parser.js";
|
|
5
|
-
import {
|
|
5
|
+
import { observeGitRepository } from "../git/branches.js";
|
|
6
6
|
import { BUILTIN_AGENT_PROFILES } from "../keiyaku.js";
|
|
7
7
|
import { getKeiyakuHome } from "./env.js";
|
|
8
8
|
import { agentNameSchema, providerInstanceNameSchema } from "./settings/schema.js";
|
|
@@ -211,7 +211,10 @@ export async function loadAkumaCatalog(cwd) {
|
|
|
211
211
|
selected[name] = candidate;
|
|
212
212
|
}
|
|
213
213
|
foldLayer(selected, shadows, candidates, "user", await readLayer(path.join(getKeiyakuHome(), "akuma"), "user"));
|
|
214
|
-
|
|
214
|
+
const repository = await observeGitRepository(cwd);
|
|
215
|
+
if (repository.capability === "unknown")
|
|
216
|
+
throw repository.error;
|
|
217
|
+
if (repository.capability === "available" && repository.coordinate === "repository") {
|
|
215
218
|
foldLayer(selected, shadows, candidates, "project", await readLayer(path.join(cwd, ".keiyaku", "akuma"), "project"));
|
|
216
219
|
}
|
|
217
220
|
return Object.freeze({
|
package/build/core/amend.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as fs from "node:fs/promises";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { FlowError } from "../flow-error.js";
|
|
4
|
-
import { getCurrentBranch,
|
|
4
|
+
import { getCurrentBranch, requireGitRepository } from "../git/branches.js";
|
|
5
5
|
import { KEIYAKU_FILE } from "../keiyaku.js";
|
|
6
6
|
import { activeLedgerRef, appendWithRetry, readLedger } from "./ledger.js";
|
|
7
7
|
import { renderContractView } from "./contract-view.js";
|
|
@@ -71,10 +71,7 @@ export async function amendContract(input) {
|
|
|
71
71
|
return { contractId: input.contractId, amendmentNumber, ledgerHead: result.head };
|
|
72
72
|
}
|
|
73
73
|
export async function amendKeiyaku(input) {
|
|
74
|
-
|
|
75
|
-
if (!isRepo) {
|
|
76
|
-
throw new FlowError("NOT_GIT_REPO", `${input.cwd} is not a git repository`);
|
|
77
|
-
}
|
|
74
|
+
await requireGitRepository(input.cwd, "amend");
|
|
78
75
|
const address = await resolveContractAddress(input.cwd, input.contractId, input.contractAddressSource);
|
|
79
76
|
return await withResolvedAddress(address, async () => {
|
|
80
77
|
const contractId = address.binding.commissionId;
|
package/build/core/arc.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as fs from "node:fs/promises";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { requireText, FlowError } from "../flow-error.js";
|
|
4
|
-
import { getCurrentBranch,
|
|
4
|
+
import { getCurrentBranch, requireGitRepository } from "../git/branches.js";
|
|
5
5
|
import { DIRTY_FILE_CATEGORY, getDirtyFiles, renderDirtyFileStatusLine } from "../git/worktree.js";
|
|
6
6
|
import { KEIYAKU_FILE, ACTOR_IDENTITY } from "../keiyaku.js";
|
|
7
7
|
import { buildDirtyWorktreeArtifactHint } from "./hints.js";
|
|
@@ -161,9 +161,7 @@ async function readBranchLabels(cwd) {
|
|
|
161
161
|
return { currentBranch, baseBranch };
|
|
162
162
|
}
|
|
163
163
|
export async function arcKeiyaku(input) {
|
|
164
|
-
|
|
165
|
-
throw new FlowError("NOT_GIT_REPO", `${input.cwd} is not a git repository`);
|
|
166
|
-
}
|
|
164
|
+
await requireGitRepository(input.cwd, "arc");
|
|
167
165
|
const address = await resolveContractAddress(input.cwd, input.contractId, input.contractAddressSource);
|
|
168
166
|
return await withResolvedAddress(address, async () => {
|
|
169
167
|
await assertCleanWorkingTree(input.cwd, []);
|
package/build/core/call/call.js
CHANGED
|
@@ -3,7 +3,7 @@ import { selectSubagent } from "../../agents/selector.js";
|
|
|
3
3
|
import { applySnapshotEffort } from "../../agents/launch-snapshot/model.js";
|
|
4
4
|
import { resolveAkumaLaunchSnapshot } from "../../agents/launch-snapshot/resolve.js";
|
|
5
5
|
import { FlowError, requireText } from "../../flow-error.js";
|
|
6
|
-
import {
|
|
6
|
+
import { assertGitObservation, observeGitRepository } from "../../git/branches.js";
|
|
7
7
|
import { ACTOR_IDENTITY } from "../../keiyaku.js";
|
|
8
8
|
import { appendDebugBlock } from "../../telemetry/debug-log.js";
|
|
9
9
|
import { logInfo } from "../../telemetry/logger.js";
|
|
@@ -54,12 +54,17 @@ export async function runCall(input) {
|
|
|
54
54
|
else if (reviveArtifact?.provenance.authority.kind === "repository") {
|
|
55
55
|
const artifactHome = await fs.realpath(reviveArtifact.storageRoot);
|
|
56
56
|
const addressed = input.repo ?? input.cwd;
|
|
57
|
-
|
|
57
|
+
const observation = await observeGitRepository(addressed);
|
|
58
|
+
const coordinate = assertGitObservation(observation, "revive");
|
|
59
|
+
if (coordinate === "repository") {
|
|
58
60
|
const addressedRoot = await stableRepoRoot(addressed);
|
|
59
61
|
if (addressedRoot !== artifactHome) {
|
|
60
62
|
throw new FlowError("INVALID_RESPONSE_PATH", `Response artifact '${reviveArtifact.artifactId}' belongs to ${artifactHome}, but the addressed repository is ${addressedRoot}.`);
|
|
61
63
|
}
|
|
62
64
|
}
|
|
65
|
+
else {
|
|
66
|
+
throw new FlowError("NOT_GIT_REPO", `${addressed} is not a git repository`);
|
|
67
|
+
}
|
|
63
68
|
rawCwd = artifactHome;
|
|
64
69
|
rawRepo = artifactHome;
|
|
65
70
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as fs from "fs/promises";
|
|
2
2
|
import { FlowError } from "../../flow-error.js";
|
|
3
|
-
import {
|
|
3
|
+
import { assertGitObservation, observeGitRepository } from "../../git/branches.js";
|
|
4
4
|
import { createGit, wrapGitError } from "../../git/core.js";
|
|
5
5
|
import { AddressResolutionError, buildResolutionAttempt, buildUncommissionedAddress, buildUncommissionedRepositoryAddress, resolveContractAddress, withResolvedAddress, } from "../addressing.js";
|
|
6
6
|
import { findOpenArcView } from "../arc.js";
|
|
@@ -48,8 +48,16 @@ async function resolveWorkspaceCwd(cwd, contractId, bind) {
|
|
|
48
48
|
}
|
|
49
49
|
export async function readCallKeiyakuContext(executionCwd, input) {
|
|
50
50
|
const ledgerCandidate = input.repoCwd ?? executionCwd;
|
|
51
|
-
const
|
|
52
|
-
if (
|
|
51
|
+
const ledgerObservation = await observeGitRepository(ledgerCandidate);
|
|
52
|
+
if (ledgerObservation.capability === "unknown")
|
|
53
|
+
throw ledgerObservation.error;
|
|
54
|
+
if (ledgerObservation.capability === "unavailable" && (input.repoCwd || input.contractId)) {
|
|
55
|
+
assertGitObservation(ledgerObservation, input.repoCwd ? `${input.mode ?? "call"} --repo` : "contract addressing");
|
|
56
|
+
}
|
|
57
|
+
if (input.repoCwd && ledgerObservation.capability === "available" && ledgerObservation.coordinate === "non-repository") {
|
|
58
|
+
throw new FlowError("NOT_GIT_REPO", `${ledgerCandidate} is not a git repository`);
|
|
59
|
+
}
|
|
60
|
+
if (ledgerObservation.capability !== "available" || ledgerObservation.coordinate !== "repository") {
|
|
53
61
|
if (input.bare && input.contractId) {
|
|
54
62
|
const asTyped = input.contractAddressSource === "at-address"
|
|
55
63
|
? `@${input.contractId}`
|
|
@@ -146,18 +154,23 @@ export async function readCallKeiyakuContext(executionCwd, input) {
|
|
|
146
154
|
if (input.contractId) {
|
|
147
155
|
address = await resolveContractAddress(ledgerCwd, input.contractId, input.contractAddressSource);
|
|
148
156
|
}
|
|
149
|
-
else
|
|
150
|
-
const
|
|
151
|
-
if (
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
157
|
+
else {
|
|
158
|
+
const executionObservation = await observeGitRepository(executionCwd);
|
|
159
|
+
if (executionObservation.capability === "unknown")
|
|
160
|
+
throw executionObservation.error;
|
|
161
|
+
if (executionObservation.capability === "available" && executionObservation.coordinate === "repository") {
|
|
162
|
+
const executionRepo = await stableRepoRoot(executionCwd);
|
|
163
|
+
if (input.repoCwd && executionRepo !== ledgerCwd) {
|
|
164
|
+
const presented = await contractPresentedByWorktree(executionCwd);
|
|
165
|
+
if (presented) {
|
|
166
|
+
throw new FlowError("EMPTY_PARAM", `cross-repository contract-worktree addressing is pending: -C presents @${presented} from ${executionRepo}, but --repo selects ${ledgerCwd}`);
|
|
167
|
+
}
|
|
155
168
|
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
169
|
+
else {
|
|
170
|
+
const presented = await contractPresentedByWorktree(executionCwd);
|
|
171
|
+
if (presented) {
|
|
172
|
+
address = await resolveContractAddress(ledgerCwd, presented, "worktree-presented");
|
|
173
|
+
}
|
|
161
174
|
}
|
|
162
175
|
}
|
|
163
176
|
}
|
|
@@ -2,7 +2,8 @@ import { appendDebugLog } from "../telemetry/debug-log.js";
|
|
|
2
2
|
import { logWarn } from "../telemetry/logger.js";
|
|
3
3
|
import { runCall } from "./call/call.js";
|
|
4
4
|
import { persistResponseHistory } from "./transcripts.js";
|
|
5
|
-
import {
|
|
5
|
+
import { assertGitObservation, observeGitRepository } from "../git/branches.js";
|
|
6
|
+
import { FlowError } from "../flow-error.js";
|
|
6
7
|
import * as path from "node:path";
|
|
7
8
|
import * as fs from "node:fs/promises";
|
|
8
9
|
import { stableRepoRoot } from "./worktree-path.js";
|
|
@@ -14,7 +15,13 @@ function responsePathFromPrintingCwd(historyStorageCwd, responsePath) {
|
|
|
14
15
|
export async function runCallAndPersist(input) {
|
|
15
16
|
const historyCandidate = input.repo ?? input.cwd;
|
|
16
17
|
const canonicalHistoryCandidate = await fs.realpath(historyCandidate);
|
|
17
|
-
const
|
|
18
|
+
const historyObservation = await observeGitRepository(historyCandidate);
|
|
19
|
+
if (input.repo && assertGitObservation(historyObservation, `${input.mode ?? "call"} --repo`) === "non-repository") {
|
|
20
|
+
throw new FlowError("NOT_GIT_REPO", `${historyCandidate} is not a git repository`);
|
|
21
|
+
}
|
|
22
|
+
if (historyObservation.capability === "unknown")
|
|
23
|
+
throw historyObservation.error;
|
|
24
|
+
const historyStorageCwd = (historyObservation.capability === "available" && historyObservation.coordinate === "repository")
|
|
18
25
|
? await stableRepoRoot(historyCandidate)
|
|
19
26
|
: canonicalHistoryCandidate;
|
|
20
27
|
const historyDisplayCwd = path.resolve(historyCandidate, path.relative(canonicalHistoryCandidate, historyStorageCwd));
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as fsPromises from "node:fs/promises";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
/**
|
|
5
|
+
* Git for Windows may emit native, slash-normalized, or MSYS drive paths.
|
|
6
|
+
* Keep one host-native spelling before using a coordinate as a filesystem cwd.
|
|
7
|
+
*/
|
|
8
|
+
export function normalizeGitPath(raw, platform = process.platform) {
|
|
9
|
+
const value = raw.trim();
|
|
10
|
+
if (platform !== "win32")
|
|
11
|
+
return path.normalize(value);
|
|
12
|
+
const msysDrive = value.match(/^\/([A-Za-z])(?:\/|$)(.*)$/);
|
|
13
|
+
if (msysDrive)
|
|
14
|
+
return path.win32.normalize(`${msysDrive[1].toUpperCase()}:/${msysDrive[2]}`);
|
|
15
|
+
const normalized = path.win32.normalize(value);
|
|
16
|
+
return normalized.replace(/^([a-z]):/, (_, drive) => `${drive.toUpperCase()}:`);
|
|
17
|
+
}
|
|
18
|
+
export function gitCommonRootPath(raw, platform = process.platform) {
|
|
19
|
+
const paths = platform === "win32" ? path.win32 : path.posix;
|
|
20
|
+
return paths.dirname(normalizeGitPath(raw, platform));
|
|
21
|
+
}
|
|
22
|
+
export async function resolveGitCommonRoot(raw) {
|
|
23
|
+
return await fsPromises.realpath(gitCommonRootPath(raw));
|
|
24
|
+
}
|
|
25
|
+
export function resolveGitCommonRootSync(raw) {
|
|
26
|
+
return fs.realpathSync(gitCommonRootPath(raw));
|
|
27
|
+
}
|
|
@@ -4,7 +4,7 @@ import { FlowError } from "../flow-error.js";
|
|
|
4
4
|
import { readCurrentContractContext } from "./context.js";
|
|
5
5
|
import { readLedger } from "./ledger.js";
|
|
6
6
|
import { commissionTarget } from "./status/drift.js";
|
|
7
|
-
import { getCurrentBranch,
|
|
7
|
+
import { getCurrentBranch, requireGitRepository } from "../git/branches.js";
|
|
8
8
|
import { claimKeiyaku } from "./petition-claim.js";
|
|
9
9
|
import { forfeitKeiyaku } from "./petition-forfeit.js";
|
|
10
10
|
const VERDICT_DENIED_CODE = "VERDICT_DENIED";
|
|
@@ -33,10 +33,7 @@ async function resolveClaimGateConfig(cwd) {
|
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
async function loadCloseContext(cwd, requestedContractId) {
|
|
36
|
-
|
|
37
|
-
if (!isRepo) {
|
|
38
|
-
throw new FlowError("NOT_GIT_REPO", `${cwd} is not a git repository`);
|
|
39
|
-
}
|
|
36
|
+
await requireGitRepository(cwd, "petition");
|
|
40
37
|
if (requestedContractId) {
|
|
41
38
|
const ledger = await readLedger(cwd, requestedContractId);
|
|
42
39
|
if (!ledger) {
|