@astrosheep/keiyaku 4.5.4 → 4.5.6
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/integrations/marketplace/plugins/keiyaku/skills/keiyaku-task/SKILL.md +15 -19
- package/build/src/cli/commands/install.js +2 -1
- package/build/src/cli/commands/task-invoke.js +2 -9
- package/build/src/task/compose-language.js +13 -6
- package/build/src/task/compose-parser.js +43 -27
- package/build/src/task/compose.d.ts +9 -1
- package/build/src/task/compose.js +14 -13
- package/build/src/task/index.d.ts +2 -2
- package/build/src/task/index.js +11 -2
- package/package.json +2 -2
|
@@ -70,30 +70,26 @@ Relations are explicit facts: `needs` orders work, `parent` groups it,
|
|
|
70
70
|
## Batch Create Or Modify
|
|
71
71
|
|
|
72
72
|
Use `task compose -` to create and modify multiple Tasks in one planning
|
|
73
|
-
document:
|
|
73
|
+
document. Its input grammar is documented by the Task CLI owner:
|
|
74
74
|
|
|
75
75
|
```bash
|
|
76
76
|
keiyaku task compose - <<'EOF'
|
|
77
77
|
ns=feature
|
|
78
|
-
|
|
78
|
+
|
|
79
|
+
+ Parent
|
|
80
|
+
as = parent
|
|
81
|
+
pri = 0
|
|
82
|
+
body <<BODY
|
|
79
83
|
Parent body.
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
84
|
+
BODY
|
|
85
|
+
|
|
86
|
+
+ Child
|
|
87
|
+
parent = ^parent
|
|
88
|
+
needs = @task/existing
|
|
83
89
|
EOF
|
|
84
90
|
```
|
|
85
91
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
- Inline assignments are `pri=`, `parent=`, `needs=`, `supersedes=`, and
|
|
91
|
-
`relates=`. Relation values are comma-separated `@task/...` references.
|
|
92
|
-
- `=` replaces or clears a field. `+=` appends only to `needs`, `supersedes`,
|
|
93
|
-
or `relates`.
|
|
94
|
-
- Prose after a node replaces its body; bare `body=` clears it. Prefix body
|
|
95
|
-
lines beginning with `+ `, `@task/`, or `\` with one `\`.
|
|
96
|
-
|
|
97
|
-
Compose does not change Task lifecycle state. Each changed Task is admitted
|
|
98
|
-
independently; an incomplete result returns a canonical draft for the remaining
|
|
99
|
-
batch.
|
|
92
|
+
Use `--plan` to inspect aliases, admission order, and body byte previews without
|
|
93
|
+
writing. Compose does not change Task lifecycle state. Each changed Task is
|
|
94
|
+
admitted independently; an incomplete result returns a reusable draft for the
|
|
95
|
+
remaining batch.
|
|
@@ -47,6 +47,7 @@ export function renderInstallHelp() {
|
|
|
47
47
|
const INSTALL_TIMEOUT_MS = 5 * 60 * 1000;
|
|
48
48
|
const ASSET_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "../../../integrations/marketplace");
|
|
49
49
|
const PLUGIN_ROOT = resolve(ASSET_ROOT, "plugins/keiyaku");
|
|
50
|
+
const PI_PACKAGE_SOURCE = "npm:@astrosheep/keiyaku";
|
|
50
51
|
function recipes(harness) {
|
|
51
52
|
switch (harness) {
|
|
52
53
|
case "codex":
|
|
@@ -63,7 +64,7 @@ function recipes(harness) {
|
|
|
63
64
|
case "opencode":
|
|
64
65
|
return [["opencode", "plugin", PLUGIN_ROOT, "--global", "--force"]];
|
|
65
66
|
case "pi":
|
|
66
|
-
return [["pi", "install",
|
|
67
|
+
return [["pi", "install", PI_PACKAGE_SOURCE]];
|
|
67
68
|
}
|
|
68
69
|
}
|
|
69
70
|
function diagnostic(argv, outcome) {
|
|
@@ -294,15 +294,8 @@ function missingWorld(command) {
|
|
|
294
294
|
});
|
|
295
295
|
if (isWorldObservation(command))
|
|
296
296
|
return { kind: "absent" };
|
|
297
|
-
if (command.action === "compose" && command.flags.plan === true)
|
|
298
|
-
return {
|
|
299
|
-
kind: "refused",
|
|
300
|
-
refusal: {
|
|
301
|
-
kind: "invalid-composition",
|
|
302
|
-
diagnostics: [{ line: 1, reason: "task world has no valid planning coordinate", token: "--plan" }],
|
|
303
|
-
},
|
|
304
|
-
};
|
|
305
|
-
}
|
|
297
|
+
if (command.action === "compose" && command.flags.plan === true)
|
|
298
|
+
return { kind: "absent" };
|
|
306
299
|
if (command.action === "namespace")
|
|
307
300
|
return { kind: "accepted", value: [] };
|
|
308
301
|
if (command.action === "hold" || command.action === "done" || command.action === "drop") {
|
|
@@ -31,17 +31,17 @@ function createdTask(id, title, at, actor) {
|
|
|
31
31
|
function aliasFor(node) {
|
|
32
32
|
return node.assignments.find((item) => item.field === "as");
|
|
33
33
|
}
|
|
34
|
-
function allocateNodes(
|
|
35
|
-
const ids = occupied(board, namespace);
|
|
34
|
+
function allocateNodes(input, diagnostics) {
|
|
35
|
+
const ids = occupied(input.board, input.namespace);
|
|
36
36
|
const allocations = new Map();
|
|
37
|
-
for (const node of parsed.nodes) {
|
|
37
|
+
for (const node of input.parsed.nodes) {
|
|
38
38
|
if (node.kind !== "new")
|
|
39
39
|
continue;
|
|
40
40
|
try {
|
|
41
41
|
const localId = allocateLocalId(deriveLocalStem(node.title), ids);
|
|
42
42
|
ids.add(localId);
|
|
43
|
-
const id = formatTaskId({ namespace, localId });
|
|
44
|
-
allocations.set(node.index, createdTask(id, node.title, at, actor));
|
|
43
|
+
const id = formatTaskId({ namespace: input.namespace, localId });
|
|
44
|
+
allocations.set(node.index, createdTask(id, node.title, input.at, input.actor));
|
|
45
45
|
}
|
|
46
46
|
catch (error) {
|
|
47
47
|
diagnostics.push(diagnostic(node.line, error instanceof Error ? error.message : String(error), node.title));
|
|
@@ -257,7 +257,14 @@ export function planTaskComposition(input) {
|
|
|
257
257
|
const parsed = parseTaskComposition(input.markdown);
|
|
258
258
|
const diagnostics = [...parsed.diagnostics];
|
|
259
259
|
const namespace = parsed.namespace ?? input.namespace;
|
|
260
|
-
const
|
|
260
|
+
const allocationInput = {
|
|
261
|
+
parsed,
|
|
262
|
+
board: input.board,
|
|
263
|
+
namespace,
|
|
264
|
+
at: input.at,
|
|
265
|
+
...(input.actor === undefined ? {} : { actor: input.actor }),
|
|
266
|
+
};
|
|
267
|
+
const allocations = allocateNodes(allocationInput, diagnostics);
|
|
261
268
|
const aliases = collectAliases(parsed.nodes, allocations, diagnostics);
|
|
262
269
|
const addressed = new Set();
|
|
263
270
|
const tasks = [];
|
|
@@ -75,6 +75,41 @@ function withBody(node, body) {
|
|
|
75
75
|
function replaceNode(nodes, node) {
|
|
76
76
|
nodes[node.index] = node;
|
|
77
77
|
}
|
|
78
|
+
function parseBodyAssignment(input, diagnostics) {
|
|
79
|
+
const fence = /^body\s+<<(.+)$/u.exec(input.text);
|
|
80
|
+
if (fence !== null) {
|
|
81
|
+
const token = fence[1];
|
|
82
|
+
if (!/^[A-Z][A-Z0-9_]{2,31}$/u.test(token)) {
|
|
83
|
+
diagnostics.push(diagnostic(input.line.number, "body token must match [A-Z][A-Z0-9_]* with length 3..32", token));
|
|
84
|
+
return { handled: true };
|
|
85
|
+
}
|
|
86
|
+
const close = input.lines.findIndex((candidate, candidateIndex) => candidateIndex > input.index && candidate.text === token);
|
|
87
|
+
if (close < 0) {
|
|
88
|
+
diagnostics.push(diagnostic(input.line.number, "body fence is not closed", token));
|
|
89
|
+
return { handled: true, stop: true };
|
|
90
|
+
}
|
|
91
|
+
const body = withoutFenceSeparator(input.source.slice(input.line.end, input.lines[close].start));
|
|
92
|
+
if (input.current.body !== undefined) {
|
|
93
|
+
diagnostics.push(diagnostic(input.line.number, "body is assigned more than once", input.text));
|
|
94
|
+
return { handled: true, close };
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
handled: true,
|
|
98
|
+
node: withBody(input.current, { kind: "replace", value: body, line: input.line.number, token }),
|
|
99
|
+
close,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
if (!/^body\s*=\s*$/u.test(input.text))
|
|
103
|
+
return { handled: false };
|
|
104
|
+
if (input.current.body !== undefined) {
|
|
105
|
+
diagnostics.push(diagnostic(input.line.number, "body is assigned more than once", input.text));
|
|
106
|
+
return { handled: true };
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
handled: true,
|
|
110
|
+
node: withBody(input.current, { kind: "clear", value: "", line: input.line.number, token: input.text }),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
78
113
|
export function parseTaskComposition(source) {
|
|
79
114
|
const lines = sourceLines(source);
|
|
80
115
|
const diagnostics = [];
|
|
@@ -119,35 +154,16 @@ export function parseTaskComposition(source) {
|
|
|
119
154
|
diagnostics.push(diagnostic(line.number, "property must follow a task node", text));
|
|
120
155
|
continue;
|
|
121
156
|
}
|
|
122
|
-
const
|
|
123
|
-
if (
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
diagnostics.push(diagnostic(line.number, "body token must match [A-Z][A-Z0-9_]* with length 3..32", token));
|
|
127
|
-
continue;
|
|
128
|
-
}
|
|
129
|
-
const close = lines.findIndex((candidate, candidateIndex) => candidateIndex > index && candidate.text === token);
|
|
130
|
-
if (close < 0) {
|
|
131
|
-
diagnostics.push(diagnostic(line.number, "body fence is not closed", token));
|
|
132
|
-
break;
|
|
133
|
-
}
|
|
134
|
-
const body = withoutFenceSeparator(source.slice(line.end, lines[close].start));
|
|
135
|
-
if (current.body !== undefined)
|
|
136
|
-
diagnostics.push(diagnostic(line.number, "body is assigned more than once", text));
|
|
137
|
-
else {
|
|
138
|
-
current = withBody(current, { kind: "replace", value: body, line: line.number, token });
|
|
139
|
-
replaceNode(nodes, current);
|
|
140
|
-
}
|
|
141
|
-
index = close;
|
|
142
|
-
continue;
|
|
143
|
-
}
|
|
144
|
-
if (/^body\s*=\s*$/u.test(text)) {
|
|
145
|
-
if (current.body !== undefined)
|
|
146
|
-
diagnostics.push(diagnostic(line.number, "body is assigned more than once", text));
|
|
147
|
-
else {
|
|
148
|
-
current = withBody(current, { kind: "clear", value: "", line: line.number, token: text });
|
|
157
|
+
const body = parseBodyAssignment({ text, line, index, lines, source, current }, diagnostics);
|
|
158
|
+
if (body.handled) {
|
|
159
|
+
if (body.node !== undefined) {
|
|
160
|
+
current = body.node;
|
|
149
161
|
replaceNode(nodes, current);
|
|
150
162
|
}
|
|
163
|
+
if (body.close !== undefined)
|
|
164
|
+
index = body.close;
|
|
165
|
+
if (body.stop === true)
|
|
166
|
+
break;
|
|
151
167
|
continue;
|
|
152
168
|
}
|
|
153
169
|
const assignment = parseAssignment(text, line.number);
|
|
@@ -2,6 +2,7 @@ import type { WorldRoot } from "../world.js";
|
|
|
2
2
|
import { type TaskCompositionAlias, type TaskCompositionBodyPreview } from "./compose-language.js";
|
|
3
3
|
import type { TaskId } from "./identity.js";
|
|
4
4
|
import type { TaskRefusal, TaskRetry } from "./operations.js";
|
|
5
|
+
export type { TaskCompositionAlias, TaskCompositionBodyPreview } from "./compose-language.js";
|
|
5
6
|
export type TaskDocumentChange = Readonly<{
|
|
6
7
|
taskId: TaskId;
|
|
7
8
|
kind: "created" | "updated";
|
|
@@ -33,4 +34,11 @@ export type TaskCompositionResult = Readonly<{
|
|
|
33
34
|
}>;
|
|
34
35
|
draft: string;
|
|
35
36
|
}> & TaskCompositionFacts);
|
|
36
|
-
export declare function composeTasks(
|
|
37
|
+
export declare function composeTasks(input: Readonly<{
|
|
38
|
+
world: WorldRoot;
|
|
39
|
+
markdown: string;
|
|
40
|
+
signal?: AbortSignal;
|
|
41
|
+
actor?: string;
|
|
42
|
+
defaultNamespace?: readonly string[];
|
|
43
|
+
planOnly?: boolean;
|
|
44
|
+
}>): Promise<TaskCompositionResult>;
|
|
@@ -107,17 +107,11 @@ async function composeUnderLocks(input) {
|
|
|
107
107
|
return await admitPlan(input.world, fresh, replanned.plan, input.signal);
|
|
108
108
|
});
|
|
109
109
|
}
|
|
110
|
-
function composeInput(
|
|
111
|
-
return
|
|
112
|
-
world,
|
|
113
|
-
markdown,
|
|
114
|
-
namespace,
|
|
115
|
-
at,
|
|
116
|
-
...(actor === undefined ? {} : { actor }),
|
|
117
|
-
...(signal === undefined ? {} : { signal }),
|
|
118
|
-
};
|
|
110
|
+
function composeInput(input) {
|
|
111
|
+
return input;
|
|
119
112
|
}
|
|
120
|
-
export async function composeTasks(
|
|
113
|
+
export async function composeTasks(input) {
|
|
114
|
+
const { world, markdown, signal, actor, defaultNamespace = [], planOnly = false } = input;
|
|
121
115
|
const at = currentTimestamp();
|
|
122
116
|
const initial = await readBoard(world);
|
|
123
117
|
const initialPlan = planAgainst(markdown, initial.board, defaultNamespace, at, actor);
|
|
@@ -125,11 +119,18 @@ export async function composeTasks(world, markdown, signal, actor, defaultNamesp
|
|
|
125
119
|
return refusal(initialPlan.diagnostics);
|
|
126
120
|
if (planOnly)
|
|
127
121
|
return plannedResult(initialPlan.plan);
|
|
128
|
-
const
|
|
122
|
+
const request = composeInput({
|
|
123
|
+
world,
|
|
124
|
+
markdown,
|
|
125
|
+
namespace: defaultNamespace,
|
|
126
|
+
at,
|
|
127
|
+
...(actor === undefined ? {} : { actor }),
|
|
128
|
+
...(signal === undefined ? {} : { signal }),
|
|
129
|
+
});
|
|
129
130
|
const allocation = initialPlan.plan.tasks.some((task) => task.kind === "new");
|
|
130
131
|
const admitted = allocation
|
|
131
|
-
? await withTaskLocks({ world, allocation: true, ids: [], ...(signal === undefined ? {} : { signal }) }, async () => await composeUnderLocks(
|
|
132
|
-
: await composeUnderLocks(
|
|
132
|
+
? await withTaskLocks({ world, allocation: true, ids: [], ...(signal === undefined ? {} : { signal }) }, async () => await composeUnderLocks(request))
|
|
133
|
+
: await composeUnderLocks(request);
|
|
133
134
|
if (admitted !== "busy")
|
|
134
135
|
return admitted;
|
|
135
136
|
return {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { WorldRoot } from "../world.js";
|
|
2
2
|
export type { WorldRoot } from "../world.js";
|
|
3
3
|
import { type BlockedTaskRow, type TaskDetailFacts, type TaskDoctorIssue, type TaskRef, type TaskRow, type TaskTreeNode } from "./board.js";
|
|
4
|
-
import { type TaskCompositionResult } from "./compose.js";
|
|
4
|
+
import { type TaskCompositionAlias, type TaskCompositionBodyPreview, type TaskCompositionResult } from "./compose.js";
|
|
5
5
|
import { TaskAuthorityCorruptionError, type TaskPriority, type TaskState } from "./document.js";
|
|
6
6
|
import type { TaskId } from "./identity.js";
|
|
7
7
|
import { type AddTaskDocumentInput, type AddTaskInput, type TaskBatchResult, type TaskCompositionDiagnostic, type TaskMutationResult, type TaskOutcome, type TaskRefusal, type TaskRetry, type TaskUpdateResult, type TaskView, type UpdateTaskInput } from "./operations.js";
|
|
@@ -17,7 +17,7 @@ export type TaskDoctorReport = Readonly<{
|
|
|
17
17
|
issues: readonly TaskDoctorIssue[];
|
|
18
18
|
}>;
|
|
19
19
|
export type TaskDecompositionTree = TaskOutcome<TaskTreeNode>;
|
|
20
|
-
export type { AddTaskDocumentInput, AddTaskInput, BlockedTaskRow, TaskBatchResult, TaskCompositionDiagnostic, TaskCompositionResult, TaskDoctorIssue, TaskId, TaskMutationResult, TaskOutcome, TaskPriority, TaskRef, TaskRefusal, TaskRetry, TaskRow, TaskState, TaskTreeNode, TaskUpdateResult, TaskView, UpdateTaskInput, TaskPage, TaskQueryExpression, TaskQueryPredicate, TaskQueryRow, TaskQuerySort, TaskRelationPredicateField, };
|
|
20
|
+
export type { AddTaskDocumentInput, AddTaskInput, BlockedTaskRow, TaskBatchResult, TaskCompositionDiagnostic, TaskCompositionAlias, TaskCompositionBodyPreview, TaskCompositionResult, TaskDoctorIssue, TaskId, TaskMutationResult, TaskOutcome, TaskPriority, TaskRef, TaskRefusal, TaskRetry, TaskRow, TaskState, TaskTreeNode, TaskUpdateResult, TaskView, UpdateTaskInput, TaskPage, TaskQueryExpression, TaskQueryPredicate, TaskQueryRow, TaskQuerySort, TaskRelationPredicateField, };
|
|
21
21
|
export { TaskAuthorityCorruptionError, TASK_RELATION_PREDICATE_FIELDS };
|
|
22
22
|
declare class TaskHandle {
|
|
23
23
|
readonly id: TaskId;
|
package/build/src/task/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { buildTree, createTaskRelations, diagnoseBoard, projectDetailFacts, } from "./board.js";
|
|
2
|
-
import { composeTasks } from "./compose.js";
|
|
2
|
+
import { composeTasks, } from "./compose.js";
|
|
3
3
|
import { TaskAuthorityCorruptionError } from "./document.js";
|
|
4
4
|
import { addTask, addTaskDocument, batchTasks, blockedTasks, lifecycleTask, listTasks, queryTasks, readyTasks, taskView, updateTask, } from "./operations.js";
|
|
5
5
|
import { readBoard } from "./store.js";
|
|
@@ -156,7 +156,16 @@ class TasksHandle {
|
|
|
156
156
|
if (v.plan !== undefined && typeof v.plan !== "boolean")
|
|
157
157
|
throw new TypeError("plan must be a boolean");
|
|
158
158
|
const selected = namespace(v.namespace);
|
|
159
|
-
|
|
159
|
+
const selectedSignal = signal(v.signal);
|
|
160
|
+
const selectedActor = actor(v.actor);
|
|
161
|
+
return composeTasks({
|
|
162
|
+
world: this.world,
|
|
163
|
+
markdown,
|
|
164
|
+
...(selectedSignal === undefined ? {} : { signal: selectedSignal }),
|
|
165
|
+
...(selectedActor === undefined ? {} : { actor: selectedActor }),
|
|
166
|
+
...(selected === undefined ? {} : { defaultNamespace: selected }),
|
|
167
|
+
planOnly: v.plan === true,
|
|
168
|
+
});
|
|
160
169
|
}
|
|
161
170
|
}
|
|
162
171
|
export const Tasks = Object.freeze({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astrosheep/keiyaku",
|
|
3
|
-
"version": "4.5.
|
|
3
|
+
"version": "4.5.6",
|
|
4
4
|
"files": [
|
|
5
5
|
"build"
|
|
6
6
|
],
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
"dependencies": {
|
|
65
65
|
"@agentclientprotocol/sdk": "^1.3.0",
|
|
66
66
|
"@anthropic-ai/claude-agent-sdk": "^0.3.226",
|
|
67
|
-
"@astrosheep/square": "0.3.
|
|
67
|
+
"@astrosheep/square": "0.3.28",
|
|
68
68
|
"@earendil-works/pi-coding-agent": "0.80.10",
|
|
69
69
|
"@opencode-ai/sdk": "1.18.3",
|
|
70
70
|
"cross-spawn": "^7.0.6",
|