@astrosheep/keiyaku 1.0.0 → 1.0.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/README.md +73 -56
- package/build/.tsbuildinfo +1 -1
- package/build/agents/call-terms_v2.js +103 -0
- package/build/agents/harness/activity-values.js +54 -0
- package/build/agents/harness/event-channel.js +55 -20
- package/build/agents/harness/events.js +47 -1
- package/build/agents/harness/execution-handle.js +292 -61
- package/build/agents/harness/index.js +103 -121
- package/build/agents/harness/pump.js +36 -47
- package/build/agents/harness/runtime.js +3 -0
- package/build/agents/launch-snapshot_v2.js +348 -0
- package/build/agents/opencode-sdk.js +42 -169
- package/build/agents/providers/claude-agent-sdk.js +240 -79
- package/build/agents/providers/codex-app-server.js +280 -72
- package/build/agents/providers/codex-sdk.js +159 -41
- package/build/agents/providers/event-type-tail.js +16 -0
- package/build/agents/providers/opencode-sdk.js +165 -59
- package/build/agents/providers/pi.js +254 -42
- package/build/agents/selector_v2.js +122 -0
- package/build/cli/commands/akuma.js +120 -42
- package/build/cli/completion.js +6 -7
- package/build/cli/flags.js +36 -10
- package/build/cli/help.js +42 -26
- package/build/cli/index.js +225 -93
- package/build/cli/parse.js +194 -75
- package/build/cli/projection-address.js +11 -0
- package/build/cli/render/address.js +88 -0
- package/build/cli/render/arc.js +10 -10
- package/build/cli/render/call.js +59 -0
- package/build/cli/render/compact-text.js +3 -0
- package/build/cli/render/errors.js +4 -2
- package/build/cli/render/format.js +6 -0
- package/build/cli/render/line-width.js +70 -0
- package/build/cli/render/misc.js +7 -4
- package/build/cli/render/petition.js +4 -3
- package/build/cli/render/projection-activity.js +201 -0
- package/build/cli/render/shared.js +46 -12
- package/build/cli/render/status.js +59 -10
- package/build/cli/render/tool-ledger-rollup.js +74 -0
- package/build/cli/render/tool-presentation.js +97 -0
- package/build/cli/render/wait.js +229 -0
- package/build/cli/skills-install.js +42 -24
- package/build/cli/subagent-guard.js +1 -1
- package/build/config/akuma-loader_v2.js +222 -0
- package/build/config/env-keys.js +0 -4
- package/build/config/env.js +0 -6
- package/build/config/settings.js +325 -12
- package/build/core/addressing.js +224 -0
- package/build/core/amend.js +30 -47
- package/build/core/arc.js +68 -60
- package/build/core/bind.js +105 -25
- package/build/core/call-persist.js +69 -0
- package/build/core/call.js +658 -0
- package/build/core/claim.js +72 -44
- package/build/core/command-io.js +19 -19
- package/build/core/context.js +11 -36
- package/build/core/draft.js +2 -3
- package/build/core/entry.js +101 -6
- package/build/core/execution-coordinate.js +62 -0
- package/build/core/execution-pact.js +141 -0
- package/build/core/forfeit.js +2 -3
- package/build/core/hints.js +10 -10
- package/build/core/ids.js +6 -0
- package/build/core/ledger.js +16 -1
- package/build/core/petition-claim-gates.js +14 -12
- package/build/core/petition-claim.js +1 -0
- package/build/core/petition-run.js +6 -2
- package/build/core/petition.js +63 -21
- package/build/core/places.js +106 -13
- package/build/core/projection/generation.js +412 -0
- package/build/core/projection/heart.js +316 -0
- package/build/core/projection/identity.js +81 -0
- package/build/core/projection/leash.js +87 -0
- package/build/core/projection/mint.js +154 -0
- package/build/core/projection-activity.js +219 -0
- package/build/core/projection-coordinate.js +35 -0
- package/build/core/projection-core.js +69 -463
- package/build/core/projection-generation-continuation.js +31 -0
- package/build/core/projection-generation-execution.js +148 -0
- package/build/core/projection-generation-identity.js +17 -0
- package/build/core/projection-generation-launcher.js +90 -0
- package/build/core/projection-generation-process.js +43 -0
- package/build/core/projection-generation-runner.js +144 -0
- package/build/core/projection-generation-runtime.js +15 -0
- package/build/core/projection-generation-store.js +707 -0
- package/build/core/projection-identity.js +11 -0
- package/build/core/projection-life-observer.js +87 -0
- package/build/core/projection-life-protocol.js +95 -0
- package/build/core/projection-mint.js +96 -24
- package/build/core/projection-runner-lock.js +230 -0
- package/build/core/projection-status.js +207 -107
- package/build/core/projection-tells.js +401 -0
- package/build/core/projection-wait.js +155 -0
- package/build/core/projection-wake.js +109 -231
- package/build/core/queue_v2.js +342 -0
- package/build/core/registry.js +6 -3
- package/build/core/render.js +63 -1
- package/build/core/renew.js +80 -53
- package/build/core/scope.js +186 -137
- package/build/core/seal.js +16 -19
- package/build/core/status.js +88 -25
- package/build/core/stored-agent-event.js +101 -0
- package/build/core/transcripts.js +214 -228
- package/build/core/verdict.js +27 -15
- package/build/core/worktree-bootstrap.js +187 -0
- package/build/core/worktree-path.js +72 -18
- package/build/flow-error.js +4 -6
- package/build/generated/version.js +1 -1
- package/build/git/branches.js +6 -1
- package/build/index.js +8 -5
- package/build/keiyaku.js +1 -3
- package/package.json +6 -5
- package/skills/keiyaku/SKILL.md +6 -47
- package/skills/keiyaku-akuma/SKILL.md +75 -0
- package/build/agents/effective-policy.js +0 -32
- package/build/agents/index.js +0 -114
- package/build/agents/selector.js +0 -28
- package/build/cli/render/summon.js +0 -17
- package/build/cli/render/tell.js +0 -11
- package/build/config/akuma-loader.js +0 -268
- package/build/core/queue.js +0 -73
- package/build/core/summon-persist.js +0 -73
- package/build/core/summon.js +0 -377
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import * as path from "node:path";
|
|
2
|
+
import { ProjectionStateError } from "./atomic-publish.js";
|
|
3
|
+
import { parseExecutionBinding as parseCanonicalExecutionBinding, parseExecutionCoordinates, } from "./execution-coordinate.js";
|
|
4
|
+
const TOP_LEVEL_FIELDS = [
|
|
5
|
+
"version",
|
|
6
|
+
"pid",
|
|
7
|
+
"akuma",
|
|
8
|
+
"provider",
|
|
9
|
+
"authority",
|
|
10
|
+
"binding",
|
|
11
|
+
"mintedAt",
|
|
12
|
+
"workspaceRoot",
|
|
13
|
+
];
|
|
14
|
+
const TOP_LEVEL_OPTIONAL_FIELDS = ["effectivePolicy"];
|
|
15
|
+
const PACT_FIELD_NAMES = TOP_LEVEL_FIELDS.filter((field) => field !== "authority");
|
|
16
|
+
function pactError(pactPath, field, reason) {
|
|
17
|
+
return new ProjectionStateError(`invalid projection pact at ${pactPath}: field '${field}' ${reason}`);
|
|
18
|
+
}
|
|
19
|
+
function isRecord(value) {
|
|
20
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
21
|
+
}
|
|
22
|
+
function assertExactFields(value, required, optional, pactPath, fieldPrefix) {
|
|
23
|
+
for (const field of required) {
|
|
24
|
+
if (!(field in value)) {
|
|
25
|
+
throw pactError(pactPath, fieldPrefix ? `${fieldPrefix}.${field}` : field, "is missing");
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const allowed = new Set([...required, ...optional]);
|
|
29
|
+
const unknown = Object.keys(value).filter((field) => !allowed.has(field)).sort()[0];
|
|
30
|
+
if (unknown !== undefined) {
|
|
31
|
+
throw pactError(pactPath, fieldPrefix ? `${fieldPrefix}.${unknown}` : unknown, "is unknown");
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function assertNonBlankString(value, pactPath, field) {
|
|
35
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
36
|
+
throw pactError(pactPath, field, "must be a non-blank string");
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/** Parse the pre-publication authority choice without inventing a durable identity. */
|
|
40
|
+
export function parseExecutionAuthoritySelection(value, pactPath = "projection identity", expectedKind) {
|
|
41
|
+
if (!isRecord(value))
|
|
42
|
+
throw pactError(pactPath, "authority", "must be an object");
|
|
43
|
+
assertExactFields(value, ["kind"], [], pactPath, "authority");
|
|
44
|
+
if (value.kind !== "repository" && value.kind !== "user") {
|
|
45
|
+
throw pactError(pactPath, "authority.kind", "must be 'repository' or 'user'");
|
|
46
|
+
}
|
|
47
|
+
if (expectedKind !== undefined && value.kind !== expectedKind) {
|
|
48
|
+
throw pactError(pactPath, "authority.kind", `must be '${expectedKind}' for this projection mint`);
|
|
49
|
+
}
|
|
50
|
+
return { kind: value.kind };
|
|
51
|
+
}
|
|
52
|
+
/** Parse and validate an execution binding without inventing authority identity. */
|
|
53
|
+
export function parseExecutionBinding(value, pactPath = "projection identity") {
|
|
54
|
+
return parseCanonicalExecutionBinding(value, (field, reason) => pactError(pactPath, field, reason));
|
|
55
|
+
}
|
|
56
|
+
export function assertExecutionPactCoordinates(authorityValue, bindingValue, pactPath = "projection identity") {
|
|
57
|
+
return parseExecutionCoordinates(authorityValue, bindingValue, (field, reason) => pactError(pactPath, field, reason));
|
|
58
|
+
}
|
|
59
|
+
function parseEffectivePolicy(value, pactPath) {
|
|
60
|
+
if (!isRecord(value))
|
|
61
|
+
throw pactError(pactPath, "effectivePolicy", "must be an object");
|
|
62
|
+
assertExactFields(value, [], ["access", "network", "webSearch"], pactPath, "effectivePolicy");
|
|
63
|
+
if (value.access !== undefined && value.access !== "read" && value.access !== "write" && value.access !== "auto") {
|
|
64
|
+
throw pactError(pactPath, "effectivePolicy.access", "must be 'read', 'write', or 'auto'");
|
|
65
|
+
}
|
|
66
|
+
if (value.network !== undefined && value.network !== "disabled" && value.network !== "enabled") {
|
|
67
|
+
throw pactError(pactPath, "effectivePolicy.network", "must be 'disabled' or 'enabled'");
|
|
68
|
+
}
|
|
69
|
+
if (value.webSearch !== undefined && !["disabled", "cached", "live"].includes(String(value.webSearch))) {
|
|
70
|
+
throw pactError(pactPath, "effectivePolicy.webSearch", "must be 'disabled', 'cached', or 'live'");
|
|
71
|
+
}
|
|
72
|
+
return value;
|
|
73
|
+
}
|
|
74
|
+
function parseExecutionPactFieldsRecord(value, pactPath) {
|
|
75
|
+
if (value.version !== 1)
|
|
76
|
+
throw pactError(pactPath, "version", "must equal 1");
|
|
77
|
+
if (!Number.isInteger(value.pid) || value.pid < 0) {
|
|
78
|
+
throw pactError(pactPath, "pid", "must be a non-negative integer");
|
|
79
|
+
}
|
|
80
|
+
assertNonBlankString(value.akuma, pactPath, "akuma");
|
|
81
|
+
assertNonBlankString(value.provider, pactPath, "provider");
|
|
82
|
+
assertNonBlankString(value.mintedAt, pactPath, "mintedAt");
|
|
83
|
+
const mintedAtMs = Date.parse(value.mintedAt);
|
|
84
|
+
if (!Number.isFinite(mintedAtMs) || new Date(mintedAtMs).toISOString() !== value.mintedAt) {
|
|
85
|
+
throw pactError(pactPath, "mintedAt", "must be an ISO-8601 timestamp");
|
|
86
|
+
}
|
|
87
|
+
assertNonBlankString(value.workspaceRoot, pactPath, "workspaceRoot");
|
|
88
|
+
if (!path.isAbsolute(value.workspaceRoot)) {
|
|
89
|
+
throw pactError(pactPath, "workspaceRoot", "must be an absolute path");
|
|
90
|
+
}
|
|
91
|
+
const binding = parseExecutionBinding(value.binding, pactPath);
|
|
92
|
+
return {
|
|
93
|
+
version: 1,
|
|
94
|
+
pid: value.pid,
|
|
95
|
+
akuma: value.akuma,
|
|
96
|
+
provider: value.provider,
|
|
97
|
+
binding,
|
|
98
|
+
mintedAt: value.mintedAt,
|
|
99
|
+
workspaceRoot: value.workspaceRoot,
|
|
100
|
+
...(value.effectivePolicy !== undefined
|
|
101
|
+
? { effectivePolicy: parseEffectivePolicy(value.effectivePolicy, pactPath) }
|
|
102
|
+
: {}),
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
/** Validate every pact field that exists before a repository identity is minted. */
|
|
106
|
+
export function parseExecutionPactFields(value, pactPath = "projection identity") {
|
|
107
|
+
if (!isRecord(value))
|
|
108
|
+
throw pactError(pactPath, "$", "must be an object");
|
|
109
|
+
assertExactFields(value, PACT_FIELD_NAMES, TOP_LEVEL_OPTIONAL_FIELDS, pactPath, "");
|
|
110
|
+
return parseExecutionPactFieldsRecord(value, pactPath);
|
|
111
|
+
}
|
|
112
|
+
export function parseExecutionPact(value, pactPath = "projection identity") {
|
|
113
|
+
if (!isRecord(value))
|
|
114
|
+
throw pactError(pactPath, "$", "must be an object");
|
|
115
|
+
assertExactFields(value, TOP_LEVEL_FIELDS, TOP_LEVEL_OPTIONAL_FIELDS, pactPath, "");
|
|
116
|
+
const fields = parseExecutionPactFieldsRecord(value, pactPath);
|
|
117
|
+
const { authority, binding } = assertExecutionPactCoordinates(value.authority, fields.binding, pactPath);
|
|
118
|
+
return {
|
|
119
|
+
version: fields.version,
|
|
120
|
+
pid: fields.pid,
|
|
121
|
+
akuma: fields.akuma,
|
|
122
|
+
provider: fields.provider,
|
|
123
|
+
authority,
|
|
124
|
+
binding,
|
|
125
|
+
mintedAt: fields.mintedAt,
|
|
126
|
+
workspaceRoot: fields.workspaceRoot,
|
|
127
|
+
...(fields.effectivePolicy !== undefined ? { effectivePolicy: fields.effectivePolicy } : {}),
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
export function parseExecutionPactJson(raw, pactPath = "projection identity") {
|
|
131
|
+
let value;
|
|
132
|
+
try {
|
|
133
|
+
value = JSON.parse(raw);
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
throw new ProjectionStateError(`invalid projection pact at ${pactPath}: field '$' contains malformed JSON`, {
|
|
137
|
+
cause: error,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
return parseExecutionPact(value, pactPath);
|
|
141
|
+
}
|
package/build/core/forfeit.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { FlowError } from "../flow-error.js";
|
|
2
2
|
import { activeLedgerRef, appendWithRetry, readLedger } from "./ledger.js";
|
|
3
|
-
import { dequeueContract } from "./queue.js";
|
|
4
3
|
import { releaseScope } from "./registry.js";
|
|
5
4
|
import { deriveContractState, isTerminalState } from "./status.js";
|
|
6
5
|
import { cleanupContractWorkspace, stableRepoRoot } from "./worktree-path.js";
|
|
@@ -15,7 +14,7 @@ export async function forfeitContract(input) {
|
|
|
15
14
|
if (ledger) {
|
|
16
15
|
const state = deriveContractState(ledger.entries);
|
|
17
16
|
if (state.state === "forfeited") {
|
|
18
|
-
|
|
17
|
+
// Already-forfeited retry only performs idempotent cleanup.
|
|
19
18
|
const cleanup = await cleanupForfeit(input.cwd, input.contractId, input.actor, at);
|
|
20
19
|
return { contractId: input.contractId, cleanup };
|
|
21
20
|
}
|
|
@@ -44,7 +43,7 @@ export async function forfeitContract(input) {
|
|
|
44
43
|
});
|
|
45
44
|
if (!result.ok)
|
|
46
45
|
throw new FlowError("INTERNAL_STATE", `failed to forfeit ${input.contractId}: ${result.reason}`);
|
|
47
|
-
|
|
46
|
+
// Forfeit entry naturally removes membership; no dequeue record.
|
|
48
47
|
const cleanup = await cleanupForfeit(input.cwd, input.contractId, input.actor, at);
|
|
49
48
|
return { contractId: input.contractId, cleanup };
|
|
50
49
|
}
|
package/build/core/hints.js
CHANGED
|
@@ -4,12 +4,12 @@ import { renderTemplate } from "../flow-error.js";
|
|
|
4
4
|
const GUIDANCE_BRANCH_LIST_LIMIT = 3;
|
|
5
5
|
const HELP_BODY_TEMPLATE = [
|
|
6
6
|
"## Workflow",
|
|
7
|
-
"`bind -> arc -> [
|
|
7
|
+
"`bind -> arc -> [call | amend | delivery work]* -> arc | renew | petition`",
|
|
8
8
|
"",
|
|
9
9
|
`- **\${bind}**: Create the active contract ledger and workspace files from Markdown stdin. The worktree must be clean; call it once per Keiyaku.`,
|
|
10
10
|
`- **\${arc}**: Record the delivery arc title, objective, and brief before work starts. It seals any open arc and does not run agents or mutate delivery code.`,
|
|
11
|
-
`- **\${
|
|
12
|
-
`- **akuma list / akuma show**: Inspect available ${ACTOR_IDENTITY_PLURAL} and their resolved provider/executable before choosing \`keiyaku
|
|
11
|
+
`- **\${call}**: Call a ${ACTOR_IDENTITY} for bounded work. An open arc supplies scoped context; the caller still owns review, integration, and commits.`,
|
|
12
|
+
`- **akuma list / akuma show**: Inspect available ${ACTOR_IDENTITY_PLURAL} and their resolved provider/executable before choosing \`keiyaku call NAME\`.`,
|
|
13
13
|
"- **delivery commits**: Commit the actual code or documentation changes in git before sealing them.",
|
|
14
14
|
`- **\${renew}**: Capture receipts, update delivery onto the default branch, seal the open arc, and record the new base.`,
|
|
15
15
|
`- **\${amend}**: Update the Keiyaku when scope, rules, criteria, or intent changed.`,
|
|
@@ -19,12 +19,12 @@ const HELP_BODY_TEMPLATE = [
|
|
|
19
19
|
"- **status**: Render the Kanshi board: 現世 head, arc sparkline, age, drift ▲N, and queue stall.",
|
|
20
20
|
"",
|
|
21
21
|
"## Command Boundaries",
|
|
22
|
-
`- \`\${arc}\` and \`\${renew}\` are ledger operations; they do not
|
|
23
|
-
`- \`\${
|
|
24
|
-
`- \`\${
|
|
22
|
+
`- \`\${arc}\` and \`\${renew}\` are ledger operations; they do not call agents and do not edit delivery code.`,
|
|
23
|
+
`- \`\${call}\` can run outside a Keiyaku, but an open arc gives the ${ACTOR_IDENTITY} better context.`,
|
|
24
|
+
`- \`\${call}\` starts a new session. Use \`keiyaku revive ARTIFACT_ID\` to continue a prior response artifact.`,
|
|
25
25
|
"- `akuma list` and `akuma show <name>` are read-only inspection commands; they do not start a helper.",
|
|
26
|
-
"- `keiyaku
|
|
27
|
-
"- Premature `petition` requests are rejected; continue with `${arc}`, `${
|
|
26
|
+
"- `keiyaku revive` restores the agent from the response artifact; `--incognito` calls cannot be revived later.",
|
|
27
|
+
"- Premature `petition` requests are rejected; continue with `${arc}`, `${call}`, or `${renew}` until the work is verified.",
|
|
28
28
|
"",
|
|
29
29
|
"## Core Files (.keiyaku/)",
|
|
30
30
|
"Detailed configuration and protocol nuances live here.",
|
|
@@ -56,13 +56,13 @@ const HELP_BODY_TEMPLATE = [
|
|
|
56
56
|
"",
|
|
57
57
|
"## Workflow Tactics",
|
|
58
58
|
"- **Pause / Resume**: Use `status` to find active contracts and `@<id-prefix>` to address them from any terminal.",
|
|
59
|
-
"- **Delegation Markers**: Place `KEIYAKU_TODO(<instruction>)` comments when you want ${
|
|
59
|
+
"- **Delegation Markers**: Place `KEIYAKU_TODO(<instruction>)` comments when you want ${call} or ${bind} to pick up scoped follow-on work.",
|
|
60
60
|
];
|
|
61
61
|
const ARCHITECT_TOOL_NAMES = {
|
|
62
62
|
bind: TOOL_NAMES.bind,
|
|
63
63
|
arc: TOOL_NAMES.arc,
|
|
64
64
|
renew: TOOL_NAMES.renew,
|
|
65
|
-
|
|
65
|
+
call: TOOL_NAMES.call,
|
|
66
66
|
amend: TOOL_NAMES.amend,
|
|
67
67
|
close: TOOL_NAMES.close,
|
|
68
68
|
};
|
package/build/core/ids.js
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { createGit, wrapGitError } from "../git/core.js";
|
|
2
2
|
const CROCKFORD = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
|
|
3
3
|
const ULID_RE = /^[0-9A-HJKMNP-TV-Z]{26}$/;
|
|
4
|
+
/** Canonical commission identity: slugified lowercase hyphen segments + `.` + Crockford ULID. */
|
|
5
|
+
export const COMMISSION_ID_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*\.[0-9A-HJKMNP-TV-Z]{26}$/;
|
|
6
|
+
/** Minted opaque repository identity: `repo_` + ULID. */
|
|
7
|
+
export function isCommissionId(value) {
|
|
8
|
+
return COMMISSION_ID_RE.test(value);
|
|
9
|
+
}
|
|
4
10
|
export class ContractIdError extends Error {
|
|
5
11
|
code = "INVALID_CONTRACT_ID";
|
|
6
12
|
constructor(message, options) {
|
package/build/core/ledger.js
CHANGED
|
@@ -2,8 +2,9 @@ import { createGit, errorContainsAnyPattern, MISSING_HEAD_PATTERNS, wrapGitError
|
|
|
2
2
|
import { formatLedgerEntryCommitMessage, parseLedgerEntryCommitMessage, } from "./entry.js";
|
|
3
3
|
const EMPTY_TREE_HASH = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
|
|
4
4
|
const MISSING_REF_SENTINEL = "0".repeat(40);
|
|
5
|
+
export const ACTIVE_LEDGER_REF_PREFIX = "refs/keiyaku/active/";
|
|
5
6
|
export function activeLedgerRef(contractId) {
|
|
6
|
-
return
|
|
7
|
+
return `${ACTIVE_LEDGER_REF_PREFIX}${contractId}`;
|
|
7
8
|
}
|
|
8
9
|
async function resolveRefHead(cwd, ref) {
|
|
9
10
|
const git = createGit(cwd);
|
|
@@ -115,3 +116,17 @@ export async function readLedger(cwd, contractId) {
|
|
|
115
116
|
return null;
|
|
116
117
|
return { contractId, ...(await readLedgerRef(cwd, ref, head)) };
|
|
117
118
|
}
|
|
119
|
+
export async function listContractIds(cwd) {
|
|
120
|
+
const git = createGit(cwd);
|
|
121
|
+
try {
|
|
122
|
+
const output = await git.raw(["for-each-ref", "--format=%(refname)", ACTIVE_LEDGER_REF_PREFIX]);
|
|
123
|
+
return output
|
|
124
|
+
.split(/\r?\n/)
|
|
125
|
+
.map((line) => line.trim())
|
|
126
|
+
.filter((line) => line.startsWith(ACTIVE_LEDGER_REF_PREFIX))
|
|
127
|
+
.map((ref) => ref.slice(ACTIVE_LEDGER_REF_PREFIX.length));
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
throw wrapGitError(`for-each-ref ${ACTIVE_LEDGER_REF_PREFIX}`, error, cwd);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { getConfig } from "../config/env.js";
|
|
3
3
|
import { assertSettingsKnobUsable, loadKeiyakuSettings, resolveReviewerAgentName, } from "../config/settings.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
4
|
+
import { loadAkumaCatalogV2 } from "../config/akuma-loader_v2.js";
|
|
5
|
+
import { formatAvailableAgentNamesV2 } from "../agents/selector_v2.js";
|
|
6
6
|
import { getDiffStats } from "../git/diff/preview.js";
|
|
7
7
|
import { readDiff } from "../git/diff/read.js";
|
|
8
8
|
import { getCurrentBranch } from "../git/branches.js";
|
|
@@ -11,7 +11,7 @@ import { DIFF_PREVIEW_TEXT_MAX_CHARS } from "../keiyaku.js";
|
|
|
11
11
|
import { FlowError } from "../flow-error.js";
|
|
12
12
|
import { persistResponseHistory } from "./transcripts.js";
|
|
13
13
|
import { logInfo } from "../telemetry/logger.js";
|
|
14
|
-
import { execSubagent } from "./
|
|
14
|
+
import { execSubagent } from "./call.js";
|
|
15
15
|
import { readContractLog } from "./log.js";
|
|
16
16
|
import { buildVerificationFailureHints } from "./hints.js";
|
|
17
17
|
const CLAIM_VERIFICATION_OUTPUT_MAX_CHARS = 1200;
|
|
@@ -159,7 +159,7 @@ export function createAgentReviewGate() {
|
|
|
159
159
|
return REVIEW_SKIPPED_RESULT;
|
|
160
160
|
}
|
|
161
161
|
const loaded = await loadKeiyakuSettings(cwd);
|
|
162
|
-
// Always touch
|
|
162
|
+
// Always touch agents catalog disease + reviewer. Touch default only when reviewer is absent
|
|
163
163
|
// and resolution would fall back through resolveReviewerAgentName → default.
|
|
164
164
|
assertSettingsKnobUsable(loaded, "agents");
|
|
165
165
|
assertSettingsKnobUsable(loaded, "reviewer");
|
|
@@ -168,10 +168,10 @@ export function createAgentReviewGate() {
|
|
|
168
168
|
}
|
|
169
169
|
const settings = loaded.knobs;
|
|
170
170
|
const reviewerAgentName = resolveReviewerAgentName(settings);
|
|
171
|
-
const catalog = await
|
|
172
|
-
const availableNames = Object.keys(catalog.
|
|
173
|
-
if (catalog.
|
|
174
|
-
throw new FlowError("UNKNOWN_SUBAGENT", `Unknown agent '${reviewerAgentName}'. Expected one of: ${
|
|
171
|
+
const catalog = await loadAkumaCatalogV2(cwd);
|
|
172
|
+
const availableNames = Object.keys(catalog.selected);
|
|
173
|
+
if (catalog.selected[reviewerAgentName] === undefined) {
|
|
174
|
+
throw new FlowError("UNKNOWN_SUBAGENT", `Unknown agent '${reviewerAgentName}'. Expected one of: ${formatAvailableAgentNamesV2(availableNames)}`);
|
|
175
175
|
}
|
|
176
176
|
const [contractLog, diffStat, fullDiff, currentBranch, headRef] = await Promise.all([
|
|
177
177
|
readContractLog({ cwd, contractId }),
|
|
@@ -197,26 +197,28 @@ export function createAgentReviewGate() {
|
|
|
197
197
|
const selectedReviewerAgentName = reviewerAgentName;
|
|
198
198
|
const subagentResult = await execSubagent(selectedReviewerAgentName, prompt, cwd, {
|
|
199
199
|
signal: input.signal,
|
|
200
|
+
authority: { kind: "repository" },
|
|
201
|
+
binding: { kind: "commission", commissionId: contractId },
|
|
200
202
|
});
|
|
201
203
|
const reviewHeadBeforeWrite = await getLatestCommitHash(cwd);
|
|
202
204
|
const raw = subagentResult.finalMessage.trim();
|
|
203
205
|
const passed = raw.startsWith(REVIEW_PASS_TOKEN);
|
|
204
206
|
const observations = passed ? raw.slice(REVIEW_PASS_TOKEN.length).trim() || undefined : undefined;
|
|
205
|
-
const
|
|
207
|
+
const persisted = await persistResponseHistory({
|
|
206
208
|
cwd,
|
|
207
209
|
tool: "review",
|
|
210
|
+
provenance: { authority: { kind: "repository" }, binding: { kind: "commission", commissionId: contractId }, evidenceCwd: cwd },
|
|
208
211
|
title: String(reviewNumber),
|
|
209
212
|
prompt,
|
|
210
213
|
response: subagentResult.finalMessage,
|
|
211
214
|
session: subagentResult.session,
|
|
212
215
|
subagentName: selectedReviewerAgentName,
|
|
213
|
-
|
|
214
|
-
commit: headRef,
|
|
216
|
+
launchSnapshot: subagentResult.launchSnapshot,
|
|
215
217
|
});
|
|
216
218
|
const review = {
|
|
217
219
|
review: reviewNumber,
|
|
218
220
|
result: passed ? "passed" : "rejected",
|
|
219
|
-
historyPath: responsePath,
|
|
221
|
+
historyPath: persisted.responsePath,
|
|
220
222
|
findings: passed ? observations : raw || REVIEW_RESPONSE_FALLBACK,
|
|
221
223
|
};
|
|
222
224
|
if (review.result === "passed") {
|
|
@@ -2,7 +2,7 @@ import { assertSettingsKnobUsable, loadKeiyakuSettings } from "../config/setting
|
|
|
2
2
|
import { FlowError } from "../flow-error.js";
|
|
3
3
|
import { readCurrentContractContext } from "./context.js";
|
|
4
4
|
import { readLedger } from "./ledger.js";
|
|
5
|
-
import {
|
|
5
|
+
import { commissionTarget } from "./status.js";
|
|
6
6
|
import { getCurrentBranch, isGitRepo } from "../git/branches.js";
|
|
7
7
|
import { claimKeiyaku } from "./petition-claim.js";
|
|
8
8
|
import { forfeitKeiyaku } from "./petition-forfeit.js";
|
|
@@ -41,10 +41,14 @@ async function loadCloseContext(cwd, requestedContractId) {
|
|
|
41
41
|
if (!ledger) {
|
|
42
42
|
throw new FlowError("INTERNAL_STATE", `contract ${requestedContractId} not found or not active`);
|
|
43
43
|
}
|
|
44
|
+
const target = commissionTarget(ledger.entries);
|
|
45
|
+
if (!target) {
|
|
46
|
+
throw new FlowError("INTERNAL_STATE", `contract ${requestedContractId} active ledger is missing commission target`);
|
|
47
|
+
}
|
|
44
48
|
return {
|
|
45
49
|
cwd,
|
|
46
50
|
keiyakuBranch: `keiyaku/${requestedContractId}`,
|
|
47
|
-
baseBranch:
|
|
51
|
+
baseBranch: target.branch,
|
|
48
52
|
contractId: requestedContractId,
|
|
49
53
|
};
|
|
50
54
|
}
|
package/build/core/petition.js
CHANGED
|
@@ -1,18 +1,32 @@
|
|
|
1
1
|
import { FlowError } from "../flow-error.js";
|
|
2
|
+
import { createGit, errorContainsAnyPattern, MISSING_HEAD_PATTERNS, wrapGitError } from "../git/core.js";
|
|
2
3
|
import { parseCommandMarkdown } from "./command-io.js";
|
|
3
4
|
import { activeLedgerRef, appendWithRetry, readLedger } from "./ledger.js";
|
|
4
|
-
import {
|
|
5
|
+
import { allocateSettlementSeat, queueMembership, readQueueReadModel } from "./queue_v2.js";
|
|
5
6
|
import { findOpenArc, receiptsAfterFence, sealOpenArc } from "./seal.js";
|
|
6
|
-
import { deriveContractState, latestBase
|
|
7
|
+
import { commissionTarget, deriveContractState, latestBase } from "./status.js";
|
|
7
8
|
import { findContractWorktreePath } from "./worktree-path.js";
|
|
8
|
-
async function
|
|
9
|
-
|
|
9
|
+
async function resolveCommit(cwd, ref) {
|
|
10
|
+
const git = createGit(cwd);
|
|
11
|
+
try {
|
|
12
|
+
return (await git.raw(["rev-parse", "--verify", `${ref}^{commit}`])).trim();
|
|
13
|
+
}
|
|
14
|
+
catch (error) {
|
|
15
|
+
if (errorContainsAnyPattern(error, MISSING_HEAD_PATTERNS))
|
|
16
|
+
return null;
|
|
17
|
+
throw wrapGitError(`rev-parse ${ref}`, error, cwd);
|
|
18
|
+
}
|
|
10
19
|
}
|
|
11
|
-
async function assertFresh(cwd, contractId, base) {
|
|
12
|
-
const target =
|
|
13
|
-
if (!target)
|
|
14
|
-
|
|
15
|
-
|
|
20
|
+
async function assertFresh(cwd, contractId, entries, base) {
|
|
21
|
+
const target = commissionTarget(entries);
|
|
22
|
+
if (!target) {
|
|
23
|
+
throw new FlowError("INTERNAL_STATE", `contract ${contractId} missing commission target`);
|
|
24
|
+
}
|
|
25
|
+
const head = await resolveCommit(cwd, target.ref);
|
|
26
|
+
if (!head) {
|
|
27
|
+
throw new FlowError("INTERNAL_STATE", `contract ${contractId} commission target ${target.ref} does not resolve`);
|
|
28
|
+
}
|
|
29
|
+
if (base !== head) {
|
|
16
30
|
throw new FlowError("INTERNAL_STATE", `contract ${contractId} must renew onto ${target.branch} before petition`, { hints: [`keiyaku @${contractId} renew — renews delivery before petition`] });
|
|
17
31
|
}
|
|
18
32
|
}
|
|
@@ -24,20 +38,15 @@ function assertPetitionable(contractId, entries) {
|
|
|
24
38
|
}
|
|
25
39
|
export async function petitionContract(input) {
|
|
26
40
|
const at = new Date(input.nowMs).toISOString();
|
|
27
|
-
|
|
41
|
+
const ledger = await readLedger(input.cwd, input.contractId);
|
|
28
42
|
if (!ledger) {
|
|
29
43
|
throw new FlowError("INTERNAL_STATE", `contract ${input.contractId} not found or not active`);
|
|
30
44
|
}
|
|
31
|
-
const existingState = deriveContractState(ledger.entries);
|
|
32
|
-
if (existingState.state === "petitioned") {
|
|
33
|
-
const queued = await enqueueContract(input.cwd, { contractId: input.contractId, actor: input.actor, at });
|
|
34
|
-
return { contractId: input.contractId, position: await settledQueuePosition(input.cwd, input.contractId, queued.position) };
|
|
35
|
-
}
|
|
36
45
|
const deliveryCwd = (await findContractWorktreePath(input.cwd, input.contractId)) ?? input.cwd;
|
|
37
46
|
const base = latestBase(ledger.entries);
|
|
38
47
|
if (!base)
|
|
39
48
|
throw new FlowError("INTERNAL_STATE", "ledger must contain bind entry");
|
|
40
|
-
await assertFresh(input.cwd, input.contractId, base);
|
|
49
|
+
await assertFresh(input.cwd, input.contractId, ledger.entries, base);
|
|
41
50
|
if (!findOpenArc(ledger.entries)) {
|
|
42
51
|
const loose = await receiptsAfterFence(deliveryCwd, ledger.entries);
|
|
43
52
|
if (loose.length > 0) {
|
|
@@ -51,6 +60,16 @@ export async function petitionContract(input) {
|
|
|
51
60
|
contractId: input.contractId,
|
|
52
61
|
sealedBy: "petition",
|
|
53
62
|
nowMs: input.nowMs,
|
|
63
|
+
waivePaths: input.intent === "claim" ? input.waivePaths : undefined,
|
|
64
|
+
});
|
|
65
|
+
const target = commissionTarget(ledger.entries);
|
|
66
|
+
if (!target) {
|
|
67
|
+
throw new FlowError("INTERNAL_STATE", `contract ${input.contractId} missing commission target`);
|
|
68
|
+
}
|
|
69
|
+
const allocated = await allocateSettlementSeat(input.cwd, {
|
|
70
|
+
target: target.ref,
|
|
71
|
+
actor: input.actor,
|
|
72
|
+
at,
|
|
54
73
|
});
|
|
55
74
|
const ref = activeLedgerRef(input.contractId);
|
|
56
75
|
const result = await appendWithRetry(input.cwd, ref, (_head, entries) => {
|
|
@@ -59,8 +78,17 @@ export async function petitionContract(input) {
|
|
|
59
78
|
return null;
|
|
60
79
|
assertPetitionable(input.contractId, entries);
|
|
61
80
|
const data = input.intent === "claim"
|
|
62
|
-
? {
|
|
63
|
-
|
|
81
|
+
? {
|
|
82
|
+
intent: "claim",
|
|
83
|
+
oath: input.oath,
|
|
84
|
+
target: target.ref,
|
|
85
|
+
seat: allocated.seat,
|
|
86
|
+
}
|
|
87
|
+
: {
|
|
88
|
+
intent: "forfeit",
|
|
89
|
+
target: target.ref,
|
|
90
|
+
seat: allocated.seat,
|
|
91
|
+
};
|
|
64
92
|
const entry = {
|
|
65
93
|
v: 1,
|
|
66
94
|
kind: "petition",
|
|
@@ -71,13 +99,26 @@ export async function petitionContract(input) {
|
|
|
71
99
|
};
|
|
72
100
|
return entry;
|
|
73
101
|
});
|
|
74
|
-
if (!result.ok
|
|
102
|
+
if (!result.ok) {
|
|
103
|
+
// Race/abort: allocated seat remains a legal hole; do not report success.
|
|
104
|
+
if (result.reason === "aborted") {
|
|
105
|
+
const current = await readLedger(input.cwd, input.contractId);
|
|
106
|
+
if (current) {
|
|
107
|
+
assertPetitionable(input.contractId, current.entries);
|
|
108
|
+
}
|
|
109
|
+
throw new FlowError("INTERNAL_STATE", `failed to petition contract ${input.contractId}: aborted`);
|
|
110
|
+
}
|
|
75
111
|
throw new FlowError("INTERNAL_STATE", `failed to petition contract ${input.contractId}: ${result.reason}`);
|
|
76
112
|
}
|
|
77
|
-
const
|
|
113
|
+
const model = await readQueueReadModel(input.cwd);
|
|
114
|
+
const membership = queueMembership(model, input.contractId);
|
|
115
|
+
const position = membership?.position ?? 0;
|
|
116
|
+
if (position <= 0) {
|
|
117
|
+
throw new FlowError("INTERNAL_STATE", `contract ${input.contractId} missing target-local queue position after petition`);
|
|
118
|
+
}
|
|
78
119
|
return {
|
|
79
120
|
contractId: input.contractId,
|
|
80
|
-
position
|
|
121
|
+
position,
|
|
81
122
|
...(sealed ? { sealed } : {}),
|
|
82
123
|
};
|
|
83
124
|
}
|
|
@@ -92,5 +133,6 @@ export function buildPetitionClaimInput(input) {
|
|
|
92
133
|
plea: `Settle contract ${input.contractId} through the petition pipeline.`,
|
|
93
134
|
oath,
|
|
94
135
|
signal: input.signal,
|
|
136
|
+
waivePaths: input.waivePaths,
|
|
95
137
|
};
|
|
96
138
|
}
|
package/build/core/places.js
CHANGED
|
@@ -5,7 +5,6 @@ export const CONTRACT_PLACES = Object.freeze([
|
|
|
5
5
|
"neverland",
|
|
6
6
|
"mordor",
|
|
7
7
|
"shire",
|
|
8
|
-
"rivendell",
|
|
9
8
|
"olympus",
|
|
10
9
|
"valhalla",
|
|
11
10
|
"asgard",
|
|
@@ -18,7 +17,6 @@ export const CONTRACT_PLACES = Object.freeze([
|
|
|
18
17
|
"namek",
|
|
19
18
|
"laputa",
|
|
20
19
|
"tatooine",
|
|
21
|
-
"pandora",
|
|
22
20
|
"stomach",
|
|
23
21
|
"eyeball",
|
|
24
22
|
"braincell",
|
|
@@ -27,20 +25,10 @@ export const CONTRACT_PLACES = Object.freeze([
|
|
|
27
25
|
"armpit",
|
|
28
26
|
"chineseroom",
|
|
29
27
|
"catbox",
|
|
30
|
-
"platocave",
|
|
31
|
-
"hilberthotel",
|
|
32
|
-
"panopticon",
|
|
33
|
-
"theseus",
|
|
34
28
|
"babel",
|
|
35
29
|
"teapot",
|
|
36
30
|
"blackhole",
|
|
37
31
|
"wormhole",
|
|
38
|
-
"oddpoint",
|
|
39
|
-
"eventhorizon",
|
|
40
|
-
"kleinbottle",
|
|
41
|
-
"mobius",
|
|
42
|
-
"tesseract",
|
|
43
|
-
"nullisland",
|
|
44
32
|
"devnull",
|
|
45
33
|
"localhost",
|
|
46
34
|
"recyclebin",
|
|
@@ -77,6 +65,111 @@ export const CONTRACT_PLACES = Object.freeze([
|
|
|
77
65
|
"dumpster",
|
|
78
66
|
"blanketfort",
|
|
79
67
|
"kotatsu",
|
|
80
|
-
"toire",
|
|
81
68
|
"fishbowl",
|
|
69
|
+
"pokeball",
|
|
70
|
+
"pineapple",
|
|
71
|
+
"titanic",
|
|
72
|
+
"disneyland",
|
|
73
|
+
"ikea",
|
|
74
|
+
"costco",
|
|
75
|
+
"warppipe",
|
|
76
|
+
"tetris",
|
|
77
|
+
"minecraft",
|
|
78
|
+
"ufo",
|
|
79
|
+
"catbus",
|
|
80
|
+
"timemachine",
|
|
81
|
+
"moonbox",
|
|
82
|
+
"huaguoshan",
|
|
83
|
+
"pansidong",
|
|
84
|
+
"bellybutton",
|
|
85
|
+
"sock",
|
|
86
|
+
"spam",
|
|
87
|
+
"downloads",
|
|
88
|
+
"toilet",
|
|
89
|
+
"blender",
|
|
90
|
+
"toaster",
|
|
91
|
+
"litterbox",
|
|
92
|
+
"kiddiepool",
|
|
93
|
+
"beanbag",
|
|
94
|
+
"vacuum",
|
|
95
|
+
"elevator",
|
|
96
|
+
"glovebox",
|
|
97
|
+
"attic",
|
|
98
|
+
"cloud",
|
|
99
|
+
"wall",
|
|
100
|
+
"can",
|
|
101
|
+
"cup",
|
|
102
|
+
"wc",
|
|
103
|
+
"bean",
|
|
104
|
+
"bus",
|
|
105
|
+
"helicopter",
|
|
106
|
+
"commandroom",
|
|
107
|
+
"box",
|
|
108
|
+
"jar",
|
|
109
|
+
"pot",
|
|
110
|
+
"wok",
|
|
111
|
+
"bowl",
|
|
112
|
+
"bucket",
|
|
113
|
+
"drawer",
|
|
114
|
+
"sofa",
|
|
115
|
+
"fridge",
|
|
116
|
+
"microwave",
|
|
117
|
+
"well",
|
|
118
|
+
"ditch",
|
|
119
|
+
"puddle",
|
|
120
|
+
"tent",
|
|
121
|
+
"cave",
|
|
122
|
+
"shed",
|
|
123
|
+
"garage",
|
|
124
|
+
"basement",
|
|
125
|
+
"balcony",
|
|
126
|
+
"corner",
|
|
127
|
+
"pigpen",
|
|
128
|
+
"henhouse",
|
|
129
|
+
"excavator",
|
|
130
|
+
"tank",
|
|
131
|
+
"submarine",
|
|
132
|
+
"treehouse",
|
|
133
|
+
"igloo",
|
|
134
|
+
"phonebooth",
|
|
135
|
+
"mailbox",
|
|
136
|
+
"doormat",
|
|
137
|
+
"bathtub",
|
|
138
|
+
"wardrobe",
|
|
139
|
+
"quicksand",
|
|
140
|
+
"tarpit",
|
|
141
|
+
"haystack",
|
|
142
|
+
"manhole",
|
|
143
|
+
"chimney",
|
|
144
|
+
"dryer",
|
|
145
|
+
"fittingroom",
|
|
146
|
+
"lazysusan",
|
|
147
|
+
"revolvingdoor",
|
|
148
|
+
"minibar",
|
|
149
|
+
"casino",
|
|
150
|
+
"karaoke",
|
|
151
|
+
"pinball",
|
|
152
|
+
"funhouse",
|
|
153
|
+
"burrito",
|
|
154
|
+
"dumpling",
|
|
155
|
+
"hotpot",
|
|
156
|
+
"bento",
|
|
157
|
+
"teabag",
|
|
158
|
+
"fortunecookie",
|
|
159
|
+
"airlock",
|
|
160
|
+
"escapepod",
|
|
161
|
+
"hammock",
|
|
162
|
+
"legroom",
|
|
163
|
+
"sidequest",
|
|
164
|
+
"lootbox",
|
|
165
|
+
"gutter",
|
|
166
|
+
"pawnshop",
|
|
167
|
+
"hermitcrab",
|
|
168
|
+
"couchgap",
|
|
169
|
+
"dumbwaiter",
|
|
170
|
+
"petridish",
|
|
171
|
+
"baggageclaim",
|
|
172
|
+
"morgue",
|
|
173
|
+
"ballpit",
|
|
174
|
+
"clawmachine",
|
|
82
175
|
]);
|