@astrosheep/keiyaku 4.6.1 → 4.6.3
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 +93 -45
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-bind/SKILL.md +3 -2
- package/build/src/akuma/providers/pi/events.js +3 -2
- package/build/src/akuma/providers/pi/index.d.ts +3 -4
- package/build/src/akuma/providers/pi/index.js +30 -26
- package/build/src/body/render.d.ts +9 -1
- package/build/src/body/render.js +11 -0
- package/build/src/cli/commands/akuma-grammar.js +12 -3
- package/build/src/cli/commands/akuma.js +6 -0
- package/build/src/cli/commands/contract-grammar.js +7 -0
- package/build/src/cli/commands/contract-help.d.ts +1 -1
- package/build/src/cli/commands/contract-help.js +21 -9
- package/build/src/cli/commands/contract.js +3 -1
- package/build/src/cli/commands/task-grammar.js +9 -7
- package/build/src/cli/parse.js +5 -0
- package/build/src/cli/render/akuma-activity.d.ts +26 -6
- package/build/src/cli/render/akuma-activity.js +128 -118
- package/build/src/cli/render/akuma-tool.js +1 -1
- package/build/src/cli/render/audit.js +4 -6
- package/build/src/cli/render/catalog.js +7 -4
- package/build/src/cli/render/contract-history.js +8 -18
- package/build/src/cli/render/contract.js +5 -10
- package/build/src/cli/render/kanshi-akuma.js +1 -1
- package/build/src/cli/render/kanshi.js +24 -30
- package/build/src/cli/render/receipt.d.ts +1 -2
- package/build/src/cli/render/receipt.js +1 -6
- package/build/src/cli/render/refusal.js +2 -1
- package/build/src/cli/render/task.js +2 -1
- package/build/src/core/facts/codec.js +0 -6
- package/build/src/core/facts/fold.js +0 -7
- package/build/src/core/facts/types.d.ts +1 -4
- package/build/src/core/verbs/deliver.js +2 -14
- package/build/src/library/bind.d.ts +20 -20
- package/build/src/library/bind.js +2 -1
- package/build/src/library/contract-composition.d.ts +5 -2
- package/build/src/library/contract-composition.js +5 -3
- package/build/src/library/contract-creation.d.ts +7 -1
- package/build/src/library/contract-creation.js +19 -9
- package/build/src/library/contract-execution.d.ts +1 -0
- package/build/src/library/contract-execution.js +12 -4
- package/build/src/library/contract-settings.d.ts +2 -2
- package/build/src/library/contract-settings.js +21 -12
- package/build/src/library/outcome.d.ts +476 -652
- package/build/src/library/refusal.d.ts +2 -2
- package/build/src/protocol/bind.d.ts +1 -1
- package/build/src/protocol/deliver.js +1 -1
- package/build/src/protocol/execution-observation.d.ts +0 -8
- package/build/src/protocol/intent.js +2 -2
- package/build/src/protocol/operations.d.ts +1 -1
- package/build/src/protocol/read/status.d.ts +1 -1
- package/build/src/protocol/read/status.js +0 -2
- package/build/src/runtime/proc/windows-launch.exe +0 -0
- package/build/src/world.d.ts +1 -0
- package/build/src/world.js +14 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -52,7 +52,7 @@ The CLI parses a predicate; the evaluator never sees a raw shell string.
|
|
|
52
52
|
Query reads only Task facts. No Contract. No Akuma.
|
|
53
53
|
|
|
54
54
|
## Verification
|
|
55
|
-
```bash
|
|
55
|
+
```bash timeout=2m
|
|
56
56
|
npm test
|
|
57
57
|
```
|
|
58
58
|
```
|
|
@@ -60,60 +60,60 @@ npm test
|
|
|
60
60
|
## What a deal looks like
|
|
61
61
|
|
|
62
62
|
```bash
|
|
63
|
-
keiyaku bind - <
|
|
64
|
-
keiyaku call worker -
|
|
65
|
-
keiyaku deliver
|
|
66
|
-
keiyaku review --satisfied
|
|
63
|
+
keiyaku bind - < contract.md # terms written; an isolated worktree appears
|
|
64
|
+
keiyaku call worker - # a worker goes in
|
|
65
|
+
keiyaku deliver <contract> # delivered; Verification runs; gates judge
|
|
66
|
+
keiyaku review <contract> --satisfied --summary - # attested; main moves with a commit receipt
|
|
67
67
|
```
|
|
68
68
|
|
|
69
69
|
## What the journal records
|
|
70
70
|
|
|
71
|
-
One real deal,
|
|
71
|
+
One real deal, read back with `keiyaku history`:
|
|
72
72
|
|
|
73
73
|
```text
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
74
|
+
history kei/ship-typed-task-query-b186 · accepted · 9 entries
|
|
75
|
+
|
|
76
|
+
2026-10-04
|
|
77
|
+
|
|
78
|
+
16:53 bound to targetless @ 3c5d8c0 · gate review
|
|
79
|
+
16:53 delivered 210eeed · ✓ verification · 10 lines
|
|
80
|
+
16:53 × review · 6 lines
|
|
81
|
+
16:53 delivered aebef87 · ✓ verification · 10 lines
|
|
82
|
+
16:53 ✓ review · 6 lines
|
|
83
|
+
16:53 accepted
|
|
84
84
|
```
|
|
85
85
|
|
|
86
|
-
The review said no
|
|
87
|
-
|
|
86
|
+
The review said no, and the deal did not land. The verdict and its reason
|
|
87
|
+
stay on the journal as bytes — `keiyaku history --json` reads them:
|
|
88
|
+
|
|
89
|
+
```json
|
|
90
|
+
{
|
|
91
|
+
"kind": "attestation",
|
|
92
|
+
"contract": "kei/ship-typed-task-query-b186",
|
|
93
|
+
"data": {
|
|
94
|
+
"gate": "reviewed",
|
|
95
|
+
"verdict": "unsatisfied",
|
|
96
|
+
"summary": "Query evaluator still reaches for Contract facts; reads are not Task-owned."
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
```
|
|
88
100
|
|
|
89
101
|
## The board
|
|
90
102
|
|
|
91
|
-
`keiyaku status` is the one screen.
|
|
103
|
+
`keiyaku status` is the one screen. Two Contracts and two Tasks in flight:
|
|
92
104
|
|
|
93
105
|
```text
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
● aku/expert-akuma/a7aafc9e running
|
|
106
|
-
alias @process-custody-lead
|
|
107
|
-
keiyaku kei/make-process-custody-capability-honest (active)
|
|
108
|
-
○ aku/design-akuma/cc53ef08 asleep
|
|
109
|
-
alias @timeline-design
|
|
110
|
-
! aku/grok/95d90b7d stranded
|
|
111
|
-
alias @acp-provider-impl
|
|
112
|
-
|
|
113
|
-
task 8 · 5 ready · 2 held
|
|
114
|
-
● task/align-task-cli-truth-promises-for-ready-compose in_progress
|
|
115
|
-
Align Task CLI truth promises for ready compose and world absence
|
|
116
|
-
P0 · keiyaku kei/align-task-cli-truth-promises (active)
|
|
106
|
+
CONTRACTS // recent
|
|
107
|
+
|
|
108
|
+
⧗ kei/ship-typed-task-query-c898 · 1s · Ship typed Task query
|
|
109
|
+
[✓] delivery [ ] review
|
|
110
|
+
⧗ kei/tighten-receipt-vocabulary-4a70 · 18s · Tighten receipt vocabulary
|
|
111
|
+
[ ] delivery [ ] review
|
|
112
|
+
|
|
113
|
+
TASKS // recent
|
|
114
|
+
|
|
115
|
+
○ task/regenerate-readme-from-real-010b · ready · P1 · Regenerate README from real output
|
|
116
|
+
○ task/align-task-cli-truth-promises-3af5 · ready · P0 · Align Task CLI truth promises
|
|
117
117
|
```
|
|
118
118
|
|
|
119
119
|
Marks accelerate scanning; the words carry the state.
|
|
@@ -122,9 +122,8 @@ Marks accelerate scanning; the words carry the state.
|
|
|
122
122
|
|
|
123
123
|
```markdown
|
|
124
124
|
---
|
|
125
|
-
provider:
|
|
126
|
-
model:
|
|
127
|
-
access: write
|
|
125
|
+
provider: pi
|
|
126
|
+
model: kimi-coding/k3-256k
|
|
128
127
|
description: Repository implementation agent
|
|
129
128
|
---
|
|
130
129
|
Make scoped changes and run relevant tests.
|
|
@@ -132,6 +131,55 @@ Make scoped changes and run relevant tests.
|
|
|
132
131
|
|
|
133
132
|
One Markdown file, one worker. `keiyaku call worker` summons it.
|
|
134
133
|
|
|
134
|
+
## Configuration
|
|
135
|
+
|
|
136
|
+
Settings are JSON at two addresses: `~/.keiyaku/settings.json` for the user,
|
|
137
|
+
`.keiyaku/settings.json` in the project. A project record
|
|
138
|
+
wholly shadows the same-name user record. There is no write command; edit the
|
|
139
|
+
files directly and inspect the merged, provenance-annotated view with
|
|
140
|
+
`keiyaku settings`.
|
|
141
|
+
|
|
142
|
+
Named gate groups provide reusable gate sets. Bind and amend select them with
|
|
143
|
+
`--gates`; the `default` group applies when binding with `--gates` omitted.
|
|
144
|
+
Amending without `--gates` keeps the existing gates:
|
|
145
|
+
|
|
146
|
+
```json
|
|
147
|
+
{
|
|
148
|
+
"gates": {
|
|
149
|
+
"default": { "kind": "bundle", "gates": ["reviewed"] },
|
|
150
|
+
"strict": { "kind": "bundle", "gates": ["reviewed", "verified"] }
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Selections accept the built-in names `reviewed` and `verified`, or configured
|
|
156
|
+
group names. Unknown names are rejected with the known names listed. Custom gates
|
|
157
|
+
must be declared inside a configured group; they stay unsatisfied until a producer
|
|
158
|
+
attests them.
|
|
159
|
+
|
|
160
|
+
Worktree hooks run commands when a Contract's managed worktree is created or
|
|
161
|
+
destroyed — dependency installs are the usual suspect:
|
|
162
|
+
|
|
163
|
+
```json
|
|
164
|
+
{
|
|
165
|
+
"worktree": {
|
|
166
|
+
"create": [
|
|
167
|
+
{ "name": "install", "argv": ["npm", "ci", "--ignore-scripts", "--prefer-offline"], "timeoutMs": 300000 }
|
|
168
|
+
],
|
|
169
|
+
"destroy": [
|
|
170
|
+
{ "name": "teardown", "argv": ["docker", "compose", "down", "-v"], "timeoutMs": 60000 }
|
|
171
|
+
]
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Hooks run as one ordered phase inside the worktree and must be replay-safe: a
|
|
177
|
+
retry reruns the phase from its beginning. Create hooks prepare the worktree;
|
|
178
|
+
destroy hooks release external resources the worktree deletion alone cannot.
|
|
179
|
+
A failing hook retains the worktree
|
|
180
|
+
and reports lag; it never abandons the Contract. `keiyaku settings --help`
|
|
181
|
+
lists every recognized namespace.
|
|
182
|
+
|
|
135
183
|
## Install
|
|
136
184
|
|
|
137
185
|
```bash
|
|
@@ -183,9 +183,10 @@ keiyaku -C <repo> bind --gates "" - < CONTRACT.md
|
|
|
183
183
|
keiyaku -C <repo> bind --gates reviewed - < CONTRACT.md
|
|
184
184
|
```
|
|
185
185
|
|
|
186
|
-
Omitting `--gates` uses `gates.default`, or `reviewed` when no default
|
|
186
|
+
Omitting `--gates` uses `gates.default`, or `reviewed` when no default group
|
|
187
187
|
exists. `--gates ""` selects no gates. Gates are named acceptance obligations,
|
|
188
|
-
not work assignments.
|
|
188
|
+
not work assignments. Named gate groups and worktree hooks live in Settings;
|
|
189
|
+
see the README Configuration section or `keiyaku settings --help`.
|
|
189
190
|
|
|
190
191
|
## 3. After Binding
|
|
191
192
|
|
|
@@ -164,10 +164,11 @@ function translateMessage(event, state) {
|
|
|
164
164
|
state.assistantSeen = true;
|
|
165
165
|
state.answer = text;
|
|
166
166
|
}
|
|
167
|
-
//
|
|
167
|
+
// Some providers emit a blank text block alongside every tool-use message:
|
|
168
|
+
// empty from Gemini, whitespace from openai-responses-style proxies. It is
|
|
168
169
|
// transport scaffolding, not narration; an ordinary terminal empty answer
|
|
169
170
|
// remains public evidence.
|
|
170
|
-
if (hasTextContent(message.content) && !(message.stopReason === "toolUse" && text.length === 0)) {
|
|
171
|
+
if (hasTextContent(message.content) && !(message.stopReason === "toolUse" && text.trim().length === 0)) {
|
|
171
172
|
translated.push({ type: "assistant", text });
|
|
172
173
|
}
|
|
173
174
|
return translated;
|
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { createAgentSessionFromServices, createAgentSessionServices, createBashToolDefinition, getAgentDir, SessionManager } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import type { ProviderExecution } from "../../provider-recipe.js";
|
|
3
3
|
import { type ProviderAdapter } from "../../provider.js";
|
|
4
4
|
export type PiSdk = Readonly<{
|
|
5
|
-
|
|
5
|
+
createAgentSessionFromServices: typeof createAgentSessionFromServices;
|
|
6
|
+
createAgentSessionServices: typeof createAgentSessionServices;
|
|
6
7
|
createBashToolDefinition: typeof createBashToolDefinition;
|
|
7
|
-
DefaultResourceLoader: typeof DefaultResourceLoader;
|
|
8
8
|
getAgentDir: typeof getAgentDir;
|
|
9
|
-
ModelRuntime: typeof ModelRuntime;
|
|
10
9
|
SessionManager: typeof SessionManager;
|
|
11
10
|
}>;
|
|
12
11
|
export declare function createPiProvider(execution?: ProviderExecution, load?: () => Promise<PiSdk>): ProviderAdapter;
|
|
@@ -21,26 +21,14 @@ function admitPiOptions(options) {
|
|
|
21
21
|
options: Object.freeze({ ...options }),
|
|
22
22
|
};
|
|
23
23
|
}
|
|
24
|
-
|
|
24
|
+
function piCreateOptions(sdk, input, services) {
|
|
25
25
|
let model;
|
|
26
|
-
let modelRuntime;
|
|
27
26
|
if (input.options.model !== undefined) {
|
|
28
27
|
const slash = input.options.model.indexOf("/");
|
|
29
|
-
|
|
30
|
-
model = modelRuntime.getModel(input.options.model.slice(0, slash), input.options.model.slice(slash + 1));
|
|
28
|
+
model = services.modelRuntime.getModel(input.options.model.slice(0, slash), input.options.model.slice(slash + 1));
|
|
31
29
|
if (model === undefined)
|
|
32
30
|
throw new Error(`Pi model '${input.options.model}' is unavailable`);
|
|
33
31
|
}
|
|
34
|
-
const resourceLoader = input.options.systemPrompt === undefined
|
|
35
|
-
? undefined
|
|
36
|
-
: new sdk.DefaultResourceLoader({
|
|
37
|
-
cwd: input.cwd,
|
|
38
|
-
agentDir: sdk.getAgentDir(),
|
|
39
|
-
...(input.options.systemPromptMode === "append"
|
|
40
|
-
? { appendSystemPromptOverride: (base) => [...base, input.options.systemPrompt] }
|
|
41
|
-
: { systemPromptOverride: () => input.options.systemPrompt }),
|
|
42
|
-
});
|
|
43
|
-
await resourceLoader?.reload();
|
|
44
32
|
const sessionManager = input.session.kind === "fresh"
|
|
45
33
|
? sdk.SessionManager.create(input.cwd)
|
|
46
34
|
: "sessionFile" in input.session.coordinate
|
|
@@ -59,22 +47,39 @@ async function piCreateOptions(sdk, input) {
|
|
|
59
47
|
}),
|
|
60
48
|
];
|
|
61
49
|
return {
|
|
62
|
-
|
|
50
|
+
services,
|
|
63
51
|
sessionManager,
|
|
64
|
-
...(model === undefined
|
|
65
|
-
...(resourceLoader === undefined ? {} : { resourceLoader }),
|
|
52
|
+
...(model === undefined ? {} : { model }),
|
|
66
53
|
...(input.options.effort === undefined ? {} : { thinkingLevel: input.options.effort }),
|
|
67
54
|
...(customTools === undefined ? {} : { customTools }),
|
|
68
55
|
};
|
|
69
56
|
}
|
|
70
57
|
async function createPiSession(sdk, execution, input) {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
58
|
+
if (execution.config !== undefined) {
|
|
59
|
+
throw new TypeError("Pi provider config cannot be consumed by native CreateAgentSessionOptions");
|
|
60
|
+
}
|
|
61
|
+
const services = await sdk.createAgentSessionServices({
|
|
62
|
+
cwd: input.cwd,
|
|
63
|
+
agentDir: sdk.getAgentDir(),
|
|
64
|
+
modelRuntimeSignal: input.signal,
|
|
65
|
+
...(input.options.systemPrompt === undefined
|
|
66
|
+
? {}
|
|
67
|
+
: {
|
|
68
|
+
resourceLoaderOptions: input.options.systemPromptMode === "append"
|
|
69
|
+
? { appendSystemPromptOverride: (base) => [...base, input.options.systemPrompt] }
|
|
70
|
+
: { systemPromptOverride: () => input.options.systemPrompt },
|
|
71
|
+
}),
|
|
76
72
|
});
|
|
77
|
-
|
|
73
|
+
try {
|
|
74
|
+
input.signal?.throwIfAborted();
|
|
75
|
+
return await sdk.createAgentSessionFromServices(piCreateOptions(sdk, input, services));
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
// Before a session owns the extension runtime, failed selection/setup must
|
|
79
|
+
// retire registrations and event-bus subscriptions loaded by the services.
|
|
80
|
+
services.resourceLoader.getExtensions().runtime.invalidate();
|
|
81
|
+
throw error;
|
|
82
|
+
}
|
|
78
83
|
}
|
|
79
84
|
async function runPiPrompt(native, input, events, state, settle) {
|
|
80
85
|
try {
|
|
@@ -273,11 +278,10 @@ async function forkPi(sdk, input) {
|
|
|
273
278
|
async function loadPiSdk() {
|
|
274
279
|
const sdk = await import("@earendil-works/pi-coding-agent");
|
|
275
280
|
return {
|
|
276
|
-
|
|
281
|
+
createAgentSessionFromServices: sdk.createAgentSessionFromServices,
|
|
282
|
+
createAgentSessionServices: sdk.createAgentSessionServices,
|
|
277
283
|
createBashToolDefinition: sdk.createBashToolDefinition,
|
|
278
|
-
DefaultResourceLoader: sdk.DefaultResourceLoader,
|
|
279
284
|
getAgentDir: sdk.getAgentDir,
|
|
280
|
-
ModelRuntime: sdk.ModelRuntime,
|
|
281
285
|
SessionManager: sdk.SessionManager,
|
|
282
286
|
};
|
|
283
287
|
}
|
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
import type { ArcData } from "../core/facts/types.js";
|
|
2
|
-
import type { ContractBody } from "./types.js";
|
|
2
|
+
import type { ContractBody, DecodedContractDocument } from "./types.js";
|
|
3
3
|
export declare function renderContractBody(body: ContractBody, currentArc?: ArcData): string;
|
|
4
|
+
/**
|
|
5
|
+
* Binding persists the canonical rendering of decoded terms, never the caller's
|
|
6
|
+
* literal byte stream: layout-only authorship is normalized at admission so it
|
|
7
|
+
* never reappears as phantom changes in later amendment diffs.
|
|
8
|
+
*/
|
|
9
|
+
export declare function decodeCanonicalContractDocument(source: string, options?: Readonly<{
|
|
10
|
+
requireTimeout?: boolean;
|
|
11
|
+
}>): DecodedContractDocument;
|
|
4
12
|
export declare function renderAmendedContractBody(currentSource: string, body: ContractBody, changedSections: ReadonlySet<string>): string;
|
package/build/src/body/render.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { formatDuration } from "../duration.js";
|
|
2
|
+
import { decodeContractDocument } from "./decode.js";
|
|
2
3
|
import { parseToAST } from "../markdown/parse.js";
|
|
3
4
|
import { indexDocument, indexedHeadings, normalizeTitle, rawSlice } from "../markdown/query.js";
|
|
4
5
|
import { CONTRACT_SECTIONS } from "./shape.js";
|
|
@@ -54,6 +55,16 @@ export function renderContractBody(body, currentArc) {
|
|
|
54
55
|
.join("\n\n")
|
|
55
56
|
.concat("\n");
|
|
56
57
|
}
|
|
58
|
+
/**
|
|
59
|
+
* Binding persists the canonical rendering of decoded terms, never the caller's
|
|
60
|
+
* literal byte stream: layout-only authorship is normalized at admission so it
|
|
61
|
+
* never reappears as phantom changes in later amendment diffs.
|
|
62
|
+
*/
|
|
63
|
+
export function decodeCanonicalContractDocument(source, options = {}) {
|
|
64
|
+
const decoded = decodeContractDocument(source, options);
|
|
65
|
+
const rendered = renderContractBody(decoded);
|
|
66
|
+
return rendered === decoded.document.bytes ? decoded : decodeContractDocument(rendered, options);
|
|
67
|
+
}
|
|
57
68
|
function sections(document) {
|
|
58
69
|
return indexedHeadings(indexDocument(document), { level: 2 }).filter((node) => node.type === "section");
|
|
59
70
|
}
|
|
@@ -304,8 +304,10 @@ function parseAddressed(action, rawSelectors, flags, output, fail) {
|
|
|
304
304
|
return { command: action, akuma, at, output };
|
|
305
305
|
}
|
|
306
306
|
function parsePrompted(action, positionals, stdin, fail) {
|
|
307
|
-
if (positionals.length
|
|
308
|
-
fail(`${action}
|
|
307
|
+
if (positionals.length === 0)
|
|
308
|
+
fail(action === "call" ? "call requires an Akuma name" : `${action} requires an Akuma selector`);
|
|
309
|
+
if (positionals.length > 2)
|
|
310
|
+
fail(`${action} accepts at most ${action === "call" ? "a name" : "a selector"} and one prompt`);
|
|
309
311
|
const argument = positionals[1];
|
|
310
312
|
if (stdin && argument !== undefined)
|
|
311
313
|
fail(`${action} accepts either a prompt argument or stdin, not both`);
|
|
@@ -406,7 +408,14 @@ export function parseAkumaCommand(argv) {
|
|
|
406
408
|
: parseAsk(flags, parsed.subject, parsed.prompt, output, fail);
|
|
407
409
|
}
|
|
408
410
|
if (spec.arity === "one-or-more" ? positionals.length === 0 : positionals.length !== spec.arity) {
|
|
409
|
-
|
|
411
|
+
if (positionals.length === 0)
|
|
412
|
+
fail(spec.arity === "one-or-more"
|
|
413
|
+
? `${action} requires at least one Akuma selector`
|
|
414
|
+
: `${action} requires an Akuma selector`);
|
|
415
|
+
else
|
|
416
|
+
fail(spec.arity === 1
|
|
417
|
+
? `${action} accepts one Akuma selector`
|
|
418
|
+
: `${action} accepts exactly ${spec.arity} Akuma selectors`);
|
|
410
419
|
}
|
|
411
420
|
if (stdin)
|
|
412
421
|
fail(`${action} reads no stdin`);
|
|
@@ -9,6 +9,7 @@ import { executionChannel } from "../../akuma/requests.js";
|
|
|
9
9
|
import { settings } from "../../settings.js";
|
|
10
10
|
import { AkumaWorldScopeError } from "../../index.js";
|
|
11
11
|
import { akumasWithExecution } from "../../library/akumas.js";
|
|
12
|
+
import { World } from "../../world.js";
|
|
12
13
|
import { contractFromInput } from "../selectors.js";
|
|
13
14
|
import { ActivityDriver } from "../activity.js";
|
|
14
15
|
import { displayContext, resultContext, writeJson, writeStderr, writeStdout } from "../streams.js";
|
|
@@ -67,6 +68,11 @@ async function inputInitiator(input) {
|
|
|
67
68
|
catch { }
|
|
68
69
|
if (initiator === undefined)
|
|
69
70
|
return {};
|
|
71
|
+
// Plugins coordinate within an established World; a `call` may run where the
|
|
72
|
+
// marker does not exist yet, and activating there would only fail noisily.
|
|
73
|
+
// The initiator still travels with the request either way.
|
|
74
|
+
if (!(await World.established(input.path)))
|
|
75
|
+
return { initiator };
|
|
70
76
|
try {
|
|
71
77
|
await emitInitiatingPluginSignal({
|
|
72
78
|
world: input.path,
|
|
@@ -22,6 +22,9 @@ function optionalFlag(flags, name) {
|
|
|
22
22
|
function refuse(command, message) {
|
|
23
23
|
throw new CliUsageError(message, commandGuide(command, CONTRACT_COMMAND_SPECS[command].usage));
|
|
24
24
|
}
|
|
25
|
+
// The CLI restates the literal gate-word grammar its help prints; the corpus
|
|
26
|
+
// pin in cli-parse.test.ts keeps this shape honest against the owner's gateWord.
|
|
27
|
+
const GATE_NAME_PATTERN = /^[a-z][a-z0-9-]{0,63}$/u;
|
|
25
28
|
function parseGateNames(parts, command) {
|
|
26
29
|
const value = optionalFlag(parts.flags, "gates");
|
|
27
30
|
if (value === undefined)
|
|
@@ -32,6 +35,10 @@ function parseGateNames(parts, command) {
|
|
|
32
35
|
if (names.some((name) => name.length === 0)) {
|
|
33
36
|
refuse(command, '--gates requires comma-separated names or "" to clear gates');
|
|
34
37
|
}
|
|
38
|
+
const invalid = names.find((name) => !GATE_NAME_PATTERN.test(name));
|
|
39
|
+
if (invalid !== undefined) {
|
|
40
|
+
refuse(command, `--gates name must match ^[a-z][a-z0-9-]{0,63}$: ${invalid}`);
|
|
41
|
+
}
|
|
35
42
|
return names;
|
|
36
43
|
}
|
|
37
44
|
function parseBind(parts) {
|
|
@@ -106,7 +106,7 @@ export declare const CONTRACT_COMMAND_SPECS: {
|
|
|
106
106
|
readonly json: "boolean";
|
|
107
107
|
};
|
|
108
108
|
readonly usage: "ls task[/] [--limit <count>]\nls kei[/] [--limit <count>]\nls aku[/] [--limit <count>]\nls aku/<name>[/] [--limit <count>]\nls \"aku/<name>/*\" [--limit <count>]\nls \"aku/*/*\" [--limit <count>]";
|
|
109
|
-
readonly purpose: "List
|
|
109
|
+
readonly purpose: "List tasks (ls task), contracts (ls kei), or akumas (ls aku).";
|
|
110
110
|
readonly details: string;
|
|
111
111
|
};
|
|
112
112
|
readonly audit: {
|
|
@@ -15,11 +15,15 @@ export const CONTRACT_COMMAND_SPECS = {
|
|
|
15
15
|
usage: "bind [--task <task/...>] [--target <ref>] [--after <kei/...> ...] [--gates <name,...>] [--actor <actor>] - | bind --fork-of <kei/...> [--target <ref>] [--actor <actor>]",
|
|
16
16
|
purpose: "Create a Contract from stdin Markdown or copy an existing Contract as a starting point.",
|
|
17
17
|
details: [
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"
|
|
18
|
+
"Gates are acceptance obligations checked at placement: reviewed awaits a",
|
|
19
|
+
"satisfied review, verified awaits a passing Verification run.",
|
|
20
|
+
"",
|
|
21
|
+
"--gates <name,...> selects reviewed, verified, or named gate groups from Settings",
|
|
22
|
+
"(keiyaku settings). A group expands into its gates; unknown names are rejected",
|
|
23
|
+
"with the known names listed. Custom gates must be declared in a configured group.",
|
|
24
|
+
"Duplicates are removed in first-seen order. Names match ^[a-z][a-z0-9-]{0,63}$.",
|
|
21
25
|
"Omitting --gates selects gates.default, or reviewed when no default exists.",
|
|
22
|
-
'--gates "" explicitly binds without gates; --gates reviewed needs no
|
|
26
|
+
'--gates "" explicitly binds without gates; --gates reviewed needs no configuration.',
|
|
23
27
|
"",
|
|
24
28
|
"Binding appoints one managed worktree for the Contract and the receipt",
|
|
25
29
|
"reports its path; the holder and delegates work there. When appointment",
|
|
@@ -55,9 +59,14 @@ export const CONTRACT_COMMAND_SPECS = {
|
|
|
55
59
|
usage: "amend [<contract>|@<contract>] [--after <kei/...> ... | --clear-after] [--gates <name,...>] [--actor <actor>] [-]",
|
|
56
60
|
purpose: "Change a Contract's document, prerequisites, or acceptance gates.",
|
|
57
61
|
details: [
|
|
58
|
-
"
|
|
59
|
-
"
|
|
60
|
-
"
|
|
62
|
+
"Gates are acceptance obligations checked at placement: reviewed awaits a",
|
|
63
|
+
"satisfied review, verified awaits a passing Verification run.",
|
|
64
|
+
"",
|
|
65
|
+
"--gates <name,...> replaces gates using reviewed, verified, or named gate groups",
|
|
66
|
+
"from Settings (keiyaku settings). A group expands into its gates; unknown names",
|
|
67
|
+
"are rejected with the known names listed. Custom gates must be declared in a",
|
|
68
|
+
"configured group. Duplicates are removed in first-seen order.",
|
|
69
|
+
"Names match ^[a-z][a-z0-9-]{0,63}$.",
|
|
61
70
|
'Omitting --gates leaves gates unchanged; --gates "" clears them.',
|
|
62
71
|
"",
|
|
63
72
|
" ## Context|Objective|Design|Region|Criteria|Verification|<extension> (replace, or add new extension)",
|
|
@@ -88,6 +97,7 @@ export const CONTRACT_COMMAND_SPECS = {
|
|
|
88
97
|
"Delivery requests integration. If prerequisites and gates pass, it integrates now and accepts the Contract; otherwise the candidate remains waiting.",
|
|
89
98
|
"Only changed candidate work invalidates earlier review. If Verification did not finish, repeating delivery resumes that candidate; --overwrite replaces it.",
|
|
90
99
|
"Delivery never counts as an independent review verdict.",
|
|
100
|
+
"Content identity names the change's content hash, never a commit; it survives rebase and Git cannot look it up.",
|
|
91
101
|
"",
|
|
92
102
|
" --materialize-conflict Write the observed integration conflict into the worktree as an uncommitted merge.",
|
|
93
103
|
" With --include-dirty, current non-ignored changes are preserved first.",
|
|
@@ -147,8 +157,9 @@ export const CONTRACT_COMMAND_SPECS = {
|
|
|
147
157
|
stdin: "none",
|
|
148
158
|
flags: { limit: "value", json: "boolean" },
|
|
149
159
|
usage: 'ls task[/] [--limit <count>]\nls kei[/] [--limit <count>]\nls aku[/] [--limit <count>]\nls aku/<name>[/] [--limit <count>]\nls "aku/<name>/*" [--limit <count>]\nls "aku/*/*" [--limit <count>]',
|
|
150
|
-
purpose: "List
|
|
160
|
+
purpose: "List tasks (ls task), contracts (ls kei), or akumas (ls aku).",
|
|
151
161
|
details: [
|
|
162
|
+
"ls kei lists active Contracts; accepted and abandoned ones leave the list, and their record stays in history and Git.",
|
|
152
163
|
"ls aku/ lists the callable Akuma-name catalog; ls aku/<name> lists the living Akumas under one name.",
|
|
153
164
|
'ls "aku/<name>/*" is the same living set in glob form; ls "aku/*/*" lists every living Akuma.',
|
|
154
165
|
].join("\n"),
|
|
@@ -162,6 +173,7 @@ export const CONTRACT_COMMAND_SPECS = {
|
|
|
162
173
|
details: [
|
|
163
174
|
"--include-dirty checks all non-ignored worktree changes, staged or not.",
|
|
164
175
|
"--show-diff includes the proposed candidate diff when one can be prepared.",
|
|
176
|
+
"Content identity names the change's content hash, never a commit; it survives rebase and Git cannot look it up.",
|
|
165
177
|
"Progress appears on stderr; stdout contains one final result.",
|
|
166
178
|
].join("\n"),
|
|
167
179
|
},
|
|
@@ -210,7 +222,7 @@ export function renderContractHelp(command) {
|
|
|
210
222
|
"command; edit the files directly.",
|
|
211
223
|
"",
|
|
212
224
|
"Recognized settings:",
|
|
213
|
-
" gates gate
|
|
225
|
+
" gates named gate groups selected by bind and amend",
|
|
214
226
|
" worktree commands run when worktrees are created or destroyed",
|
|
215
227
|
" git.requireBranchesToBeUpToDate whether deliver and audit require up-to-date target branches",
|
|
216
228
|
" providers the available Akuma and how each is run",
|
|
@@ -148,7 +148,9 @@ async function contractLibrary(command, execution, configuration, runtime) {
|
|
|
148
148
|
return composeContractLibrary(execution, createKeiyakuHandle, {
|
|
149
149
|
...(configuration === undefined ? {} : { settings: configuration }),
|
|
150
150
|
...(actor === undefined ? {} : { actor }),
|
|
151
|
-
}
|
|
151
|
+
},
|
|
152
|
+
// The CLI owns this default: an omitted Markdown --target names the invocation branch.
|
|
153
|
+
{ kind: "current-branch" });
|
|
152
154
|
}
|
|
153
155
|
async function runBind(command, input, library) {
|
|
154
156
|
const { coordinates, runtime } = input;
|
|
@@ -204,14 +204,12 @@ export function renderTaskHelp(action) {
|
|
|
204
204
|
const spec = TASK_COMMAND_SPECS[action];
|
|
205
205
|
return `${spec.purpose}\n\n${renderTaskUsage(action)}${spec.details === undefined ? "" : `\n\n${spec.details}`}`;
|
|
206
206
|
}
|
|
207
|
+
const nameWidth = Math.max(...Object.keys(TASK_COMMAND_SPECS).map((name) => name.length));
|
|
207
208
|
return [
|
|
208
209
|
"usage keiyaku task <command> ...",
|
|
210
|
+
" keiyaku task <command> --help shows that command's complete usage",
|
|
209
211
|
"",
|
|
210
|
-
|
|
211
|
-
...Object.values(TASK_COMMAND_SPECS).flatMap((spec) => spec.usage
|
|
212
|
-
.split("\n")
|
|
213
|
-
.map((line) => ` ${line}`)
|
|
214
|
-
.concat(` ${spec.purpose}`)),
|
|
212
|
+
...Object.entries(TASK_COMMAND_SPECS).map(([name, spec]) => ` ${name.padEnd(nameWidth)} ${spec.purpose}`),
|
|
215
213
|
].join("\n");
|
|
216
214
|
}
|
|
217
215
|
function renderTaskUsage(action) {
|
|
@@ -387,8 +385,12 @@ function validateListSelector(selector, fail) {
|
|
|
387
385
|
}
|
|
388
386
|
function validateTaskScan(action, scanned, fail) {
|
|
389
387
|
const spec = TASK_COMMAND_SPECS[action], { positionals, flags, stdin } = scanned;
|
|
390
|
-
if (positionals.length < spec.arity[0]
|
|
391
|
-
fail(`task ${action}
|
|
388
|
+
if (positionals.length < spec.arity[0])
|
|
389
|
+
fail(spec.arity[1] === 1 ? `task ${action} requires a TaskId` : `task ${action} requires at least one TaskId`);
|
|
390
|
+
if (positionals.length > spec.arity[1])
|
|
391
|
+
fail(spec.arity[1] === 0
|
|
392
|
+
? `task ${action} accepts no positional arguments`
|
|
393
|
+
: `task ${action} accepts at most ${spec.arity[1]} positional argument${spec.arity[1] === 1 ? "" : "s"}`);
|
|
392
394
|
if (action === "add" && (stdin === "document") === (positionals.length === 1))
|
|
393
395
|
fail("task add requires either TITLE or '-' input");
|
|
394
396
|
if (action === "add" &&
|
package/build/src/cli/parse.js
CHANGED
|
@@ -294,6 +294,11 @@ export function parseArgv(argv) {
|
|
|
294
294
|
const help = helpCoordinate(invocation.commandArgv);
|
|
295
295
|
if (help !== null)
|
|
296
296
|
return { help };
|
|
297
|
+
// A bare invocation asks for orientation: the root help index, not a refusal.
|
|
298
|
+
if (invocation.commandArgv.length === 0)
|
|
299
|
+
return { help: { kind: "root" } };
|
|
300
|
+
if (invocation.commandArgv.length === 1 && invocation.commandArgv[0] === "task")
|
|
301
|
+
return { help: { kind: "task" } };
|
|
297
302
|
if (invocation.commandArgv.length === 1 && invocation.commandArgv[0] === "--version")
|
|
298
303
|
return { version: true };
|
|
299
304
|
const task = invocation.commandArgv[0] === "task" ? parseTaskCommand(invocation.commandArgv.slice(1)) : undefined;
|
|
@@ -14,6 +14,10 @@ export declare const DEFAULT_CONTEXT: TextRenderContext;
|
|
|
14
14
|
*/
|
|
15
15
|
export declare function frameRule(headLines: readonly string[]): string;
|
|
16
16
|
type StatusTimeline = AkumaObservation["status"]["timeline"];
|
|
17
|
+
type StatusTimelineEntry = StatusTimeline["entries"][number];
|
|
18
|
+
type RenderRow = ActivityRow | Extract<StatusTimelineEntry, {
|
|
19
|
+
kind: "row";
|
|
20
|
+
}>["row"];
|
|
17
21
|
type RenderedSnapshot = StatusTimeline;
|
|
18
22
|
type RenderedActivity = Readonly<{
|
|
19
23
|
snapshot: RenderedSnapshot;
|
|
@@ -56,15 +60,31 @@ export type ActivityStream = ((activity: RenderedActivity) => readonly string[])
|
|
|
56
60
|
seed: (activity: RenderedActivity, alreadyRenderedSequence?: number) => readonly string[];
|
|
57
61
|
/** Current live rows for the redrawable frame; never part of append-only output. */
|
|
58
62
|
frame: () => readonly string[];
|
|
59
|
-
/** Emit the
|
|
63
|
+
/** Emit the pending omission count and unsettled rows once before the command's conclusion. */
|
|
60
64
|
flush: () => readonly string[];
|
|
65
|
+
/** The same decisions undrawn, so concurrent streams can merge before rendering. */
|
|
66
|
+
emissions: Readonly<{
|
|
67
|
+
observe: (activity: RenderedActivity) => readonly StreamEmission[];
|
|
68
|
+
seed: (activity: RenderedActivity, alreadyRenderedSequence?: number) => readonly StreamEmission[];
|
|
69
|
+
flush: () => readonly StreamEmission[];
|
|
70
|
+
}>;
|
|
71
|
+
render: (emissions: readonly StreamEmission[]) => readonly string[];
|
|
72
|
+
}>;
|
|
73
|
+
/** One decided piece of append-only output: a settled row, or an untimed omission count. */
|
|
74
|
+
export type StreamEmission = Readonly<{
|
|
75
|
+
kind: "gap";
|
|
76
|
+
count: number;
|
|
77
|
+
}> | Readonly<{
|
|
78
|
+
kind: "row";
|
|
79
|
+
row: RenderRow;
|
|
80
|
+
inFlightSay?: boolean;
|
|
81
|
+
unsettled?: boolean;
|
|
61
82
|
}>;
|
|
62
83
|
/**
|
|
63
|
-
* Append-only live view over one command's successive settled snapshots.
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
* in place. Other narrative rows wait behind undecided tail tools.
|
|
84
|
+
* Append-only live view over one command's successive settled snapshots.
|
|
85
|
+
* After each say or Tell, the first three tools print as they settle; later
|
|
86
|
+
* tools until the next checkpoint fold into one omission marker. Failed tools
|
|
87
|
+
* always print.
|
|
68
88
|
*/
|
|
69
89
|
export declare function activityStream(context: TextRenderContext, layout?: RowLayout): ActivityStream;
|
|
70
90
|
/** What a wait conclusion renders over: its observed and unobserved members. */
|