@astrosheep/keiyaku 2.9.7 → 2.9.8
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/agents/harness/event-persistence.js +7 -5
- package/build/agents/harness/events.js +3 -2
- package/build/agents/providers/codex-app-server/adapter.js +6 -1
- package/build/agents/providers/codex-app-server/session.js +8 -7
- package/build/agents/selector.js +12 -1
- package/build/cli/commands/akuma/view/handler.js +3 -11
- package/build/cli/commands/contract/amend/handler.js +1 -1
- package/build/cli/commands/contract/amend/meta.js +4 -4
- package/build/cli/commands/projection/status/handler.js +5 -4
- package/build/cli/commands/projection/status/meta.js +2 -2
- package/build/cli/commands/task/add/meta.js +9 -1
- package/build/cli/commands/task/shared.js +2 -1
- package/build/cli/completion.js +8 -0
- package/build/cli/render/kanshi.js +2 -2
- package/build/cli/render/path-prefix-compaction.js +119 -76
- package/build/cli/render/projection-activity.js +10 -1
- package/build/cli/render/shared.js +8 -7
- package/build/cli/render/status.js +8 -5
- package/build/cli/render/wait.js +1 -1
- package/build/config/settings/disease.js +4 -4
- package/build/config/settings/loader.js +44 -21
- package/build/core/addressing.js +40 -9
- package/build/core/amend.js +21 -5
- package/build/core/call/context.js +19 -3
- package/build/core/call/execution.js +43 -20
- package/build/core/ledger-batch.js +194 -0
- package/build/core/projection/generation/database.js +22 -0
- package/build/core/projection/generation/projection-generation-execution.js +45 -37
- package/build/core/projection/generation/projection-generation-launcher.js +148 -20
- package/build/core/projection/generation/projection-generation-process.js +3 -1
- package/build/core/projection/generation/projection-generation-runner.js +76 -40
- package/build/core/projection/generation/projection-generation-runtime.js +82 -19
- package/build/core/projection/generation/store.js +17 -1
- package/build/core/projection/generation/transitions.js +89 -12
- package/build/core/projection/index.js +3 -3
- package/build/core/projection/projection-kill.js +22 -10
- package/build/core/projection/projection-runner-lock.js +177 -37
- package/build/core/projection/projection-status.js +143 -55
- package/build/core/projection/projection-wake.js +171 -72
- package/build/core/status/board.js +42 -4
- package/build/core/status/drift.js +21 -5
- package/build/core/status/ledger-batch.js +1 -158
- package/build/core/task/task-git-runtime.js +8 -10
- package/build/core/task/task-git-store.js +5 -3
- package/build/core/worktree-path.js +39 -25
- package/build/flow-error.js +1 -1
- package/build/generated/version.js +2 -2
- package/build/git/refs.js +47 -1
- package/package.json +1 -1
- package/skills/keiyaku-akuma/SKILL.md +18 -0
- package/skills/keiyaku-workflow/SKILL.md +68 -13
|
@@ -3,6 +3,7 @@ import * as fs from "node:fs/promises";
|
|
|
3
3
|
import { FlowError } from "../flow-error.js";
|
|
4
4
|
import { createGit, wrapGitError } from "../git/core.js";
|
|
5
5
|
import { readLedger } from "./ledger.js";
|
|
6
|
+
import { readLedgerSnapshot } from "./ledger-batch.js";
|
|
6
7
|
import { deriveContractState, isTerminalState } from "./status/lifecycle.js";
|
|
7
8
|
import { normalizeFilesystemCoordinate } from "../fs/path-coordinate.js";
|
|
8
9
|
import { gitCommonRootPath, resolveGitCommonRoot } from "../git/path-coordinate.js";
|
|
@@ -73,7 +74,10 @@ export async function stableRepoRoot(cwd) {
|
|
|
73
74
|
}
|
|
74
75
|
}
|
|
75
76
|
export async function contractWorktreePathFromBind(cwd, contractId, bind) {
|
|
76
|
-
return
|
|
77
|
+
return contractWorktreePathFromStableRoot(await stableRepoRoot(cwd), contractId, bind);
|
|
78
|
+
}
|
|
79
|
+
function contractWorktreePathFromStableRoot(stableRoot, contractId, bind) {
|
|
80
|
+
return path.join(stableRoot, ".keiyaku", "wt", bind?.data.place ?? contractId);
|
|
77
81
|
}
|
|
78
82
|
export async function contractWorktreePath(cwd, contractId) {
|
|
79
83
|
const ledger = await readLedger(cwd, contractId);
|
|
@@ -110,7 +114,7 @@ export async function findRegisteredContractWorktreePath(cwd, contractId) {
|
|
|
110
114
|
return undefined;
|
|
111
115
|
let expected;
|
|
112
116
|
try {
|
|
113
|
-
expected = await fs.realpath(
|
|
117
|
+
expected = await fs.realpath(contractWorktreePathFromStableRoot(stableRoot, contractId, bind));
|
|
114
118
|
}
|
|
115
119
|
catch {
|
|
116
120
|
return undefined;
|
|
@@ -139,48 +143,58 @@ export async function resolveContractDeliveryCwd(cwd, contractId) {
|
|
|
139
143
|
* terminal state is optional so residual worktrees after cleanup still bind.
|
|
140
144
|
*/
|
|
141
145
|
async function contractIdForEngineOwnedWorktree(cwd, options) {
|
|
142
|
-
const stableRoot = await stableRepoRoot(cwd);
|
|
146
|
+
const stableRoot = options.stableRoot ?? await stableRepoRoot(cwd);
|
|
143
147
|
const canonicalCwd = await fs.realpath(cwd);
|
|
148
|
+
const candidates = [];
|
|
144
149
|
for (const worktree of await listWorktrees(stableRoot)) {
|
|
145
150
|
const branchPrefix = "refs/heads/keiyaku/";
|
|
146
151
|
if (!worktree.branch?.startsWith(branchPrefix))
|
|
147
152
|
continue;
|
|
148
|
-
const contractId = worktree.branch.slice(branchPrefix.length);
|
|
149
|
-
const ledger = await readLedger(stableRoot, contractId);
|
|
150
|
-
if (!ledger)
|
|
151
|
-
continue;
|
|
152
|
-
if (options.requireActive && isTerminalState(deriveContractState(ledger.entries)))
|
|
153
|
-
continue;
|
|
154
|
-
const bind = findBindEntry(ledger.entries);
|
|
155
|
-
if (!bind || bind.data.workspace !== "worktree")
|
|
156
|
-
continue;
|
|
157
153
|
let canonicalWorktree;
|
|
158
|
-
let expectedWorktree;
|
|
159
154
|
try {
|
|
160
155
|
canonicalWorktree = await fs.realpath(worktree.path);
|
|
161
|
-
expectedWorktree = await fs.realpath(await contractWorktreePathFromBind(stableRoot, contractId, bind));
|
|
162
156
|
}
|
|
163
157
|
catch {
|
|
164
158
|
continue;
|
|
165
159
|
}
|
|
166
|
-
if (canonicalWorktree
|
|
160
|
+
if (!worktreeContainsCoordinate(canonicalWorktree, canonicalCwd))
|
|
167
161
|
continue;
|
|
168
|
-
|
|
169
|
-
|
|
162
|
+
candidates.push({
|
|
163
|
+
contractId: worktree.branch.slice(branchPrefix.length),
|
|
164
|
+
path: canonicalWorktree,
|
|
165
|
+
});
|
|
170
166
|
}
|
|
171
|
-
|
|
167
|
+
const candidate = candidates.sort((left, right) => right.path.length - left.path.length)[0];
|
|
168
|
+
if (!candidate)
|
|
169
|
+
return undefined;
|
|
170
|
+
const ledger = await readLedgerSnapshot(stableRoot, candidate.contractId);
|
|
171
|
+
if (!ledger)
|
|
172
|
+
return undefined;
|
|
173
|
+
if (options.requireActive && isTerminalState(deriveContractState(ledger.entries)))
|
|
174
|
+
return undefined;
|
|
175
|
+
const bind = findBindEntry(ledger.entries);
|
|
176
|
+
if (!bind || bind.data.workspace !== "worktree")
|
|
177
|
+
return undefined;
|
|
178
|
+
let expectedWorktree;
|
|
179
|
+
try {
|
|
180
|
+
expectedWorktree = await fs.realpath(contractWorktreePathFromStableRoot(stableRoot, candidate.contractId, bind));
|
|
181
|
+
}
|
|
182
|
+
catch {
|
|
183
|
+
return undefined;
|
|
184
|
+
}
|
|
185
|
+
return candidate.path === expectedWorktree ? candidate.contractId : undefined;
|
|
172
186
|
}
|
|
173
187
|
/**
|
|
174
188
|
* Resolve the contract bound by an engine-owned worktree containing cwd,
|
|
175
|
-
* including when the ledger is already terminal. Task CLI
|
|
176
|
-
*
|
|
189
|
+
* including when the ledger is already terminal. Task CLI context uses this
|
|
190
|
+
* residual binding; call uses the active-only sibling below.
|
|
177
191
|
*/
|
|
178
|
-
export async function contractBoundByEngineWorktree(cwd) {
|
|
179
|
-
return contractIdForEngineOwnedWorktree(cwd, { requireActive: false });
|
|
192
|
+
export async function contractBoundByEngineWorktree(cwd, stableRoot) {
|
|
193
|
+
return contractIdForEngineOwnedWorktree(cwd, { requireActive: false, stableRoot });
|
|
180
194
|
}
|
|
181
|
-
/** Existing-contract verbs
|
|
182
|
-
export async function activeContractBoundByEngineWorktree(cwd) {
|
|
183
|
-
return contractIdForEngineOwnedWorktree(cwd, { requireActive: true });
|
|
195
|
+
/** Existing-contract verbs and no-selector call attachment use only this verified active binding. */
|
|
196
|
+
export async function activeContractBoundByEngineWorktree(cwd, stableRoot) {
|
|
197
|
+
return contractIdForEngineOwnedWorktree(cwd, { requireActive: true, stableRoot });
|
|
184
198
|
}
|
|
185
199
|
export async function cleanupContractWorkspace(cwd, contractId) {
|
|
186
200
|
const stableRoot = await stableRepoRoot(cwd);
|
package/build/flow-error.js
CHANGED
|
@@ -20,7 +20,7 @@ export function renderTemplate(template, values) {
|
|
|
20
20
|
}
|
|
21
21
|
function formatInvalidSettingsDiseaseHints(diseases) {
|
|
22
22
|
return diseases.map((disease) => {
|
|
23
|
-
const where = disease.knob ? `${disease.
|
|
23
|
+
const where = disease.knob ? `${disease.coordinate}:${disease.knob}` : `${disease.coordinate}:(root)`;
|
|
24
24
|
return `${where}: ${disease.reason}`;
|
|
25
25
|
});
|
|
26
26
|
}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
// Generated into build output by scripts/build.mjs
|
|
2
|
-
export const VERSION = "2.9.
|
|
3
|
-
export const GIT_HASH = "
|
|
2
|
+
export const VERSION = "2.9.8";
|
|
3
|
+
export const GIT_HASH = "c9cfce1";
|
package/build/git/refs.js
CHANGED
|
@@ -1,5 +1,51 @@
|
|
|
1
|
-
import { createGit, errorContainsAnyPattern, MISSING_HEAD_PATTERNS, wrapGitError } from "./core.js";
|
|
1
|
+
import { createGit, errorContainsAnyPattern, MISSING_HEAD_PATTERNS, runGitProcess, wrapGitError, } from "./core.js";
|
|
2
2
|
const ZERO_OBJECT_ID = "0000000000000000000000000000000000000000";
|
|
3
|
+
/** Normalize a configured branch to the sole local-branch ref spelling. */
|
|
4
|
+
export function normalizeLocalBranchRef(branch) {
|
|
5
|
+
return branch.startsWith("refs/heads/") ? branch : `refs/heads/${branch}`;
|
|
6
|
+
}
|
|
7
|
+
function isMissingExactRef(result) {
|
|
8
|
+
return result.status === 1 && result.stdout === "";
|
|
9
|
+
}
|
|
10
|
+
function parseShowRefRows(output) {
|
|
11
|
+
const lines = output.split(/\r?\n/);
|
|
12
|
+
if (lines.at(-1) === "")
|
|
13
|
+
lines.pop();
|
|
14
|
+
if (lines.length === 0 || lines.some((line) => line.length === 0))
|
|
15
|
+
return null;
|
|
16
|
+
const rows = [];
|
|
17
|
+
for (const line of lines) {
|
|
18
|
+
const match = /^(?<objectId>[0-9a-fA-F]{40}|[0-9a-fA-F]{64}) (?<ref>refs\/\S+)$/.exec(line);
|
|
19
|
+
if (!match?.groups)
|
|
20
|
+
return null;
|
|
21
|
+
rows.push({ objectId: match.groups.objectId, ref: match.groups.ref });
|
|
22
|
+
}
|
|
23
|
+
return rows;
|
|
24
|
+
}
|
|
25
|
+
function throwGitProcessFailure(commandLabel, result, cwd) {
|
|
26
|
+
const failure = result.error ?? Object.assign(new Error(result.stderr || `git ${commandLabel} exited ${result.status}`), {
|
|
27
|
+
stderr: result.stderr,
|
|
28
|
+
stdout: result.stdout,
|
|
29
|
+
});
|
|
30
|
+
throw wrapGitError(commandLabel, failure, cwd);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Resolve only a local branch through the argv-based Git boundary. A configured
|
|
34
|
+
* short name is never allowed to fall through Git's revision search rules.
|
|
35
|
+
*/
|
|
36
|
+
export async function resolveLocalBranchHead(cwd, branch, run = runGitProcess) {
|
|
37
|
+
const ref = normalizeLocalBranchRef(branch);
|
|
38
|
+
const result = await run(cwd, ["show-ref", ref]);
|
|
39
|
+
if (isMissingExactRef(result))
|
|
40
|
+
return null;
|
|
41
|
+
if (result.status === 0) {
|
|
42
|
+
const exactRows = parseShowRefRows(result.stdout)?.filter((row) => row.ref === ref);
|
|
43
|
+
if (exactRows?.length === 1) {
|
|
44
|
+
return { ref, head: exactRows[0].objectId };
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return throwGitProcessFailure(`show-ref ${ref}`, result, cwd);
|
|
48
|
+
}
|
|
3
49
|
async function resolveRefOrNull(cwd, ref) {
|
|
4
50
|
const git = createGit(cwd);
|
|
5
51
|
try {
|
package/package.json
CHANGED
|
@@ -19,6 +19,24 @@ Rule of thumb: continue the same conversation with its projection id. Use an
|
|
|
19
19
|
artifact id with `revive` only when deliberately starting a new projection;
|
|
20
20
|
failed/dead state alone is not a reason to revive.
|
|
21
21
|
|
|
22
|
+
## Projection aliases
|
|
23
|
+
|
|
24
|
+
When a lane will be supervised repeatedly, assign a short stable alias at call
|
|
25
|
+
time and use it as the operator coordination handle:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
keiyaku call worker-akuma --alias release-lane --detach "implement the brief"
|
|
29
|
+
keiyaku wait release-lane --timeout 10m
|
|
30
|
+
keiyaku tell release-lane "run the focused checks before handoff"
|
|
31
|
+
keiyaku kill release-lane
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Aliases are per selected projection ledger, not projection identity. Keep the
|
|
35
|
+
full `name/8hex` ID in durable records and exact recovery. Reusing an alias
|
|
36
|
+
word atomically moves only that per-ledger ref to the new projection; it does
|
|
37
|
+
not stop, mutate, or delete the prior projection. Its old full ID remains
|
|
38
|
+
addressable. `revive` starts a new projection and does not inherit an alias.
|
|
39
|
+
|
|
22
40
|
## Common usage
|
|
23
41
|
|
|
24
42
|
```bash
|
|
@@ -13,7 +13,8 @@ How a contract lives. Running akuma is the `keiyaku-akuma` skill; task planning
|
|
|
13
13
|
- A contract is one delivery intent with one ledger, branch, and linked worktree.
|
|
14
14
|
- Workers edit and test; the host reviews their dirty tree and commits accepted bytes.
|
|
15
15
|
- `audit` reads one pinned candidate. `petition` is the settlement action.
|
|
16
|
-
- Scope is
|
|
16
|
+
- Scope is the smallest closure of files currently expected to be written. Amend
|
|
17
|
+
newly expected exact paths before delivery.
|
|
17
18
|
- An arc is a named story chapter for one coherent stretch inside the contract.
|
|
18
19
|
|
|
19
20
|
Lifecycle:
|
|
@@ -32,8 +33,9 @@ Before binding, complete the fact and decision work needed to state the delivery
|
|
|
32
33
|
- read the registered authority and locate the current owning modules;
|
|
33
34
|
- settle inputs, outputs, state consequences, failure behavior, invariants, and
|
|
34
35
|
forbidden expansion;
|
|
35
|
-
-
|
|
36
|
-
delivery dependencies;
|
|
36
|
+
- identify the smallest expected write set and account for known write overlap
|
|
37
|
+
and delivery dependencies; ownership remains with the authority registry and
|
|
38
|
+
its owning chapter;
|
|
37
39
|
- write checks that prove the critical success path, the highest-risk rejection,
|
|
38
40
|
and the relevant durability, concurrency, or recovery boundary.
|
|
39
41
|
|
|
@@ -48,11 +50,59 @@ After bind, the worker implements and verifies that contract. `amend` records
|
|
|
48
50
|
new evidence or a change of intent that genuinely emerges during implementation
|
|
49
51
|
or review, then implementation continues from the updated contract.
|
|
50
52
|
|
|
53
|
+
## Choosing Scope
|
|
54
|
+
|
|
55
|
+
Scope names the files this contract is currently expected to write, not a
|
|
56
|
+
territory the contract owns. Choose the narrowest form supported by the
|
|
57
|
+
evidence, in this order:
|
|
58
|
+
|
|
59
|
+
1. When the write set is known, list exact repository-relative paths, one per
|
|
60
|
+
line. This is the default and needs no justification.
|
|
61
|
+
2. When filenames are not yet known but changes are scattered within one
|
|
62
|
+
directory, use a one-component wildcard for that component.
|
|
63
|
+
3. Use the recursive wildcard only for a real whole-subtree rewrite, migration,
|
|
64
|
+
or generated-output operation. The Objective must state why the entire tree
|
|
65
|
+
is expected to change.
|
|
66
|
+
|
|
67
|
+
Known files use exact paths:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
keiyaku bind --place fix-pump --objective "make the pump test pass" \
|
|
71
|
+
--scope "src/pump.ts" --scope "tests/unit/pump.test.ts" \
|
|
72
|
+
--checks "pump tests green"
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Unknown filenames within one component may use a one-component wildcard:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
keiyaku bind --place repair-pump-fixtures --objective "repair the affected pump fixtures" \
|
|
79
|
+
--scope "tests/fixtures/pump/*" --checks "pump fixture tests green"
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
A recursive pattern is reserved for an actual whole-tree operation:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
keiyaku bind --place regenerate-api-docs \
|
|
86
|
+
--objective "regenerate API documentation because the generator changes every file under docs/generated" \
|
|
87
|
+
--scope "docs/generated/**" --checks "generated API docs are current"
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
If implementation evidence later identifies one more expected file, amend with
|
|
91
|
+
that exact path:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
keiyaku amend --append-scope "src/pump/metrics.ts" - < amendment.md
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Amend is cheap. Broad Scope consumes parallel coordination and weakens audit
|
|
98
|
+
signal, so moving to a broader pattern needs a reason; moving narrower does not.
|
|
99
|
+
|
|
51
100
|
## The default flow
|
|
52
101
|
|
|
53
102
|
```bash
|
|
54
103
|
keiyaku bind --place fix-pump --objective "make the pump test pass" \
|
|
55
|
-
--scope "src/pump
|
|
104
|
+
--scope "src/pump.ts" --scope "tests/unit/pump.test.ts" \
|
|
105
|
+
--checks "pump tests green"
|
|
56
106
|
# or the same four fields as Markdown: keiyaku bind - < contract.md
|
|
57
107
|
# (# <name> / ## Objective / ## Scope / ## Checks)
|
|
58
108
|
# or promote one or more ready tasks:
|
|
@@ -149,29 +199,34 @@ keiyaku arc - < arc.md # optional iteration boundary: seals the current in
|
|
|
149
199
|
keiyaku renew # main moved under you → rebase onto it. Refuses on conflict
|
|
150
200
|
# instead of guessing; resolve, then renew again.
|
|
151
201
|
keiyaku amend - < amendment.md # scope/checks changed → update the paper before the code
|
|
152
|
-
# Scope terms can be appended durably during a lifecycle operation:
|
|
153
|
-
keiyaku @fix-pump arc --append-scope "docs/generated/**" - < arc.md
|
|
154
|
-
keiyaku @fix-pump renew --append-scope "docs/generated/**"
|
|
155
202
|
keiyaku log # this contract's history
|
|
156
203
|
```
|
|
157
204
|
|
|
158
|
-
`amend` accepts prose plus
|
|
205
|
+
`amend` accepts prose plus one optional ordered scope-pattern delta. Use either
|
|
206
|
+
repeated flags:
|
|
207
|
+
|
|
208
|
+
```sh
|
|
209
|
+
keiyaku amend --append-scope "docs/api-reference.md" --append-scope "!docs/obsolete-api-reference.md" - < amendment.md
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
or a document section:
|
|
159
213
|
|
|
160
214
|
````markdown
|
|
161
|
-
Clarify the
|
|
215
|
+
Clarify the newly expected documentation files.
|
|
162
216
|
|
|
163
217
|
## Scope Append
|
|
164
218
|
~~~
|
|
165
|
-
docs
|
|
166
|
-
!docs/
|
|
219
|
+
docs/api-reference.md
|
|
220
|
+
!docs/obsolete-api-reference.md
|
|
167
221
|
~~~
|
|
168
222
|
````
|
|
169
223
|
|
|
170
224
|
`Scope Append` appends ordered raw gitignore-subset lines in one column-zero
|
|
171
225
|
tilde fence; direct input also accepts a matching backtick fence. `Scope Add`
|
|
172
226
|
and Markdown bullets are rejected. A later
|
|
173
|
-
`!pattern` can narrow earlier scope.
|
|
174
|
-
|
|
227
|
+
`!pattern` can narrow earlier scope. Flags and a document Scope Append block
|
|
228
|
+
are mutually exclusive; plain amendment prose plus flags is valid. Use
|
|
229
|
+
`--append-scope PATTERN` when a lifecycle operation discovers one durable term.
|
|
175
230
|
|
|
176
231
|
Gotchas:
|
|
177
232
|
- `-C DIR` selects the effective working directory; repository discovery starts there.
|