@astrosheep/keiyaku 2.8.4 → 2.9.0
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 +35 -15
- package/build/.tsbuildinfo +1 -1
- package/build/agents/{call-terms_v2.js → call-terms.js} +12 -8
- package/build/agents/harness/control-types.js +1 -0
- package/build/agents/harness/execution-handle.js +23 -3
- package/build/agents/harness/index.js +6 -607
- package/build/agents/harness/outcome.js +141 -0
- package/build/agents/harness/projection.js +310 -0
- package/build/agents/harness/router.js +201 -0
- package/build/agents/harness/runtime.js +2 -1
- package/build/agents/{launch-snapshot_v2.js → launch-snapshot/model.js} +69 -74
- package/build/agents/launch-snapshot/resolve.js +72 -0
- package/build/agents/opencode-sdk.js +24 -3
- package/build/agents/providers/{claude-agent-sdk.js → claude-agent-sdk/adapter.js} +46 -368
- package/build/agents/providers/claude-agent-sdk/events.js +218 -0
- package/build/agents/providers/claude-agent-sdk/session.js +158 -0
- package/build/agents/providers/codex-app-server/adapter.js +347 -0
- package/build/agents/providers/codex-app-server/events.js +352 -0
- package/build/agents/providers/codex-app-server/session.js +266 -0
- package/build/agents/providers/codex-sdk.js +23 -5
- package/build/agents/providers/opencode-sdk/adapter.js +344 -0
- package/build/agents/providers/opencode-sdk/events.js +354 -0
- package/build/agents/providers/opencode-sdk/session.js +180 -0
- package/build/agents/providers/pi/adapter.js +269 -0
- package/build/agents/providers/pi/checkpoint.js +41 -0
- package/build/agents/providers/pi/events.js +282 -0
- package/build/agents/{selector_v2.js → selector.js} +8 -6
- package/build/cli/commands/akuma/akuma/handler.js +4 -0
- package/build/cli/commands/akuma/akuma/meta.js +16 -0
- package/build/cli/commands/akuma/catalog.js +8 -0
- package/build/cli/commands/akuma/list/handler.js +15 -0
- package/build/cli/commands/akuma/list/meta-ls.js +9 -0
- package/build/cli/commands/akuma/view/handler.js +34 -0
- package/build/cli/commands/akuma/view/meta.js +8 -0
- package/build/cli/commands/akuma.js +1 -1
- package/build/cli/commands/contract/amend/handler.js +10 -0
- package/build/cli/commands/contract/amend/meta.js +12 -0
- package/build/cli/commands/contract/arc/handler.js +9 -0
- package/build/cli/commands/contract/arc/meta.js +26 -0
- package/build/cli/commands/contract/bind/handler.js +23 -0
- package/build/cli/commands/contract/bind/meta.js +30 -0
- package/build/cli/commands/contract/catalog.js +16 -0
- package/build/cli/commands/contract/forfeit/handler.js +13 -0
- package/build/cli/commands/contract/forfeit/meta.js +12 -0
- package/build/cli/commands/contract/log/handler.js +12 -0
- package/build/cli/commands/contract/log/meta.js +12 -0
- package/build/cli/commands/contract/petition/handler.js +26 -0
- package/build/cli/commands/contract/petition/meta.js +23 -0
- package/build/cli/commands/contract/renew/handler.js +10 -0
- package/build/cli/commands/contract/renew/meta.js +17 -0
- package/build/cli/commands/projection/call/handler.js +39 -0
- package/build/cli/commands/projection/call/meta.js +9 -0
- package/build/cli/commands/projection/catalog.js +14 -0
- package/build/cli/commands/projection/kill/handler.js +35 -0
- package/build/cli/commands/projection/kill/meta.js +11 -0
- package/build/cli/commands/projection/revive/handler.js +53 -0
- package/build/cli/commands/projection/revive/meta.js +9 -0
- package/build/cli/commands/projection/status/handler.js +23 -0
- package/build/cli/commands/projection/status/meta.js +9 -0
- package/build/cli/commands/projection/tell/handler.js +34 -0
- package/build/cli/commands/projection/tell/meta.js +9 -0
- package/build/cli/commands/projection/wait/handler.js +21 -0
- package/build/cli/commands/projection/wait/meta.js +11 -0
- package/build/cli/commands/registry.js +100 -0
- package/build/cli/commands/shared.js +71 -0
- package/build/cli/commands/skills/catalog.js +6 -0
- package/build/cli/commands/skills/install/handler.js +10 -0
- package/build/cli/commands/skills/install/meta.js +9 -0
- package/build/cli/commands/skills/skills/handler.js +4 -0
- package/build/cli/commands/skills/skills/meta.js +10 -0
- package/build/cli/commands/system/catalog.js +8 -0
- package/build/cli/commands/system/completion/handler.js +10 -0
- package/build/cli/commands/system/completion/meta.js +9 -0
- package/build/cli/commands/system/dump-env/handler.js +3 -0
- package/build/cli/commands/system/dump-env/meta.js +10 -0
- package/build/cli/commands/system/guide/handler.js +4 -0
- package/build/cli/commands/system/guide/meta.js +9 -0
- package/build/cli/commands/task/add/handler.js +21 -0
- package/build/cli/commands/task/add/meta.js +8 -0
- package/build/cli/commands/task/catalog.js +22 -0
- package/build/cli/commands/task/doctor/handler.js +4 -0
- package/build/cli/commands/task/doctor/meta.js +8 -0
- package/build/cli/commands/task/done/handler.js +17 -0
- package/build/cli/commands/task/done/meta.js +8 -0
- package/build/cli/commands/task/drop/handler.js +6 -0
- package/build/cli/commands/task/drop/meta.js +8 -0
- package/build/cli/commands/task/ls/handler.js +13 -0
- package/build/cli/commands/task/ls/meta.js +8 -0
- package/build/cli/commands/task/shared.js +131 -0
- package/build/cli/commands/task/start/handler.js +6 -0
- package/build/cli/commands/task/start/meta.js +8 -0
- package/build/cli/commands/task/stop/handler.js +17 -0
- package/build/cli/commands/task/stop/meta.js +8 -0
- package/build/cli/commands/task/task/handler.js +5 -0
- package/build/cli/commands/task/task/meta.js +11 -0
- package/build/cli/commands/task/update/handler.js +18 -0
- package/build/cli/commands/task/update/meta.js +8 -0
- package/build/cli/commands/task/view/handler.js +14 -0
- package/build/cli/commands/task/view/meta.js +8 -0
- package/build/cli/completion.js +26 -27
- package/build/cli/flags.js +79 -2
- package/build/cli/help.js +27 -49
- package/build/cli/index.js +121 -374
- package/build/cli/output-stream-error-policy.js +17 -0
- package/build/cli/parse.js +159 -72
- package/build/cli/projection-address.js +5 -0
- package/build/cli/render/arc.js +1 -1
- package/build/cli/render/errors.js +4 -3
- package/build/cli/render/format.js +14 -0
- package/build/cli/render/kanshi.js +106 -0
- package/build/cli/render/petition.js +24 -1
- package/build/cli/render/projection-activity.js +5 -1
- package/build/cli/render/shared.js +2 -2
- package/build/cli/render/status.js +79 -13
- package/build/cli/render/task.js +113 -0
- package/build/cli/render/wait.js +23 -3
- package/build/cli/types.js +1 -1
- package/build/config/{akuma-loader_v2.js → akuma-loader.js} +2 -2
- package/build/config/env.js +9 -4
- package/build/config/settings/disease.js +26 -0
- package/build/config/settings/knobs.js +8 -0
- package/build/config/settings/loader.js +244 -0
- package/build/config/settings/schema.js +276 -0
- package/build/core/addressing.js +1 -1
- package/build/core/amend.js +3 -3
- package/build/core/arc.js +5 -4
- package/build/core/bind-workspace.js +123 -0
- package/build/core/bind.js +75 -155
- package/build/core/call/call.js +224 -0
- package/build/core/call/context.js +238 -0
- package/build/core/call/execution.js +217 -0
- package/build/core/call/prompt.js +42 -0
- package/build/core/call/reference.js +24 -0
- package/build/core/call-persist.js +1 -1
- package/build/core/claim-delivery.js +247 -0
- package/build/core/claim.js +39 -277
- package/build/core/context.js +2 -1
- package/build/core/{render.js → contract-view.js} +12 -140
- package/build/core/draft.js +107 -9
- package/build/core/entry.js +9 -1
- package/build/core/execution-coordinate.js +4 -20
- package/build/core/execution-pact.js +16 -31
- package/build/core/forfeit.js +35 -22
- package/build/core/hints.js +2 -2
- package/build/core/log.js +2 -2
- package/build/core/petition-claim-gates.js +8 -6
- package/build/core/petition-preview.js +97 -0
- package/build/core/petition-run.js +3 -2
- package/build/core/petition.js +39 -26
- package/build/core/projection/generation/database.js +306 -0
- package/build/core/projection/generation/identity.js +84 -0
- package/build/core/projection/generation/model.js +201 -0
- package/build/core/projection/generation/store.js +73 -0
- package/build/core/projection/generation/transitions.js +258 -0
- package/build/core/projection/heart.js +0 -307
- package/build/core/projection/tell/database.js +109 -0
- package/build/core/projection/tell/model.js +97 -0
- package/build/core/{projection-tells.js → projection/tell/store.js} +3 -158
- package/build/core/projection-activity.js +23 -2
- package/build/core/projection-core.js +2 -1
- package/build/core/projection-execution-observer.js +259 -0
- package/build/core/projection-generation-continuation.js +1 -1
- package/build/core/projection-generation-execution.js +90 -20
- package/build/core/projection-generation-launcher.js +1 -1
- package/build/core/projection-generation-runner.js +198 -4
- package/build/core/projection-identity.js +1 -1
- package/build/core/projection-kill.js +50 -17
- package/build/core/projection-life-observer.js +1 -1
- package/build/core/projection-life-protocol.js +10 -0
- package/build/core/projection-mint.js +1 -1
- package/build/core/projection-status.js +45 -32
- package/build/core/projection-wait.js +12 -2
- package/build/core/projection-wake.js +5 -2
- package/build/core/{queue_v2.js → queue.js} +2 -1
- package/build/core/registry.js +1 -1
- package/build/core/renew.js +7 -7
- package/build/core/response-artifact-catalog-sqlite.js +112 -0
- package/build/core/response-artifact-catalog.js +7 -0
- package/build/core/seal.js +44 -16
- package/build/core/status/board.js +286 -0
- package/build/core/status/drift.js +99 -0
- package/build/core/status/lifecycle.js +84 -0
- package/build/core/status/reconciliation.js +131 -0
- package/build/core/stored-agent-event.js +28 -4
- package/build/core/target-ref.js +10 -0
- package/build/core/task/board.js +175 -0
- package/build/core/task/commands.js +211 -0
- package/build/core/task/document.js +189 -0
- package/build/core/task/model.js +1 -0
- package/build/core/task/settlement-git.js +207 -0
- package/build/core/task/settlement-policy.js +82 -0
- package/build/core/task/source-board.js +84 -0
- package/build/core/task/source-model.js +1 -0
- package/build/core/task/validation.js +44 -0
- package/build/core/task-contract.js +224 -0
- package/build/core/task-doctor.js +14 -0
- package/build/core/task-git-actor.js +38 -0
- package/build/core/task-git-runtime.js +34 -0
- package/build/core/task-git-store.js +375 -0
- package/build/core/task-repository.js +1 -0
- package/build/core/task-store-repository.js +86 -0
- package/build/core/task-store.js +1 -0
- package/build/core/task-worktree.js +51 -0
- package/build/core/task.js +4 -0
- package/build/core/transcripts.js +138 -56
- package/build/core/validation.js +17 -0
- package/build/core/verdict.js +2 -2
- package/build/core/worktree-path.js +24 -6
- package/build/flow-error.js +3 -2
- package/build/generated/version.js +1 -1
- package/build/git/commits.js +3 -0
- package/build/git/diff/parsers.js +0 -94
- package/build/index.js +2 -0
- package/build/keiyaku-constants.js +2 -0
- package/build/keiyaku.js +4 -3
- package/package.json +3 -2
- package/skills/keiyaku/SKILL.md +1 -1
- package/skills/keiyaku-akuma/SKILL.md +11 -6
- package/build/agents/harness/pump.js +0 -158
- package/build/agents/providers/codex-app-server.js +0 -864
- package/build/agents/providers/opencode-sdk.js +0 -363
- package/build/agents/providers/pi.js +0 -557
- package/build/config/settings.js +0 -533
- package/build/core/call.js +0 -686
- package/build/core/projection/generation.js +0 -412
- package/build/core/projection/identity.js +0 -81
- package/build/core/projection/mint.js +0 -174
- package/build/core/projection-generation-store.js +0 -707
- package/build/core/status.js +0 -539
package/build/core/status.js
DELETED
|
@@ -1,539 +0,0 @@
|
|
|
1
|
-
import * as fs from "node:fs";
|
|
2
|
-
import * as path from "node:path";
|
|
3
|
-
import { assertSettingsKnobUsable, loadKeiyakuSettings, } from "../config/settings.js";
|
|
4
|
-
import { FlowError } from "../flow-error.js";
|
|
5
|
-
import { SETTINGS_FILE } from "../keiyaku.js";
|
|
6
|
-
import { isGitRepo } from "../git/branches.js";
|
|
7
|
-
import { createGit, errorContainsAnyPattern, MISSING_HEAD_PATTERNS, wrapGitError } from "../git/core.js";
|
|
8
|
-
import { fullBranchRefSchema } from "./entry.js";
|
|
9
|
-
import { listContractIds, readLedger } from "./ledger.js";
|
|
10
|
-
import { readProjectionStatusBoard } from "./projection-status.js";
|
|
11
|
-
import { deriveQueueReadModel, queueMembership, } from "./queue_v2.js";
|
|
12
|
-
import { findOpenArc, getMaxArc, receiptsAfterFence } from "./seal.js";
|
|
13
|
-
import { stableRepoRoot } from "./worktree-path.js";
|
|
14
|
-
/** Prefix bare branch names and validate the full ref. Sole status/bind normalizer. */
|
|
15
|
-
export function normalizeTargetRef(raw) {
|
|
16
|
-
const ref = raw.startsWith("refs/heads/") ? raw : `refs/heads/${raw}`;
|
|
17
|
-
if (!fullBranchRefSchema.safeParse(ref).success) {
|
|
18
|
-
throw new FlowError("INVALID_TARGET", `invalid target ref '${raw}' — pass a branch name or refs/heads/<name>`);
|
|
19
|
-
}
|
|
20
|
-
return ref;
|
|
21
|
-
}
|
|
22
|
-
function transitionFromEntry(current, entry) {
|
|
23
|
-
if (current === "claimed" || current === "forfeited" || current === "corrupt") {
|
|
24
|
-
return "corrupt";
|
|
25
|
-
}
|
|
26
|
-
switch (entry.kind) {
|
|
27
|
-
case "bind":
|
|
28
|
-
return "corrupt";
|
|
29
|
-
case "bootstrap":
|
|
30
|
-
// Placement legality is owned exclusively by deriveContractState.
|
|
31
|
-
return "corrupt";
|
|
32
|
-
case "arc":
|
|
33
|
-
return current === "bound" || current === "active" ? "active" : "corrupt";
|
|
34
|
-
case "seal":
|
|
35
|
-
return current === "active" ? "active" : "corrupt";
|
|
36
|
-
case "amend":
|
|
37
|
-
return current === "petitioned" ? "active" : current;
|
|
38
|
-
case "renew":
|
|
39
|
-
return current;
|
|
40
|
-
case "verdict":
|
|
41
|
-
if (current !== "petitioned")
|
|
42
|
-
return "corrupt";
|
|
43
|
-
return entry.data.result === "rejected" ? "active" : "petitioned";
|
|
44
|
-
case "petition":
|
|
45
|
-
return current === "bound" || current === "active" ? "petitioned" : "corrupt";
|
|
46
|
-
case "claim":
|
|
47
|
-
return current === "petitioned" ? "claimed" : "corrupt";
|
|
48
|
-
case "forfeit":
|
|
49
|
-
return "forfeited";
|
|
50
|
-
}
|
|
51
|
-
return "corrupt";
|
|
52
|
-
}
|
|
53
|
-
export function deriveContractState(entries) {
|
|
54
|
-
if (entries.length === 0 || entries[0]?.kind !== "bind") {
|
|
55
|
-
return { state: "corrupt", at: 0, reason: "ledger must start with bind" };
|
|
56
|
-
}
|
|
57
|
-
const bind = entries[0];
|
|
58
|
-
let state = "bound";
|
|
59
|
-
for (let index = 1; index < entries.length; index += 1) {
|
|
60
|
-
const entry = entries[index];
|
|
61
|
-
if (entry.kind === "bootstrap") {
|
|
62
|
-
if (index !== 1 || state !== "bound") {
|
|
63
|
-
return { state: "corrupt", at: index + 1, reason: "bootstrap must immediately follow bind" };
|
|
64
|
-
}
|
|
65
|
-
if (!bind.data.bootstrap) {
|
|
66
|
-
return { state: "corrupt", at: index + 1, reason: "bootstrap without bind plan" };
|
|
67
|
-
}
|
|
68
|
-
// Legal bootstrap is a durable fact and leaves the derived state bound.
|
|
69
|
-
continue;
|
|
70
|
-
}
|
|
71
|
-
const next = transitionFromEntry(state, entry);
|
|
72
|
-
if (next === "corrupt") {
|
|
73
|
-
return { state: "corrupt", at: index + 1, reason: `invalid ${entry.kind} after ${state}` };
|
|
74
|
-
}
|
|
75
|
-
state = next;
|
|
76
|
-
}
|
|
77
|
-
return { state };
|
|
78
|
-
}
|
|
79
|
-
export function deriveBootstrapDiagnostics(entries) {
|
|
80
|
-
const bind = entries.find((entry) => entry.kind === "bind");
|
|
81
|
-
if (!bind?.data.bootstrap)
|
|
82
|
-
return [];
|
|
83
|
-
if (entries.some((entry) => entry.kind === "bootstrap"))
|
|
84
|
-
return [];
|
|
85
|
-
return [{ kind: "plan-without-result" }];
|
|
86
|
-
}
|
|
87
|
-
export function isTerminalState(state) {
|
|
88
|
-
return state.state === "claimed" || state.state === "forfeited";
|
|
89
|
-
}
|
|
90
|
-
function hasCurrentPetitionVerdict(entries) {
|
|
91
|
-
for (let index = entries.length - 1; index >= 0; index -= 1) {
|
|
92
|
-
const entry = entries[index];
|
|
93
|
-
if (entry.kind === "verdict")
|
|
94
|
-
return true;
|
|
95
|
-
if (entry.kind === "petition")
|
|
96
|
-
return false;
|
|
97
|
-
}
|
|
98
|
-
return false;
|
|
99
|
-
}
|
|
100
|
-
function isQueueHeadStall(input) {
|
|
101
|
-
return (input.queuePosition === 1 &&
|
|
102
|
-
input.queueLength > 1 &&
|
|
103
|
-
input.state.state === "petitioned" &&
|
|
104
|
-
!hasCurrentPetitionVerdict(input.entries));
|
|
105
|
-
}
|
|
106
|
-
export function latestBase(entries) {
|
|
107
|
-
for (let index = entries.length - 1; index >= 0; index -= 1) {
|
|
108
|
-
const entry = entries[index];
|
|
109
|
-
if (entry.kind === "renew")
|
|
110
|
-
return entry.data.newBase;
|
|
111
|
-
}
|
|
112
|
-
return entries.find((entry) => entry.kind === "bind")?.data.base;
|
|
113
|
-
}
|
|
114
|
-
/** Commission settlement target from the bind entry. Schema guarantees refs/heads/<name>. */
|
|
115
|
-
export function commissionTarget(entries) {
|
|
116
|
-
const bind = findBindEntry(entries);
|
|
117
|
-
if (!bind)
|
|
118
|
-
return undefined;
|
|
119
|
-
const ref = bind.data.target;
|
|
120
|
-
return {
|
|
121
|
-
ref,
|
|
122
|
-
branch: ref.slice("refs/heads/".length),
|
|
123
|
-
};
|
|
124
|
-
}
|
|
125
|
-
async function resolveCommit(cwd, ref) {
|
|
126
|
-
const git = createGit(cwd);
|
|
127
|
-
try {
|
|
128
|
-
return (await git.raw(["rev-parse", "--verify", `${ref}^{commit}`])).trim();
|
|
129
|
-
}
|
|
130
|
-
catch (error) {
|
|
131
|
-
if (errorContainsAnyPattern(error, MISSING_HEAD_PATTERNS))
|
|
132
|
-
return null;
|
|
133
|
-
throw wrapGitError(`rev-parse ${ref}`, error, cwd);
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
export async function readSettingsDiseases(cwd) {
|
|
137
|
-
const loaded = await loadKeiyakuSettings(cwd);
|
|
138
|
-
return loaded.diseases;
|
|
139
|
-
}
|
|
140
|
-
export async function readDefaultBranchOverride(cwd) {
|
|
141
|
-
const loaded = await loadKeiyakuSettings(cwd);
|
|
142
|
-
return loaded.knobs?.defaultBranch;
|
|
143
|
-
}
|
|
144
|
-
async function resolveDefaultBranchFromSettings(cwd, loaded, rejectDiseasedOverride) {
|
|
145
|
-
if (rejectDiseasedOverride)
|
|
146
|
-
assertSettingsKnobUsable(loaded, "defaultBranch");
|
|
147
|
-
const override = loaded.knobs?.defaultBranch;
|
|
148
|
-
if (override) {
|
|
149
|
-
const head = await resolveCommit(cwd, override);
|
|
150
|
-
if (!head)
|
|
151
|
-
throw new FlowError("INVALID_SETTINGS", `${SETTINGS_FILE} defaultBranch '${override}' does not resolve`);
|
|
152
|
-
return { branch: override, head };
|
|
153
|
-
}
|
|
154
|
-
const git = createGit(cwd);
|
|
155
|
-
for (const branch of ["main", "master"]) {
|
|
156
|
-
const head = await resolveCommit(cwd, branch);
|
|
157
|
-
if (head)
|
|
158
|
-
return { branch, head };
|
|
159
|
-
}
|
|
160
|
-
try {
|
|
161
|
-
const originHead = (await git.raw(["symbolic-ref", "--quiet", "--short", "refs/remotes/origin/HEAD"])).trim();
|
|
162
|
-
if (originHead) {
|
|
163
|
-
const head = await resolveCommit(cwd, originHead);
|
|
164
|
-
if (head)
|
|
165
|
-
return { branch: originHead, head };
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
catch {
|
|
169
|
-
// Fall through to local branch heuristics.
|
|
170
|
-
}
|
|
171
|
-
return null;
|
|
172
|
-
}
|
|
173
|
-
export async function resolveDefaultBranch(cwd) {
|
|
174
|
-
return resolveDefaultBranchFromSettings(cwd, await loadKeiyakuSettings(cwd), true);
|
|
175
|
-
}
|
|
176
|
-
export async function evaluateBaseDrift(cwd, base, defaultBranch) {
|
|
177
|
-
if (!defaultBranch)
|
|
178
|
-
return { state: "unknown", label: "n/a (no default branch)" };
|
|
179
|
-
if (base === defaultBranch.head) {
|
|
180
|
-
return { state: "green", defaultBranch: defaultBranch.branch, defaultHead: defaultBranch.head };
|
|
181
|
-
}
|
|
182
|
-
const git = createGit(cwd);
|
|
183
|
-
try {
|
|
184
|
-
await git.raw(["merge-base", "--is-ancestor", base, defaultBranch.head]);
|
|
185
|
-
const behindBy = Number.parseInt((await git.raw(["rev-list", "--count", `${base}..${defaultBranch.head}`])).trim(), 10);
|
|
186
|
-
return {
|
|
187
|
-
state: "drift",
|
|
188
|
-
defaultBranch: defaultBranch.branch,
|
|
189
|
-
defaultHead: defaultBranch.head,
|
|
190
|
-
behindBy: Number.isFinite(behindBy) ? behindBy : 0,
|
|
191
|
-
};
|
|
192
|
-
}
|
|
193
|
-
catch {
|
|
194
|
-
return { state: "orphaned", defaultBranch: defaultBranch.branch, defaultHead: defaultBranch.head };
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
function findBindEntry(entries) {
|
|
198
|
-
return entries.find((entry) => entry.kind === "bind");
|
|
199
|
-
}
|
|
200
|
-
function contractSlugFromId(id) {
|
|
201
|
-
const dot = id.indexOf(".");
|
|
202
|
-
if (dot > 0)
|
|
203
|
-
return id.slice(0, dot);
|
|
204
|
-
return id;
|
|
205
|
-
}
|
|
206
|
-
function deriveCurrentArc(entries, bindObjective) {
|
|
207
|
-
const arcObjectives = new Map();
|
|
208
|
-
let currentArc = null;
|
|
209
|
-
for (const entry of entries) {
|
|
210
|
-
if (entry.kind === "arc") {
|
|
211
|
-
arcObjectives.set(entry.data.arc, entry.data.objective);
|
|
212
|
-
currentArc = {
|
|
213
|
-
number: entry.data.arc,
|
|
214
|
-
objective: entry.data.objective,
|
|
215
|
-
state: "open",
|
|
216
|
-
};
|
|
217
|
-
continue;
|
|
218
|
-
}
|
|
219
|
-
if (entry.kind === "seal") {
|
|
220
|
-
currentArc = {
|
|
221
|
-
number: entry.data.arc,
|
|
222
|
-
objective: arcObjectives.get(entry.data.arc) ?? bindObjective ?? "",
|
|
223
|
-
state: "closed",
|
|
224
|
-
};
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
if (currentArc)
|
|
228
|
-
return currentArc;
|
|
229
|
-
return {
|
|
230
|
-
number: 0,
|
|
231
|
-
objective: bindObjective ?? "",
|
|
232
|
-
state: "closed",
|
|
233
|
-
};
|
|
234
|
-
}
|
|
235
|
-
function deriveNextStep(input) {
|
|
236
|
-
if (input.state.state === "corrupt") {
|
|
237
|
-
return { kind: "inspect-log", detail: "inspect log" };
|
|
238
|
-
}
|
|
239
|
-
const pendingAfter = input.after.filter((item) => !item.terminal).map((item) => contractSlugFromId(item.id));
|
|
240
|
-
if (pendingAfter.length > 0) {
|
|
241
|
-
return { kind: "wait-after", detail: `finish after ${pendingAfter.join(",")}` };
|
|
242
|
-
}
|
|
243
|
-
if (input.state.state === "petitioned") {
|
|
244
|
-
return { kind: "await-claim", detail: "await claim" };
|
|
245
|
-
}
|
|
246
|
-
if (input.baseDrift.state === "drift" || input.baseDrift.state === "orphaned") {
|
|
247
|
-
return { kind: "renew", detail: "renew" };
|
|
248
|
-
}
|
|
249
|
-
if (input.currentArc.state === "open") {
|
|
250
|
-
return { kind: "arc", detail: "arc" };
|
|
251
|
-
}
|
|
252
|
-
if (input.state.state === "active" && input.currentArc.number > 0) {
|
|
253
|
-
return { kind: "petition", detail: "petition" };
|
|
254
|
-
}
|
|
255
|
-
return { kind: "arc", detail: "arc" };
|
|
256
|
-
}
|
|
257
|
-
function deriveLastActiveAge(entries, now) {
|
|
258
|
-
const last = entries.at(-1);
|
|
259
|
-
if (!last)
|
|
260
|
-
return {};
|
|
261
|
-
const atMs = Date.parse(last.at);
|
|
262
|
-
if (!Number.isFinite(atMs)) {
|
|
263
|
-
return { lastActivityAt: last.at };
|
|
264
|
-
}
|
|
265
|
-
return {
|
|
266
|
-
lastActivityAt: last.at,
|
|
267
|
-
lastActiveMs: Math.max(0, now - atMs),
|
|
268
|
-
};
|
|
269
|
-
}
|
|
270
|
-
function isClaimReceipt(value) {
|
|
271
|
-
if (!value || typeof value !== "object")
|
|
272
|
-
return false;
|
|
273
|
-
const receipt = value;
|
|
274
|
-
return receipt.kind === "receipt" && [receipt.oldHead, receipt.newHead, receipt.contract].every((field) => typeof field === "string" && field.length > 0);
|
|
275
|
-
}
|
|
276
|
-
function isClaimReceiptBaseline(value) {
|
|
277
|
-
if (!value || typeof value !== "object")
|
|
278
|
-
return false;
|
|
279
|
-
const baseline = value;
|
|
280
|
-
if (baseline.kind !== "baseline" || typeof baseline.head !== "string" || typeof baseline.by !== "string" || baseline.by.length === 0) {
|
|
281
|
-
return false;
|
|
282
|
-
}
|
|
283
|
-
if (typeof baseline.establishedAt !== "string" || !baseline.establishedAt.endsWith("Z"))
|
|
284
|
-
return false;
|
|
285
|
-
const at = Date.parse(baseline.establishedAt);
|
|
286
|
-
return Number.isFinite(at) && new Date(at).toISOString() === baseline.establishedAt;
|
|
287
|
-
}
|
|
288
|
-
async function readClaimReceipts(cwd) {
|
|
289
|
-
const receiptsPath = path.join(await stableRepoRoot(cwd), ".keiyaku", "claim-receipts.jsonl");
|
|
290
|
-
try {
|
|
291
|
-
const content = fs.readFileSync(receiptsPath, "utf8");
|
|
292
|
-
return content
|
|
293
|
-
.split("\n")
|
|
294
|
-
.map((line, index) => ({ line, index: index + 1 }))
|
|
295
|
-
.filter(({ line }) => line.trim().length > 0)
|
|
296
|
-
.map(({ line, index }) => {
|
|
297
|
-
try {
|
|
298
|
-
const parsed = JSON.parse(line);
|
|
299
|
-
return isClaimReceipt(parsed) || isClaimReceiptBaseline(parsed)
|
|
300
|
-
? { line: index, record: parsed }
|
|
301
|
-
: { line: index, issue: { line: index, reason: "malformed" } };
|
|
302
|
-
}
|
|
303
|
-
catch {
|
|
304
|
-
return { line: index, issue: { line: index, reason: "malformed" } };
|
|
305
|
-
}
|
|
306
|
-
});
|
|
307
|
-
}
|
|
308
|
-
catch {
|
|
309
|
-
return [];
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
async function computeClaimReconciliation(cwd, defaultBranch) {
|
|
313
|
-
if (!defaultBranch)
|
|
314
|
-
return null;
|
|
315
|
-
const receiptLines = await readClaimReceipts(cwd);
|
|
316
|
-
if (receiptLines.length === 0)
|
|
317
|
-
return null;
|
|
318
|
-
const git = createGit(cwd);
|
|
319
|
-
const firstParentHistory = (await git.raw(["rev-list", "--first-parent", defaultBranch.head])).trim().split("\n").filter(Boolean);
|
|
320
|
-
const firstParentSet = new Set(firstParentHistory);
|
|
321
|
-
let baselineIndex = -1;
|
|
322
|
-
for (let index = receiptLines.length - 1; index >= 0; index -= 1) {
|
|
323
|
-
const record = receiptLines[index].record;
|
|
324
|
-
if (record?.kind === "baseline" && firstParentSet.has(record.head)) {
|
|
325
|
-
baselineIndex = index;
|
|
326
|
-
break;
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
if (baselineIndex < 0)
|
|
330
|
-
return null;
|
|
331
|
-
const baseline = receiptLines[baselineIndex].record;
|
|
332
|
-
const issues = receiptLines.slice(baselineIndex + 1).flatMap((line) => {
|
|
333
|
-
if (line.issue)
|
|
334
|
-
return [line.issue];
|
|
335
|
-
return line.record?.kind === "baseline" ? [{ line: line.line, reason: "non-chain" }] : [];
|
|
336
|
-
});
|
|
337
|
-
// Only records after the latest valid baseline belong to this fence window.
|
|
338
|
-
let revList;
|
|
339
|
-
try {
|
|
340
|
-
const output = await git.raw(["rev-list", "--first-parent", "--reverse", `${baseline.head}..${defaultBranch.head}`]);
|
|
341
|
-
revList = output.trim().split("\n").filter((h) => h.length > 0);
|
|
342
|
-
}
|
|
343
|
-
catch {
|
|
344
|
-
return {
|
|
345
|
-
defaultBranch: defaultBranch.branch,
|
|
346
|
-
head: defaultBranch.head,
|
|
347
|
-
unclaimedSegments: [],
|
|
348
|
-
receiptIssues: [...issues, ...receiptLines.slice(baselineIndex + 1).filter((line) => line.record?.kind === "receipt").map((line) => ({ line: line.line, reason: "non-chain" }))],
|
|
349
|
-
};
|
|
350
|
-
}
|
|
351
|
-
const firstParent = new Map();
|
|
352
|
-
let parent = baseline.head;
|
|
353
|
-
for (const commit of revList) {
|
|
354
|
-
firstParent.set(commit, parent);
|
|
355
|
-
parent = commit;
|
|
356
|
-
}
|
|
357
|
-
const indexByCommit = new Map(revList.map((commit, index) => [commit, index]));
|
|
358
|
-
const claimedHeads = new Set();
|
|
359
|
-
let previousReceiptIndex = -1;
|
|
360
|
-
for (const line of receiptLines.slice(baselineIndex + 1)) {
|
|
361
|
-
if (line.record?.kind !== "receipt")
|
|
362
|
-
continue;
|
|
363
|
-
const index = indexByCommit.get(line.record.newHead);
|
|
364
|
-
if (index === undefined || index <= previousReceiptIndex || firstParent.get(line.record.newHead) !== line.record.oldHead) {
|
|
365
|
-
issues.push({ line: line.line, reason: "non-chain" });
|
|
366
|
-
continue;
|
|
367
|
-
}
|
|
368
|
-
previousReceiptIndex = index;
|
|
369
|
-
claimedHeads.add(line.record.newHead);
|
|
370
|
-
}
|
|
371
|
-
// Walk default-branch first-parent edges and group only movement not covered
|
|
372
|
-
// by a valid receipt. These are facts, not a judgment about their source.
|
|
373
|
-
const segments = [];
|
|
374
|
-
let segmentStart = null;
|
|
375
|
-
let segmentCount = 0;
|
|
376
|
-
for (const commit of revList) {
|
|
377
|
-
if (claimedHeads.has(commit)) {
|
|
378
|
-
if (segmentStart !== null) {
|
|
379
|
-
segments.push({ oldHead: segmentStart, newHead: firstParent.get(commit), commitCount: segmentCount });
|
|
380
|
-
segmentStart = null;
|
|
381
|
-
segmentCount = 0;
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
else {
|
|
385
|
-
if (segmentStart === null) {
|
|
386
|
-
segmentStart = firstParent.get(commit);
|
|
387
|
-
}
|
|
388
|
-
segmentCount++;
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
// Trailing unclaimed segment up to HEAD
|
|
392
|
-
if (segmentStart !== null) {
|
|
393
|
-
segments.push({ oldHead: segmentStart, newHead: defaultBranch.head, commitCount: segmentCount });
|
|
394
|
-
}
|
|
395
|
-
return { defaultBranch: defaultBranch.branch, head: defaultBranch.head, unclaimedSegments: segments, receiptIssues: issues };
|
|
396
|
-
}
|
|
397
|
-
export async function readKanshiBoard(cwd, now, opts) {
|
|
398
|
-
const targetFilter = opts?.target ?? null;
|
|
399
|
-
const loadedSettings = await loadKeiyakuSettings(cwd);
|
|
400
|
-
if (!(await isGitRepo(cwd))) {
|
|
401
|
-
return {
|
|
402
|
-
contracts: [],
|
|
403
|
-
projections: null,
|
|
404
|
-
queueLength: 0,
|
|
405
|
-
diseases: loadedSettings.diseases,
|
|
406
|
-
claimReconciliation: null,
|
|
407
|
-
targetFilter,
|
|
408
|
-
};
|
|
409
|
-
}
|
|
410
|
-
// Status is a pure reader: use the same resolver, but retain its ability to
|
|
411
|
-
// open when the defaultBranch knob itself is diseased.
|
|
412
|
-
let defaultBranch = null;
|
|
413
|
-
try {
|
|
414
|
-
defaultBranch = await resolveDefaultBranchFromSettings(cwd, loadedSettings, false);
|
|
415
|
-
}
|
|
416
|
-
catch {
|
|
417
|
-
defaultBranch = null;
|
|
418
|
-
}
|
|
419
|
-
const contractIds = await listContractIds(cwd);
|
|
420
|
-
const terminalById = new Map();
|
|
421
|
-
const ledgers = [];
|
|
422
|
-
const readableLedgers = [];
|
|
423
|
-
for (const id of contractIds) {
|
|
424
|
-
try {
|
|
425
|
-
const ledger = await readLedger(cwd, id);
|
|
426
|
-
if (ledger) {
|
|
427
|
-
ledgers.push(ledger);
|
|
428
|
-
readableLedgers.push(ledger);
|
|
429
|
-
terminalById.set(id, isTerminalState(deriveContractState(ledger.entries)));
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
catch (error) {
|
|
433
|
-
ledgers.push({ id, error: error instanceof Error ? error : new Error(String(error)) });
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
const queueModel = deriveQueueReadModel(readableLedgers);
|
|
437
|
-
const boardQueueLength = queueModel.targetQueues.reduce((sum, queue) => sum + queue.ordered.length, 0);
|
|
438
|
-
const contracts = [];
|
|
439
|
-
// Readable-ledger commission targets retained for post-assembly filter only.
|
|
440
|
-
const derivedTargetById = new Map();
|
|
441
|
-
for (const item of ledgers) {
|
|
442
|
-
if ("error" in item) {
|
|
443
|
-
// Unreadable ledgers have no derivable target and stay under every filter.
|
|
444
|
-
derivedTargetById.set(item.id, undefined);
|
|
445
|
-
contracts.push({
|
|
446
|
-
id: item.id,
|
|
447
|
-
slug: contractSlugFromId(item.id),
|
|
448
|
-
state: { state: "corrupt", at: 0, reason: item.error.message },
|
|
449
|
-
queueHeadStalled: false,
|
|
450
|
-
arcs: 0,
|
|
451
|
-
currentArc: { number: 0, objective: "", state: "closed" },
|
|
452
|
-
nextStep: { kind: "inspect-log", detail: "inspect log" },
|
|
453
|
-
baseDrift: { state: "unknown", label: "n/a (corrupt ledger)" },
|
|
454
|
-
after: [],
|
|
455
|
-
corruptReason: item.error.message,
|
|
456
|
-
bootstrapDiagnostics: [],
|
|
457
|
-
queueDiagnostics: [],
|
|
458
|
-
});
|
|
459
|
-
continue;
|
|
460
|
-
}
|
|
461
|
-
const bind = findBindEntry(item.entries);
|
|
462
|
-
derivedTargetById.set(item.contractId, commissionTarget(item.entries)?.ref);
|
|
463
|
-
const base = latestBase(item.entries);
|
|
464
|
-
const state = deriveContractState(item.entries);
|
|
465
|
-
if (isTerminalState(state))
|
|
466
|
-
continue;
|
|
467
|
-
const after = (bind?.data.after ?? []).map((id) => ({ id, terminal: terminalById.get(id) ?? false }));
|
|
468
|
-
const currentArc = deriveCurrentArc(item.entries, bind?.data.objective);
|
|
469
|
-
const baseDrift = base
|
|
470
|
-
? await evaluateBaseDrift(cwd, base, defaultBranch)
|
|
471
|
-
: { state: "unknown", label: "n/a (no default branch)" };
|
|
472
|
-
const nextStep = deriveNextStep({
|
|
473
|
-
id: item.contractId,
|
|
474
|
-
state,
|
|
475
|
-
currentArc,
|
|
476
|
-
baseDrift,
|
|
477
|
-
after,
|
|
478
|
-
});
|
|
479
|
-
if (nextStep.kind === "renew" && baseDrift.state === "drift") {
|
|
480
|
-
const openArc = findOpenArc(item.entries);
|
|
481
|
-
let receiptCount = 0;
|
|
482
|
-
if (openArc) {
|
|
483
|
-
try {
|
|
484
|
-
receiptCount = (await receiptsAfterFence(cwd, item.entries, `keiyaku/${item.contractId}`)).length;
|
|
485
|
-
}
|
|
486
|
-
catch {
|
|
487
|
-
// A ledger-only here contract may not have a delivery ref yet.
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
const sealForecast = openArc ? `seals a${openArc.data.arc} (${receiptCount} receipts), ` : "";
|
|
491
|
-
nextStep.hint = `run: keiyaku @${item.contractId} renew — ${sealForecast}follows ${baseDrift.defaultBranch} ▲${baseDrift.behindBy}`;
|
|
492
|
-
}
|
|
493
|
-
const membership = queueMembership(queueModel, item.contractId);
|
|
494
|
-
const queuePosition = membership?.position ?? null;
|
|
495
|
-
const targetQueueLength = membership?.queueLength ?? 0;
|
|
496
|
-
contracts.push({
|
|
497
|
-
id: item.contractId,
|
|
498
|
-
slug: contractSlugFromId(item.contractId),
|
|
499
|
-
state,
|
|
500
|
-
queuePosition,
|
|
501
|
-
queueHeadStalled: isQueueHeadStall({
|
|
502
|
-
entries: item.entries,
|
|
503
|
-
queuePosition,
|
|
504
|
-
queueLength: targetQueueLength,
|
|
505
|
-
state,
|
|
506
|
-
}),
|
|
507
|
-
arcs: getMaxArc(item.entries),
|
|
508
|
-
currentArc,
|
|
509
|
-
nextStep,
|
|
510
|
-
...deriveLastActiveAge(item.entries, now),
|
|
511
|
-
place: bind?.data.place ?? null,
|
|
512
|
-
base,
|
|
513
|
-
baseDrift,
|
|
514
|
-
after,
|
|
515
|
-
bootstrapDiagnostics: deriveBootstrapDiagnostics(item.entries),
|
|
516
|
-
queueDiagnostics: membership ? [...membership.diagnostics] : [],
|
|
517
|
-
});
|
|
518
|
-
}
|
|
519
|
-
const filteredContracts = targetFilter === null
|
|
520
|
-
? contracts
|
|
521
|
-
: contracts.filter((contract) => {
|
|
522
|
-
const derived = derivedTargetById.get(contract.id);
|
|
523
|
-
return derived === undefined || derived === targetFilter;
|
|
524
|
-
});
|
|
525
|
-
const queueLength = targetFilter === null
|
|
526
|
-
? boardQueueLength
|
|
527
|
-
: (queueModel.targetQueues.find((queue) => queue.target === targetFilter)?.ordered.length ?? 0);
|
|
528
|
-
const claimReconciliation = await computeClaimReconciliation(cwd, defaultBranch);
|
|
529
|
-
return {
|
|
530
|
-
contracts: filteredContracts,
|
|
531
|
-
projections: readProjectionStatusBoard(cwd, now, { showAll: opts?.showAllProjections }),
|
|
532
|
-
queueLength,
|
|
533
|
-
defaultBranch: defaultBranch?.branch,
|
|
534
|
-
defaultHead: defaultBranch?.head,
|
|
535
|
-
diseases: loadedSettings.diseases,
|
|
536
|
-
claimReconciliation,
|
|
537
|
-
targetFilter,
|
|
538
|
-
};
|
|
539
|
-
}
|