@astrosheep/keiyaku 2.9.2 → 2.9.4
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/call-terms.js +13 -94
- package/build/agents/harness/activity-values.js +16 -0
- package/build/agents/harness/event-persistence.js +218 -0
- package/build/agents/harness/execution-handle.js +25 -222
- package/build/agents/harness/index.js +3 -1
- package/build/agents/harness/outcome.js +47 -4
- package/build/agents/harness/projection.js +42 -59
- package/build/agents/harness/provider-adapter.js +58 -0
- package/build/agents/harness/router.js +11 -187
- package/build/agents/harness/runtime.js +17 -4
- package/build/agents/harness/session.js +15 -0
- package/build/agents/launch-snapshot/model.js +15 -236
- package/build/agents/launch-snapshot/parse-helpers.js +114 -0
- package/build/agents/launch-snapshot/resolve.js +17 -43
- package/build/agents/opencode-sdk.js +45 -12
- package/build/agents/providers/claude-agent-sdk/adapter.js +6 -17
- package/build/agents/providers/claude-agent-sdk/registration.js +214 -0
- package/build/agents/providers/claude-agent-sdk/session-schema.js +2 -0
- package/build/agents/providers/codex-app-server/adapter.js +50 -53
- package/build/agents/providers/codex-app-server/events.js +67 -11
- package/build/agents/providers/codex-app-server/identity.js +1 -0
- package/build/agents/providers/codex-app-server/policy.js +29 -0
- package/build/agents/providers/codex-app-server/registration.js +248 -0
- package/build/agents/providers/codex-app-server/session-schema.js +6 -0
- package/build/agents/providers/codex-app-server/session.js +23 -17
- package/build/agents/providers/codex-app-server/terms.js +1 -0
- package/build/agents/providers/opencode-sdk/adapter.js +94 -50
- package/build/agents/providers/opencode-sdk/events.js +8 -4
- package/build/agents/providers/opencode-sdk/registration.js +135 -0
- package/build/agents/providers/opencode-sdk/session-schema.js +2 -0
- package/build/agents/providers/opencode-sdk/session.js +21 -14
- package/build/agents/providers/opencode-sdk/terms.js +1 -0
- package/build/agents/providers/pi/adapter.js +36 -30
- package/build/agents/providers/pi/registration.js +145 -0
- package/build/agents/providers/pi/session-schema.js +6 -0
- package/build/agents/providers/pi/terms.js +1 -0
- package/build/agents/providers/registration.js +7 -0
- package/build/agents/providers/registry.js +76 -0
- package/build/agents/selector.js +2 -29
- package/build/agents/types.js +8 -0
- package/build/artifact-directories.js +22 -0
- package/build/cli/commands/akuma/akuma/handler.js +1 -1
- package/build/cli/commands/akuma/view/handler.js +5 -4
- package/build/cli/commands/akuma.js +6 -0
- package/build/cli/commands/contract/amend/handler.js +8 -2
- package/build/cli/commands/contract/arc/handler.js +7 -1
- package/build/cli/commands/contract/bind/handler.js +4 -3
- package/build/cli/commands/contract/bind/meta.js +34 -7
- package/build/cli/commands/contract/forfeit/handler.js +4 -4
- package/build/cli/commands/contract/log/handler.js +3 -3
- package/build/cli/commands/contract/petition/handler.js +4 -6
- package/build/cli/commands/contract/renew/handler.js +20 -6
- package/build/cli/commands/contract/renew/meta.js +5 -4
- package/build/cli/commands/metadata.js +58 -0
- package/build/cli/commands/projection/call/handler.js +6 -5
- package/build/cli/commands/projection/kill/handler.js +16 -32
- package/build/cli/commands/projection/shared.js +28 -0
- package/build/cli/commands/projection/status/handler.js +1 -1
- package/build/cli/commands/projection/tell/handler.js +14 -30
- package/build/cli/commands/projection/wait/handler.js +45 -22
- package/build/cli/commands/projection/wait/meta.js +7 -2
- package/build/cli/commands/registry.js +2 -54
- package/build/cli/commands/shared.js +10 -7
- package/build/cli/commands/skills/install/handler.js +2 -5
- package/build/cli/commands/skills/skills/handler.js +1 -1
- package/build/cli/commands/task/add/handler.js +5 -13
- package/build/cli/commands/task/catalog.js +2 -2
- package/build/cli/commands/task/done/handler.js +4 -8
- package/build/cli/commands/task/drop/handler.js +3 -6
- package/build/cli/commands/task/ls/handler.js +3 -5
- package/build/cli/commands/task/shared.js +14 -4
- package/build/cli/commands/task/{view → show}/handler.js +7 -8
- package/build/cli/commands/task/show/meta.js +8 -0
- package/build/cli/commands/task/start/handler.js +3 -6
- package/build/cli/commands/task/stop/handler.js +4 -8
- package/build/cli/commands/task/task/handler.js +1 -1
- package/build/cli/commands/task/update/handler.js +4 -8
- package/build/cli/completion.js +1 -1
- package/build/cli/flags.js +19 -3
- package/build/cli/help.js +4 -11
- package/build/cli/index.js +57 -83
- package/build/cli/missing-subcommand.js +7 -0
- package/build/cli/parse-flags.js +14 -5
- package/build/cli/parse-metadata.js +3 -3
- package/build/cli/parse-selectors.js +1 -1
- package/build/cli/parse.js +21 -0
- package/build/cli/projection-address.js +7 -12
- package/build/cli/render/address.js +41 -37
- package/build/cli/render/arc.js +24 -12
- package/build/cli/render/call.js +11 -5
- package/build/cli/render/errors.js +36 -1
- package/build/cli/render/format.js +4 -0
- package/build/cli/render/kanshi.js +6 -12
- package/build/cli/render/kill.js +9 -0
- package/build/cli/render/misc.js +55 -12
- package/build/cli/render/petition.js +20 -15
- package/build/cli/render/projection-activity.js +3 -0
- package/build/cli/render/shared.js +60 -21
- package/build/cli/render/skills.js +10 -0
- package/build/cli/render/status.js +37 -14
- package/build/cli/render/success-response.js +77 -0
- package/build/cli/render/task.js +54 -14
- package/build/cli/render/tell.js +5 -0
- package/build/cli/render/terminal-failure.js +58 -0
- package/build/cli/render/types.js +1 -0
- package/build/cli/render/wait.js +38 -36
- package/build/cli/types.js +1 -1
- package/build/config/akuma-loader.js +0 -5
- package/build/config/env-keys.js +1 -1
- package/build/config/provider-policy-ownership.js +3 -32
- package/build/config/settings/disease.js +8 -1
- package/build/config/settings/loader.js +11 -3
- package/build/config/settings/provider-instance-fields.js +51 -0
- package/build/config/settings/schema.js +6 -176
- package/build/core/addressing.js +66 -16
- package/build/core/amend.js +135 -18
- package/build/core/arc.js +19 -67
- package/build/core/bind.js +82 -35
- package/build/core/call/call.js +15 -9
- package/build/core/call/context.js +6 -51
- package/build/core/call/execution.js +21 -22
- package/build/core/call/prompt.js +14 -1
- package/build/core/command-markdown.js +45 -0
- package/build/core/context.js +13 -7
- package/build/core/contract-view.js +20 -3
- package/build/core/draft.js +123 -16
- package/build/core/forfeit.js +5 -2
- package/build/core/hints.js +1 -75
- package/build/core/identifier-slug.js +66 -0
- package/build/core/ids.js +25 -11
- package/build/core/ledger.js +20 -68
- package/build/core/markdown/parser.js +0 -68
- package/build/core/markdown/render.js +68 -0
- package/build/core/markdown/sections.js +2 -1
- package/build/core/outcome-base.js +4 -0
- package/build/core/projection/generation/database.js +2 -38
- package/build/core/projection/generation/model.js +42 -1
- package/build/core/{projection-generation-continuation.js → projection/generation/projection-generation-continuation.js} +3 -3
- package/build/core/{projection-generation-execution.js → projection/generation/projection-generation-execution.js} +19 -49
- package/build/core/{projection-generation-identity.js → projection/generation/projection-generation-identity.js} +2 -2
- package/build/core/{projection-generation-launcher.js → projection/generation/projection-generation-launcher.js} +5 -3
- package/build/core/{projection-generation-process.js → projection/generation/projection-generation-process.js} +2 -1
- package/build/core/{projection-generation-runner.js → projection/generation/projection-generation-runner.js} +18 -3
- package/build/core/projection/generation/store.js +2 -2
- package/build/core/projection/generation/transitions.js +1 -1
- package/build/core/projection/index.js +27 -0
- package/build/core/{projection-activity.js → projection/projection-activity.js} +12 -0
- package/build/core/{projection-alias.js → projection/projection-alias.js} +14 -19
- package/build/core/projection/projection-coordinate.js +61 -0
- package/build/core/{projection-core.js → projection/projection-core.js} +9 -9
- package/build/core/{projection-execution-observer.js → projection/projection-execution-observer.js} +2 -57
- package/build/core/{projection-identity.js → projection/projection-identity.js} +1 -1
- package/build/core/{projection-kill.js → projection/projection-kill.js} +1 -1
- package/build/core/{projection-life-observer.js → projection/projection-life-observer.js} +1 -1
- package/build/core/{projection-mint.js → projection/projection-mint.js} +10 -10
- package/build/core/{projection-status.js → projection/projection-status.js} +98 -25
- package/build/core/projection/projection-terminal-failure.js +104 -0
- package/build/core/projection/projection-wait.js +278 -0
- package/build/core/projection/projection-wake.js +203 -0
- package/build/core/provider-loader-ports.js +4 -0
- package/build/core/ref-log.js +65 -40
- package/build/core/registry.js +63 -10
- package/build/core/renew-session.js +128 -0
- package/build/core/renew.js +426 -99
- package/build/core/scope.js +143 -66
- package/build/core/seal.js +139 -35
- package/build/core/{claim-delivery.js → settlement/claim-delivery.js} +9 -14
- package/build/core/{claim.js → settlement/claim.js} +32 -8
- package/build/core/settlement/index.js +19 -0
- package/build/core/{petition-claim-gates.js → settlement/petition-claim-gates.js} +22 -18
- package/build/core/{petition-forfeit.js → settlement/petition-forfeit.js} +2 -2
- package/build/core/{petition-head-guard.js → settlement/petition-head-guard.js} +2 -2
- package/build/core/settlement/petition-io.js +1 -0
- package/build/core/{petition-preview.js → settlement/petition-preview.js} +1 -1
- package/build/core/{petition.js → settlement/petition.js} +12 -10
- package/build/core/settlement/queue-read-model.js +143 -0
- package/build/core/settlement/queue-seat-allocation.js +156 -0
- package/build/core/settlement/queue.js +6 -0
- package/build/core/settlement/settlement.js +295 -0
- package/build/core/{verdict.js → settlement/verdict.js} +9 -37
- package/build/core/status/board.js +24 -23
- package/build/core/status/ledger-batch.js +158 -0
- package/build/core/status/reconciliation.js +10 -4
- package/build/core/stored-agent-event.js +3 -0
- package/build/core/task/board.js +40 -4
- package/build/core/task/commands.js +30 -21
- package/build/core/task/index.js +12 -0
- package/build/core/task/settlement-git.js +157 -65
- package/build/core/task/settlement-policy.js +54 -24
- package/build/core/task/task-contract.js +370 -0
- package/build/core/{task-doctor.js → task/task-doctor.js} +1 -1
- package/build/core/{task-git-actor.js → task/task-git-actor.js} +5 -10
- package/build/core/{task-git-runtime.js → task/task-git-runtime.js} +3 -3
- package/build/core/{task-git-store.js → task/task-git-store.js} +92 -29
- package/build/core/task/task-repository.js +1 -0
- package/build/core/{task-store-repository.js → task/task-store-repository.js} +5 -4
- package/build/core/task/task-store.js +1 -0
- package/build/core/{task-worktree.js → task/task-worktree.js} +2 -1
- package/build/core/task/task.js +5 -0
- package/build/core/task/tree.js +37 -0
- package/build/core/task/validation.js +5 -2
- package/build/core/transcripts.js +15 -3
- package/build/core/worktree-path.js +43 -28
- package/build/flow-error.js +85 -39
- package/build/fs/durability.js +27 -0
- package/build/fs/path-coordinate.js +16 -0
- package/build/generated/version.js +3 -2
- package/build/git/branches.js +2 -1
- package/build/git/core.js +106 -2
- package/build/git/path-coordinate.js +17 -0
- package/build/git/refs.js +57 -0
- package/build/git/streaming-batch.js +301 -0
- package/build/git/worktree.js +65 -0
- package/build/index.js +3 -3
- package/build/keiyaku.js +2 -36
- package/package.json +4 -10
- package/skills/keiyaku/SKILL.md +3 -2
- package/skills/keiyaku-akuma/SKILL.md +10 -6
- package/skills/keiyaku-task/SKILL.md +1 -1
- package/skills/keiyaku-workflow/SKILL.md +25 -4
- package/build/agents/providers/codex-sdk.js +0 -329
- package/build/cli/commands/task/view/meta.js +0 -8
- package/build/core/command-io.js +0 -174
- package/build/core/path-coordinate.js +0 -27
- package/build/core/petition-claim.js +0 -117
- package/build/core/petition-run.js +0 -83
- package/build/core/projection-coordinate.js +0 -63
- package/build/core/projection-wait.js +0 -165
- package/build/core/projection-wake.js +0 -136
- package/build/core/queue.js +0 -343
- package/build/core/task-contract.js +0 -237
- package/build/core/task.js +0 -4
- /package/build/{core/task-repository.js → agents/launch-snapshot/types.js} +0 -0
- /package/build/{core/task-store.js → agents/providers/claude-agent-sdk/terms.js} +0 -0
- /package/build/core/{projection-generation-runtime.js → projection/generation/projection-generation-runtime.js} +0 -0
- /package/build/core/{projection-life-protocol.js → projection/projection-life-protocol.js} +0 -0
- /package/build/core/{projection-runner-lock.js → projection/projection-runner-lock.js} +0 -0
package/build/core/context.js
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
import * as path from "node:path";
|
|
2
1
|
import { KEIYAKU_BRANCH_PREFIX } from "../keiyaku.js";
|
|
3
2
|
import { FlowError } from "../flow-error.js";
|
|
4
|
-
import { createGit, errorContainsAnyPattern, MISSING_HEAD_PATTERNS, wrapGitError } from "../git/core.js";
|
|
3
|
+
import { createGit, errorContainsAnyPattern, MISSING_HEAD_PATTERNS, NOT_GIT_REPOSITORY_PATTERNS, wrapGitError } from "../git/core.js";
|
|
5
4
|
import { getCurrentBranch, listLocalBranches } from "../git/branches.js";
|
|
6
5
|
import { readLedger } from "./ledger.js";
|
|
7
6
|
import { listContractIds } from "./ledger.js";
|
|
8
7
|
import { deriveContractState, isTerminalState } from "./status/lifecycle.js";
|
|
9
8
|
import { latestBase } from "./status/drift.js";
|
|
10
|
-
import {
|
|
9
|
+
import { activeContractBoundByEngineWorktree } from "./worktree-path.js";
|
|
11
10
|
function isOperableLedger(entries) {
|
|
12
11
|
const state = deriveContractState(entries);
|
|
13
12
|
return state.state !== "corrupt" && !isTerminalState(state);
|
|
@@ -25,6 +24,17 @@ async function isOperableContract(cwd, contractId) {
|
|
|
25
24
|
return (await operableContractIds(cwd)).includes(contractId);
|
|
26
25
|
}
|
|
27
26
|
export async function resolveImplicitCurrentContractId(cwd) {
|
|
27
|
+
// Prefer the verified engine-worktree binding (same tier as existing-commission verbs).
|
|
28
|
+
try {
|
|
29
|
+
const bound = await activeContractBoundByEngineWorktree(cwd);
|
|
30
|
+
if (bound)
|
|
31
|
+
return bound;
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
if (!errorContainsAnyPattern(error, NOT_GIT_REPOSITORY_PATTERNS))
|
|
35
|
+
throw error;
|
|
36
|
+
}
|
|
37
|
+
// Branch remains a residual signal for workspace:here checkouts without an engine worktree.
|
|
28
38
|
const git = createGit(cwd);
|
|
29
39
|
try {
|
|
30
40
|
const branch = (await git.raw(["branch", "--show-current"])).trim();
|
|
@@ -39,10 +49,6 @@ export async function resolveImplicitCurrentContractId(cwd) {
|
|
|
39
49
|
throw wrapGitError("branch --show-current", error, cwd);
|
|
40
50
|
}
|
|
41
51
|
}
|
|
42
|
-
const worktreeId = path.basename(cwd);
|
|
43
|
-
const worktreeContractId = await activeContractIdForWorktreeDir(cwd, worktreeId);
|
|
44
|
-
if (worktreeContractId)
|
|
45
|
-
return worktreeContractId;
|
|
46
52
|
return undefined;
|
|
47
53
|
}
|
|
48
54
|
export async function resolveCurrentContractId(cwd) {
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
+
import * as fs from "node:fs/promises";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { FlowError } from "../flow-error.js";
|
|
1
4
|
import { shortCommitHash } from "../git/commits.js";
|
|
5
|
+
import { KEIYAKU_FILE } from "../keiyaku.js";
|
|
6
|
+
import { readLedger } from "./ledger.js";
|
|
2
7
|
import { deriveContractState } from "./status/lifecycle.js";
|
|
3
8
|
const VERB_COLUMN_WIDTH = 11;
|
|
4
9
|
const TIME_COLUMN_WIDTH = 6;
|
|
@@ -208,6 +213,18 @@ export function renderVerticalLog(input) {
|
|
|
208
213
|
}
|
|
209
214
|
return renderLedgerRows(input.entries).join("\n");
|
|
210
215
|
}
|
|
216
|
+
/**
|
|
217
|
+
* Sole post-mutation KEIYAKU.md refresh: read the authoritative ledger at
|
|
218
|
+
* ledgerCwd, then write the projection at renderCwd. ledgerCwd and renderCwd
|
|
219
|
+
* are distinct coordinates (hub vs linked delivery worktree).
|
|
220
|
+
*/
|
|
221
|
+
export async function refreshContractView(input) {
|
|
222
|
+
const ledger = await readLedger(input.ledgerCwd, input.contractId);
|
|
223
|
+
if (!ledger) {
|
|
224
|
+
throw new FlowError("INTERNAL_STATE", `contract ${input.contractId} active ledger disappeared before render`);
|
|
225
|
+
}
|
|
226
|
+
await fs.writeFile(path.join(input.renderCwd, KEIYAKU_FILE), renderContractView({ id: input.contractId, entries: ledger.entries }), "utf8");
|
|
227
|
+
}
|
|
211
228
|
export function renderContractView(input) {
|
|
212
229
|
const bind = input.entries.find((entry) => entry.kind === "bind");
|
|
213
230
|
const state = deriveContractState(input.entries);
|
|
@@ -237,13 +254,13 @@ export function renderContractView(input) {
|
|
|
237
254
|
`place: ${bind.data.place ?? "(none)"}`,
|
|
238
255
|
`after: ${(bind.data.after ?? []).join(", ") || "(none)"}`,
|
|
239
256
|
"",
|
|
240
|
-
"
|
|
257
|
+
"## Objective",
|
|
241
258
|
bind.data.objective,
|
|
242
259
|
"",
|
|
243
|
-
"
|
|
260
|
+
"## Scope",
|
|
244
261
|
...bind.data.scope.map((pattern) => `- ${pattern}`),
|
|
245
262
|
"",
|
|
246
|
-
"
|
|
263
|
+
"## Checks",
|
|
247
264
|
...bind.data.checks.map((check) => `- ${check}`),
|
|
248
265
|
"",
|
|
249
266
|
...(bind.data.extensions.length > 0
|
package/build/core/draft.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { FlowError } from "../flow-error.js";
|
|
2
|
-
import {
|
|
2
|
+
import { parseToAST } from "./markdown/parser.js";
|
|
3
|
+
import { extractListItems, renderSectionContent } from "./markdown/render.js";
|
|
3
4
|
import { normalizeScopePattern } from "./scope.js";
|
|
4
5
|
const KNOWN_SECTIONS = new Set(["objective", "scope", "checks"]);
|
|
6
|
+
const BIND_DRAFT_SECTION_ORDER = ["name", "objective", "scope", "checks"];
|
|
5
7
|
function invalidBindDraft(message) {
|
|
6
8
|
throw new FlowError("INVALID_KEIYAKU_DRAFT", `INVALID_BIND_DRAFT: ${message}`);
|
|
7
9
|
}
|
|
@@ -71,7 +73,7 @@ function extractSingleLineChecks(section) {
|
|
|
71
73
|
return trimmed;
|
|
72
74
|
});
|
|
73
75
|
}
|
|
74
|
-
|
|
76
|
+
function collectBindDraftNodes(content) {
|
|
75
77
|
const ast = parseToAST(content);
|
|
76
78
|
let name;
|
|
77
79
|
let seenH1 = false;
|
|
@@ -103,6 +105,124 @@ export function parseBindDraft(content) {
|
|
|
103
105
|
content: normalizeExtensionContent(renderSectionContent(node)),
|
|
104
106
|
});
|
|
105
107
|
}
|
|
108
|
+
return { ...(name === undefined ? {} : { name }), sections, extensions };
|
|
109
|
+
}
|
|
110
|
+
function parseScopeSection(scope) {
|
|
111
|
+
const unexpectedScopeLine = findUnexpectedListOnlyLine(scope);
|
|
112
|
+
if (unexpectedScopeLine) {
|
|
113
|
+
invalidBindDraft(`Scope must be a bullet list; found '${unexpectedScopeLine}'`);
|
|
114
|
+
}
|
|
115
|
+
const scopeItems = extractListItems(scope);
|
|
116
|
+
if (scopeItems.length === 0) {
|
|
117
|
+
invalidBindDraft("Scope must be a bullet list; found empty Scope section");
|
|
118
|
+
}
|
|
119
|
+
return scopeItems.map((item) => {
|
|
120
|
+
if (item.trim().length === 0) {
|
|
121
|
+
invalidBindDraft("empty scope pattern in bind draft");
|
|
122
|
+
}
|
|
123
|
+
return normalizeScopePattern(item);
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Parse any subset of the bind document grammar. Absent sections stay undefined
|
|
128
|
+
* so callers can merge task defaults under stdin overrides.
|
|
129
|
+
*/
|
|
130
|
+
export function parsePartialBindDraft(content) {
|
|
131
|
+
if (content.trim().length === 0)
|
|
132
|
+
return {};
|
|
133
|
+
const { name, sections, extensions } = collectBindDraftNodes(content);
|
|
134
|
+
const partial = {};
|
|
135
|
+
if (name !== undefined)
|
|
136
|
+
partial.name = name;
|
|
137
|
+
const objective = sections.get("objective");
|
|
138
|
+
if (objective) {
|
|
139
|
+
const objectiveText = renderSectionContent(objective).trim();
|
|
140
|
+
if (!objectiveText)
|
|
141
|
+
invalidBindDraft("Objective section cannot be empty");
|
|
142
|
+
partial.objective = objectiveText;
|
|
143
|
+
}
|
|
144
|
+
const scope = sections.get("scope");
|
|
145
|
+
if (scope)
|
|
146
|
+
partial.scope = parseScopeSection(scope);
|
|
147
|
+
const checks = sections.get("checks");
|
|
148
|
+
if (checks)
|
|
149
|
+
partial.checks = extractSingleLineChecks(checks);
|
|
150
|
+
if (extensions.length > 0)
|
|
151
|
+
partial.extensions = extensions;
|
|
152
|
+
return partial;
|
|
153
|
+
}
|
|
154
|
+
export function mergeBindDraft(base, override) {
|
|
155
|
+
return {
|
|
156
|
+
...(base.name !== undefined ? { name: base.name } : {}),
|
|
157
|
+
...(base.objective !== undefined ? { objective: base.objective } : {}),
|
|
158
|
+
...(base.scope !== undefined ? { scope: base.scope } : {}),
|
|
159
|
+
...(base.checks !== undefined ? { checks: base.checks } : {}),
|
|
160
|
+
...(base.extensions !== undefined ? { extensions: base.extensions } : {}),
|
|
161
|
+
...(override.name !== undefined ? { name: override.name } : {}),
|
|
162
|
+
...(override.objective !== undefined ? { objective: override.objective } : {}),
|
|
163
|
+
...(override.scope !== undefined ? { scope: override.scope } : {}),
|
|
164
|
+
...(override.checks !== undefined ? { checks: override.checks } : {}),
|
|
165
|
+
...(override.extensions !== undefined ? { extensions: override.extensions } : {}),
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
export function missingBindDraftSections(partial) {
|
|
169
|
+
const missing = [];
|
|
170
|
+
if (partial.name === undefined || partial.name.trim().length === 0)
|
|
171
|
+
missing.push("name");
|
|
172
|
+
if (partial.objective === undefined || partial.objective.trim().length === 0)
|
|
173
|
+
missing.push("objective");
|
|
174
|
+
if (partial.scope === undefined || partial.scope.length === 0)
|
|
175
|
+
missing.push("scope");
|
|
176
|
+
if (partial.checks === undefined || partial.checks.length === 0)
|
|
177
|
+
missing.push("checks");
|
|
178
|
+
return missing;
|
|
179
|
+
}
|
|
180
|
+
/** Paste-ready partial markdown for only the missing required sections. */
|
|
181
|
+
export function renderMissingBindSectionsTemplate(missing) {
|
|
182
|
+
const lines = [];
|
|
183
|
+
for (const section of BIND_DRAFT_SECTION_ORDER) {
|
|
184
|
+
if (!missing.includes(section))
|
|
185
|
+
continue;
|
|
186
|
+
if (section === "name") {
|
|
187
|
+
lines.push("# <name>");
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
if (section === "objective") {
|
|
191
|
+
lines.push("## Objective", "<what the commission should accomplish>");
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
if (section === "scope") {
|
|
195
|
+
lines.push("## Scope", "- <pattern>");
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
lines.push("## Checks", "- <verification>");
|
|
199
|
+
}
|
|
200
|
+
return lines.join("\n");
|
|
201
|
+
}
|
|
202
|
+
export function describeMissingBindSections(missing) {
|
|
203
|
+
if (missing.length === 0)
|
|
204
|
+
return "";
|
|
205
|
+
if (missing.length === 1)
|
|
206
|
+
return missing[0];
|
|
207
|
+
if (missing.length === 2)
|
|
208
|
+
return `${missing[0]} or ${missing[1]}`;
|
|
209
|
+
return `${missing.slice(0, -1).join(", ")}, or ${missing[missing.length - 1]}`;
|
|
210
|
+
}
|
|
211
|
+
export function materializeBindDraft(partial) {
|
|
212
|
+
const missing = missingBindDraftSections(partial);
|
|
213
|
+
if (missing.length > 0) {
|
|
214
|
+
invalidBindDraft(`missing ${describeMissingBindSections(missing)} in bind draft`);
|
|
215
|
+
}
|
|
216
|
+
return {
|
|
217
|
+
name: partial.name.trim(),
|
|
218
|
+
objective: partial.objective.trim(),
|
|
219
|
+
scope: partial.scope,
|
|
220
|
+
checks: partial.checks,
|
|
221
|
+
extensions: partial.extensions ?? [],
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
export function parseBindDraft(content) {
|
|
225
|
+
const { name, sections, extensions } = collectBindDraftNodes(content);
|
|
106
226
|
if (!name) {
|
|
107
227
|
invalidBindDraft("missing H1 in bind draft; expected '# <name>' followed by '## Objective', '## Scope', and '## Checks'");
|
|
108
228
|
}
|
|
@@ -118,23 +238,10 @@ export function parseBindDraft(content) {
|
|
|
118
238
|
const objectiveText = renderSectionContent(objective).trim();
|
|
119
239
|
if (!objectiveText)
|
|
120
240
|
invalidBindDraft("Objective section cannot be empty");
|
|
121
|
-
const unexpectedScopeLine = findUnexpectedListOnlyLine(scope);
|
|
122
|
-
if (unexpectedScopeLine) {
|
|
123
|
-
invalidBindDraft(`Scope must be a bullet list; found '${unexpectedScopeLine}'`);
|
|
124
|
-
}
|
|
125
|
-
const scopeItems = extractListItems(scope);
|
|
126
|
-
if (scopeItems.length === 0) {
|
|
127
|
-
invalidBindDraft("Scope must be a bullet list; found empty Scope section");
|
|
128
|
-
}
|
|
129
241
|
return {
|
|
130
242
|
name,
|
|
131
243
|
objective: objectiveText,
|
|
132
|
-
scope:
|
|
133
|
-
if (item.trim().length === 0) {
|
|
134
|
-
invalidBindDraft("empty scope pattern in bind draft");
|
|
135
|
-
}
|
|
136
|
-
return normalizeScopePattern(item);
|
|
137
|
-
}),
|
|
244
|
+
scope: parseScopeSection(scope),
|
|
138
245
|
checks: extractSingleLineChecks(checks),
|
|
139
246
|
extensions,
|
|
140
247
|
};
|
package/build/core/forfeit.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { FlowError } from "../flow-error.js";
|
|
2
|
+
import { assertCleanTrackedWorkingTree } from "../git/worktree.js";
|
|
2
3
|
import { activeLedgerRef, appendWithRetry, readLedger } from "./ledger.js";
|
|
3
4
|
import { releaseScope } from "./registry.js";
|
|
4
5
|
import { deriveContractState, isTerminalState } from "./status/lifecycle.js";
|
|
5
|
-
import { reconcileTaskForfeit } from "./task
|
|
6
|
-
import { cleanupContractWorkspace, stableRepoRoot } from "./worktree-path.js";
|
|
6
|
+
import { reconcileTaskForfeit } from "./task/index.js";
|
|
7
|
+
import { cleanupContractWorkspace, findContractWorktreePath, stableRepoRoot } from "./worktree-path.js";
|
|
7
8
|
async function cleanupForfeit(cwd, contractId, actor, at) {
|
|
8
9
|
const stableRoot = await stableRepoRoot(cwd);
|
|
9
10
|
await releaseScope(stableRoot, { contractId, actor, at });
|
|
@@ -12,6 +13,8 @@ async function cleanupForfeit(cwd, contractId, actor, at) {
|
|
|
12
13
|
export async function forfeitContract(input, deps = {}) {
|
|
13
14
|
const at = new Date(input.nowMs).toISOString();
|
|
14
15
|
const stableRoot = await stableRepoRoot(input.cwd);
|
|
16
|
+
const deliveryWorktree = await findContractWorktreePath(stableRoot, input.contractId);
|
|
17
|
+
await assertCleanTrackedWorkingTree(deliveryWorktree ?? stableRoot);
|
|
15
18
|
const ledger = await readLedger(stableRoot, input.contractId);
|
|
16
19
|
if (ledger) {
|
|
17
20
|
const state = deriveContractState(ledger.entries);
|
package/build/core/hints.js
CHANGED
|
@@ -1,63 +1,6 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { ACTOR_IDENTITY, ACTOR_IDENTITY_PLURAL, BASE_RULES_FILE, KEIYAKU_ARTIFACT_DIRS, KEIYAKU_DRAFT_DIR, KEIYAKU_FILE, RESPONSE_HISTORY_DIR, SETTINGS_FILE, TOOL_NAMES, formatArtifactDirectories, } from "../keiyaku.js";
|
|
1
|
+
import { TOOL_NAMES } from "../keiyaku.js";
|
|
3
2
|
import { renderTemplate } from "../flow-error.js";
|
|
4
3
|
const GUIDANCE_BRANCH_LIST_LIMIT = 3;
|
|
5
|
-
const HELP_BODY_TEMPLATE = [
|
|
6
|
-
"## Workflow",
|
|
7
|
-
"`bind -> arc -> [call | amend | delivery work]* -> arc | renew | petition`",
|
|
8
|
-
"",
|
|
9
|
-
`- **\${bind}**: Create the active contract ledger and workspace files from Markdown stdin. The worktree must be clean; call it once per Keiyaku.`,
|
|
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
|
-
`- **\${call}**: Call a ${ACTOR_IDENTITY} for bounded work. An open arc supplies scoped context; the caller still owns review, integration, and commits.`,
|
|
12
|
-
`- **akuma ls / akuma view**: Inspect available ${ACTOR_IDENTITY_PLURAL} and their resolved provider/executable before choosing \`keiyaku call NAME\`.`,
|
|
13
|
-
"- **delivery commits**: Commit the actual code or documentation changes in git before sealing them.",
|
|
14
|
-
`- **\${renew}**: Capture receipts, update delivery onto the default branch, seal the open arc, and record the new base.`,
|
|
15
|
-
`- **\${amend}**: Update the Keiyaku when scope, rules, criteria, or intent changed.`,
|
|
16
|
-
"- **petition**: Run the gated petition -> verdict -> engine-claim settlement pipeline.",
|
|
17
|
-
"- **claim**: Engine-only tombstone; use `keiyaku petition`.",
|
|
18
|
-
"- **forfeit**: Mark an addressed contract as forfeited, optionally with `--reason TEXT`.",
|
|
19
|
-
"- **status**: Render the Kanshi board: 現世 head, arc sparkline, age, drift ▲N, and queue stall.",
|
|
20
|
-
"",
|
|
21
|
-
"## Command Boundaries",
|
|
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
|
-
"- `akuma ls` and `akuma view <name>` are read-only inspection commands; they do not start a helper.",
|
|
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
|
-
"",
|
|
29
|
-
"## Core Files (.keiyaku/)",
|
|
30
|
-
"Detailed configuration and protocol nuances live here.",
|
|
31
|
-
"",
|
|
32
|
-
`- **${path.posix.basename(BASE_RULES_FILE)}**: Mandatory architectural boundaries and coding standards.`,
|
|
33
|
-
" - **Purpose**: Global rules injected into every mission.",
|
|
34
|
-
`- **${path.posix.basename(SETTINGS_FILE)}**: Local JSON configuration at \`${SETTINGS_FILE}\`.`,
|
|
35
|
-
" - **Shape**: Supports `default` (agent name), `reviewer` (agent name), `gate` (gated commands), and `defaultBranch` (base branch override) at the top level.",
|
|
36
|
-
" - **Use Cases**: Configure defaults, petition gates, and base branch override.",
|
|
37
|
-
"- **.env**: Local environment defaults at `.keiyaku/.env`.",
|
|
38
|
-
" - **Format**: Standard dotenv-style `KEY=value` entries.",
|
|
39
|
-
" - **Precedence**: Real process environment variables override `.keiyaku/.env`.",
|
|
40
|
-
" - **Template**: Run `keiyaku dump-env` to print the full env template with defaults.",
|
|
41
|
-
"",
|
|
42
|
-
"## Rules Protocol",
|
|
43
|
-
"- **Raw Markdown**: Project rules are preserved as authored; use any valid Markdown structure.",
|
|
44
|
-
"- **No Header Flattening**: Headings stay as headings instead of being rewritten into prefixed list labels.",
|
|
45
|
-
`- **Preserve Authoring**: Formatting is kept intact aside from safe heading-level demotion when sections are rendered into \`${KEIYAKU_FILE}\`.`,
|
|
46
|
-
`- **Strict Rule**: Keep section-level structure in \`${KEIYAKU_FILE}\` intact (\`##\` headings define sections).`,
|
|
47
|
-
"",
|
|
48
|
-
"## Draft Recovery",
|
|
49
|
-
`- Failed \${bind} runs may leave timestamped drafts under \`${KEIYAKU_DRAFT_DIR}/\` for retry.`,
|
|
50
|
-
`- Reuse a saved draft by passing its path to \`\${bind}\` with \`from_draft\`.`,
|
|
51
|
-
`- \`claim\` preserves drafts; Keiyaku never stages or commits files under \`${KEIYAKU_DRAFT_DIR}/\` during arcs.`,
|
|
52
|
-
"",
|
|
53
|
-
"## Runtime Artifacts",
|
|
54
|
-
`- Response history is written under \`${RESPONSE_HISTORY_DIR}/\`; draft retries live under \`${KEIYAKU_DRAFT_DIR}/\`.`,
|
|
55
|
-
"- Add these directories to `.gitignore` if you do not want tracked artifacts blocking operations.",
|
|
56
|
-
"",
|
|
57
|
-
"## Workflow Tactics",
|
|
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 ${call} or ${bind} to pick up scoped follow-on work.",
|
|
60
|
-
];
|
|
61
4
|
const ARCHITECT_TOOL_NAMES = {
|
|
62
5
|
bind: TOOL_NAMES.bind,
|
|
63
6
|
arc: TOOL_NAMES.arc,
|
|
@@ -66,9 +9,6 @@ const ARCHITECT_TOOL_NAMES = {
|
|
|
66
9
|
amend: TOOL_NAMES.amend,
|
|
67
10
|
close: TOOL_NAMES.close,
|
|
68
11
|
};
|
|
69
|
-
function renderArchitectText(template) {
|
|
70
|
-
return template.map((line) => renderTemplate(line, ARCHITECT_TOOL_NAMES));
|
|
71
|
-
}
|
|
72
12
|
export function buildNoActiveKeiyakuGuidance(input) {
|
|
73
13
|
const targetToolName = ARCHITECT_TOOL_NAMES[input.toolName];
|
|
74
14
|
const message = `Current workspace '${input.currentBranch}' is not addressing an active Keiyaku contract.`;
|
|
@@ -92,17 +32,3 @@ export function buildNoActiveKeiyakuGuidance(input) {
|
|
|
92
32
|
],
|
|
93
33
|
};
|
|
94
34
|
}
|
|
95
|
-
export function buildGuideBody(version) {
|
|
96
|
-
const body = renderArchitectText(HELP_BODY_TEMPLATE).join("\n");
|
|
97
|
-
return [`# Keiyaku Guide (v${version})`, "", body].join("\n");
|
|
98
|
-
}
|
|
99
|
-
export function buildDirtyWorktreeArtifactHint() {
|
|
100
|
-
return `Files under \`.keiyaku/\` are blocking the operation. Add ${formatArtifactDirectories(KEIYAKU_ARTIFACT_DIRS)} to \`.gitignore\` if you do not want those artifacts tracked.`;
|
|
101
|
-
}
|
|
102
|
-
export function buildVerificationFailureHints(label, stdout, stderr) {
|
|
103
|
-
return [
|
|
104
|
-
`Fix failures from '${label}', then retry CLAIM.`,
|
|
105
|
-
`stdout:\n${stdout}`,
|
|
106
|
-
`stderr:\n${stderr}`,
|
|
107
|
-
];
|
|
108
|
-
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
const SLUG_CHARACTER = /[\p{Letter}\p{Number}\p{Mark}]/u;
|
|
2
|
+
const SLUG_LETTER_OR_NUMBER = /[\p{Letter}\p{Number}]/u;
|
|
3
|
+
const WINDOWS_RESERVED_BASENAME = /^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])$/u;
|
|
4
|
+
/**
|
|
5
|
+
* Leaves room in a portable 255-byte filename component for identity prefixes,
|
|
6
|
+
* collision ordinals, extensions, and temporary-write bookkeeping.
|
|
7
|
+
*/
|
|
8
|
+
export const IDENTIFIER_SLUG_MAX_UTF8_BYTES = 120;
|
|
9
|
+
/** Reserves `-` plus every decimal digit of a safe-integer collision ordinal. */
|
|
10
|
+
export const SUFFIXABLE_IDENTIFIER_SLUG_MAX_UTF8_BYTES = IDENTIFIER_SLUG_MAX_UTF8_BYTES - 17;
|
|
11
|
+
export class IdentifierSlugError extends Error {
|
|
12
|
+
code = "INVALID_IDENTIFIER_SLUG";
|
|
13
|
+
constructor(message, options) {
|
|
14
|
+
super(message, options);
|
|
15
|
+
this.name = "IdentifierSlugError";
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function utf8Bytes(value) {
|
|
19
|
+
return Buffer.byteLength(value, "utf8");
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Normalize human text into one Unicode-safe, portable identifier slug.
|
|
23
|
+
* NFKC makes canonically and compatibly equivalent input deterministic. All
|
|
24
|
+
* non-letter/number/mark runs become one ASCII hyphen, then the result is
|
|
25
|
+
* truncated at a Unicode code-point boundary to the shared UTF-8 byte bound.
|
|
26
|
+
*/
|
|
27
|
+
export function normalizeIdentifierSlug(value, maxUtf8Bytes = IDENTIFIER_SLUG_MAX_UTF8_BYTES) {
|
|
28
|
+
if (!Number.isSafeInteger(maxUtf8Bytes) || maxUtf8Bytes < 1 || maxUtf8Bytes > IDENTIFIER_SLUG_MAX_UTF8_BYTES) {
|
|
29
|
+
throw new IdentifierSlugError(`slug byte bound must be from 1 through ${IDENTIFIER_SLUG_MAX_UTF8_BYTES}`);
|
|
30
|
+
}
|
|
31
|
+
let slug = "";
|
|
32
|
+
let pendingSeparator = false;
|
|
33
|
+
for (const character of value.normalize("NFKC").toLowerCase()) {
|
|
34
|
+
if (SLUG_CHARACTER.test(character)) {
|
|
35
|
+
const addition = `${pendingSeparator && slug ? "-" : ""}${character}`;
|
|
36
|
+
if (utf8Bytes(slug) + utf8Bytes(addition) > maxUtf8Bytes)
|
|
37
|
+
break;
|
|
38
|
+
slug += addition;
|
|
39
|
+
pendingSeparator = false;
|
|
40
|
+
}
|
|
41
|
+
else if (slug) {
|
|
42
|
+
pendingSeparator = true;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (!slug || !SLUG_LETTER_OR_NUMBER.test(slug)) {
|
|
46
|
+
throw new IdentifierSlugError("text cannot be converted to a portable identifier slug; use at least one Unicode letter or number");
|
|
47
|
+
}
|
|
48
|
+
if (WINDOWS_RESERVED_BASENAME.test(slug)) {
|
|
49
|
+
const portable = `${slug}-id`;
|
|
50
|
+
if (utf8Bytes(portable) > maxUtf8Bytes) {
|
|
51
|
+
throw new IdentifierSlugError("text normalizes to a reserved Windows filename within the configured byte bound");
|
|
52
|
+
}
|
|
53
|
+
return portable;
|
|
54
|
+
}
|
|
55
|
+
return slug;
|
|
56
|
+
}
|
|
57
|
+
export function isNormalizedIdentifierSlug(value) {
|
|
58
|
+
if (!value || utf8Bytes(value) > IDENTIFIER_SLUG_MAX_UTF8_BYTES)
|
|
59
|
+
return false;
|
|
60
|
+
try {
|
|
61
|
+
return normalizeIdentifierSlug(value) === value;
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
}
|
package/build/core/ids.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { createGit, wrapGitError } from "../git/core.js";
|
|
2
|
+
import { IdentifierSlugError, isNormalizedIdentifierSlug, normalizeIdentifierSlug, } from "./identifier-slug.js";
|
|
2
3
|
const CROCKFORD = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
|
|
3
4
|
const ULID_RE = /^[0-9A-HJKMNP-TV-Z]{26}$/;
|
|
4
5
|
/** Canonical commission identity: slugified lowercase hyphen segments + `.` + Crockford ULID. */
|
|
5
|
-
export const COMMISSION_ID_RE = /^[
|
|
6
|
+
export const COMMISSION_ID_RE = /^([\p{Letter}\p{Number}\p{Mark}]+(?:-[\p{Letter}\p{Number}\p{Mark}]+)*)\.([0-9A-HJKMNP-TV-Z]{26})$/u;
|
|
6
7
|
/** Minted opaque repository identity: `repo_` + ULID. */
|
|
7
8
|
export function isCommissionId(value) {
|
|
8
|
-
|
|
9
|
+
const match = COMMISSION_ID_RE.exec(value);
|
|
10
|
+
return match !== null && isNormalizedIdentifierSlug(match[1]);
|
|
9
11
|
}
|
|
10
12
|
export class ContractIdError extends Error {
|
|
11
13
|
code = "INVALID_CONTRACT_ID";
|
|
@@ -15,16 +17,28 @@ export class ContractIdError extends Error {
|
|
|
15
17
|
}
|
|
16
18
|
}
|
|
17
19
|
export function slugifyContractName(name) {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
throw
|
|
20
|
+
try {
|
|
21
|
+
return normalizeIdentifierSlug(name);
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
if (error instanceof IdentifierSlugError) {
|
|
25
|
+
throw new ContractIdError(`contract name ${error.message}`, { cause: error });
|
|
26
|
+
}
|
|
27
|
+
throw error;
|
|
26
28
|
}
|
|
27
|
-
|
|
29
|
+
}
|
|
30
|
+
/** Human-facing slug from a commission ID (`<slug>.<ULID>`). Splits on the last `.`. */
|
|
31
|
+
export function commissionSlug(commissionId) {
|
|
32
|
+
const separator = commissionId.lastIndexOf(".");
|
|
33
|
+
return separator > 0 ? commissionId.slice(0, separator) : commissionId;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Human-facing contract coordinate: `<slug> @<place>` in worktree state, `<slug>` in here state.
|
|
37
|
+
* Full ULID never appears in this string.
|
|
38
|
+
*/
|
|
39
|
+
export function formatCommissionCoordinate(input) {
|
|
40
|
+
const slug = commissionSlug(input.commissionId);
|
|
41
|
+
return input.place && input.place !== slug ? `${slug} @${input.place}` : slug;
|
|
28
42
|
}
|
|
29
43
|
function encodeTime(nowMs) {
|
|
30
44
|
if (!Number.isSafeInteger(nowMs) || nowMs < 0 || nowMs > 0xffffffffffff) {
|
package/build/core/ledger.js
CHANGED
|
@@ -1,70 +1,34 @@
|
|
|
1
|
-
import { createGit,
|
|
1
|
+
import { createGit, wrapGitError, } from "../git/core.js";
|
|
2
2
|
import { formatLedgerEntryCommitMessage, parseLedgerEntryCommitMessage, } from "./entry.js";
|
|
3
|
-
|
|
4
|
-
const MISSING_REF_SENTINEL = "0".repeat(40);
|
|
3
|
+
import { casUpdateRefLog, createRefLogCommit, listRefLogCommits, readRefLogCommitMessage, resolveRefLogHead, } from "./ref-log.js";
|
|
5
4
|
export const ACTIVE_LEDGER_REF_PREFIX = "refs/keiyaku/active/";
|
|
6
5
|
export function activeLedgerRef(contractId) {
|
|
7
6
|
return `${ACTIVE_LEDGER_REF_PREFIX}${contractId}`;
|
|
8
7
|
}
|
|
9
|
-
async function
|
|
10
|
-
const git = createGit(cwd);
|
|
11
|
-
try {
|
|
12
|
-
const output = await git.raw(["rev-parse", "--verify", `${ref}^{commit}`]);
|
|
13
|
-
return output.trim();
|
|
14
|
-
}
|
|
15
|
-
catch (error) {
|
|
16
|
-
if (errorContainsAnyPattern(error, MISSING_HEAD_PATTERNS)) {
|
|
17
|
-
return null;
|
|
18
|
-
}
|
|
19
|
-
throw wrapGitError(`rev-parse ${ref}`, error, cwd);
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
async function createLedgerCommit(cwd, entry, parentHead) {
|
|
23
|
-
const git = createGit(cwd);
|
|
8
|
+
export async function appendEntry(cwd, ref, entry, expectedHead) {
|
|
24
9
|
const message = formatLedgerEntryCommitMessage(entry);
|
|
25
10
|
const separator = message.indexOf("\n\n");
|
|
26
11
|
const subject = message.slice(0, separator);
|
|
27
12
|
const body = message.slice(separator + 2).trimEnd();
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
catch (error) {
|
|
37
|
-
throw wrapGitError(`commit-tree ledger ${entry.kind} ${entry.contract}`, error, cwd);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
async function readCommitMessage(cwd, commit) {
|
|
41
|
-
const git = createGit(cwd);
|
|
42
|
-
try {
|
|
43
|
-
return await git.raw(["show", "-s", "--format=%B", commit]);
|
|
44
|
-
}
|
|
45
|
-
catch (error) {
|
|
46
|
-
throw wrapGitError(`show commit message ${commit}`, error, cwd);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
export async function appendEntry(cwd, ref, entry, expectedHead) {
|
|
50
|
-
const git = createGit(cwd);
|
|
51
|
-
const newHead = await createLedgerCommit(cwd, entry, expectedHead);
|
|
52
|
-
const oldValue = expectedHead ?? MISSING_REF_SENTINEL;
|
|
53
|
-
try {
|
|
54
|
-
await git.raw(["update-ref", ref, newHead, oldValue]);
|
|
13
|
+
const newHead = await createRefLogCommit(cwd, {
|
|
14
|
+
subject,
|
|
15
|
+
body,
|
|
16
|
+
parentHead: expectedHead,
|
|
17
|
+
errorLabel: `ledger ${entry.kind} ${entry.contract}`,
|
|
18
|
+
});
|
|
19
|
+
const casOk = await casUpdateRefLog(cwd, ref, newHead, expectedHead);
|
|
20
|
+
if (casOk)
|
|
55
21
|
return { ok: true, head: newHead };
|
|
22
|
+
// Intentional: CAS loss re-resolves and surfaces conflict/missing-ref without throw.
|
|
23
|
+
const currentHead = await resolveRefLogHead(cwd, ref);
|
|
24
|
+
if (currentHead === null) {
|
|
25
|
+
return { ok: false, reason: "missing-ref" };
|
|
56
26
|
}
|
|
57
|
-
|
|
58
|
-
const currentHead = await resolveRefHead(cwd, ref);
|
|
59
|
-
if (currentHead === null) {
|
|
60
|
-
return { ok: false, reason: "missing-ref" };
|
|
61
|
-
}
|
|
62
|
-
return { ok: false, reason: "conflict", currentHead };
|
|
63
|
-
}
|
|
27
|
+
return { ok: false, reason: "conflict", currentHead };
|
|
64
28
|
}
|
|
65
29
|
export async function appendWithRetry(cwd, ref, build, options = {}) {
|
|
66
30
|
const maxRetries = options.maxRetries ?? 3;
|
|
67
|
-
let currentHead = await
|
|
31
|
+
let currentHead = await resolveRefLogHead(cwd, ref);
|
|
68
32
|
const startedMissing = currentHead === null;
|
|
69
33
|
for (let attempt = 0; attempt <= maxRetries; attempt += 1) {
|
|
70
34
|
const currentEntries = currentHead ? (await readLedgerRef(cwd, ref, currentHead)).entries : [];
|
|
@@ -90,28 +54,16 @@ export async function appendWithRetry(cwd, ref, build, options = {}) {
|
|
|
90
54
|
return { ok: false, reason: "conflict", currentHead };
|
|
91
55
|
}
|
|
92
56
|
async function readLedgerRef(cwd, ref, head) {
|
|
93
|
-
const
|
|
94
|
-
let output;
|
|
95
|
-
try {
|
|
96
|
-
output = await git.raw(["rev-list", "--reverse", head]);
|
|
97
|
-
}
|
|
98
|
-
catch (error) {
|
|
99
|
-
throw wrapGitError(`rev-list ${ref}`, error, cwd);
|
|
100
|
-
}
|
|
101
|
-
const commits = output
|
|
102
|
-
.trim()
|
|
103
|
-
.split("\n")
|
|
104
|
-
.map((value) => value.trim())
|
|
105
|
-
.filter((value) => value.length > 0);
|
|
57
|
+
const commits = await listRefLogCommits(cwd, head, ref);
|
|
106
58
|
const entries = [];
|
|
107
59
|
for (const commit of commits) {
|
|
108
|
-
entries.push(parseLedgerEntryCommitMessage(await
|
|
60
|
+
entries.push(parseLedgerEntryCommitMessage(await readRefLogCommitMessage(cwd, commit)));
|
|
109
61
|
}
|
|
110
62
|
return { ref, head, entries };
|
|
111
63
|
}
|
|
112
64
|
export async function readLedger(cwd, contractId) {
|
|
113
65
|
const ref = activeLedgerRef(contractId);
|
|
114
|
-
const head = await
|
|
66
|
+
const head = await resolveRefLogHead(cwd, ref);
|
|
115
67
|
if (!head)
|
|
116
68
|
return null;
|
|
117
69
|
return { contractId, ...(await readLedgerRef(cwd, ref, head)) };
|