@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/renew.js
CHANGED
|
@@ -5,9 +5,10 @@ import { createGit, errorContainsAnyPattern, MISSING_HEAD_PATTERNS, wrapGitError
|
|
|
5
5
|
import { KEIYAKU_FILE } from "../keiyaku.js";
|
|
6
6
|
import { activeLedgerRef, appendWithRetry, readLedger } from "./ledger.js";
|
|
7
7
|
import { readProjectionStatusBoard } from "./projection-status.js";
|
|
8
|
-
import { renderContractView } from "./
|
|
9
|
-
import { captureSeal, findOpenArc, receiptsAfterFence,
|
|
10
|
-
import {
|
|
8
|
+
import { renderContractView } from "./contract-view.js";
|
|
9
|
+
import { captureSeal, findOpenArc, getMaxArc, receiptsAfterFence, sealCurrentArc } from "./seal.js";
|
|
10
|
+
import { deriveContractState, isTerminalState } from "./status/lifecycle.js";
|
|
11
|
+
import { commissionTarget, latestBase } from "./status/drift.js";
|
|
11
12
|
import { findContractWorktreePath } from "./worktree-path.js";
|
|
12
13
|
import { getCurrentBranch, isGitRepo } from "../git/branches.js";
|
|
13
14
|
import { resolveContractAddress, withResolvedAddress } from "./addressing.js";
|
|
@@ -101,9 +102,8 @@ export async function renewContract(input) {
|
|
|
101
102
|
throw new FlowError("INTERNAL_STATE", "ledger must contain bind entry");
|
|
102
103
|
const branch = `keiyaku/${input.contractId}`;
|
|
103
104
|
const oldHead = await resolveCommit(input.cwd, branch);
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
if (!open) {
|
|
105
|
+
const capture = await captureSeal(input.cwd, ledger.entries, oldHead, input.waivePaths);
|
|
106
|
+
if (getMaxArc(ledger.entries) > 0 && !findOpenArc(ledger.entries)) {
|
|
107
107
|
const loose = await receiptsAfterFence(input.cwd, ledger.entries, oldHead);
|
|
108
108
|
if (loose.length > 0)
|
|
109
109
|
throw looseCommitError(input.contractId, loose);
|
|
@@ -125,7 +125,7 @@ export async function renewContract(input) {
|
|
|
125
125
|
assertNoInFlightProjection(rewriteCwd, input.contractId, input.nowMs);
|
|
126
126
|
await rewriteBranch(rewriteCwd, targetHead, oldBase, worktreePath ? undefined : branch);
|
|
127
127
|
const newHead = await resolveCommit(input.cwd, branch);
|
|
128
|
-
const sealed = await
|
|
128
|
+
const sealed = await sealCurrentArc({
|
|
129
129
|
cwd: input.cwd,
|
|
130
130
|
renderCwd: rewriteCwd,
|
|
131
131
|
contractId: input.contractId,
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { DatabaseSync } from "node:sqlite";
|
|
4
|
+
const CATALOG_FILE = "catalog.db";
|
|
5
|
+
const CREATE_CATALOG_SQL = `
|
|
6
|
+
CREATE TABLE IF NOT EXISTS response_artifacts (
|
|
7
|
+
id TEXT PRIMARY KEY,
|
|
8
|
+
stable_root TEXT NOT NULL,
|
|
9
|
+
registered_at TEXT NOT NULL,
|
|
10
|
+
last_seen_at TEXT NOT NULL
|
|
11
|
+
) STRICT
|
|
12
|
+
`;
|
|
13
|
+
function recordFromRow(row) {
|
|
14
|
+
return {
|
|
15
|
+
id: row.id,
|
|
16
|
+
stableRoot: row.stable_root,
|
|
17
|
+
registeredAt: row.registered_at,
|
|
18
|
+
lastSeenAt: row.last_seen_at,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export function createSqliteResponseArtifactCatalog(input) {
|
|
22
|
+
const databasePath = path.join(path.resolve(input.keiyakuHome), CATALOG_FILE);
|
|
23
|
+
const timestamp = () => (input.now?.() ?? new Date()).toISOString();
|
|
24
|
+
const withWritableDatabase = (operation) => {
|
|
25
|
+
fs.mkdirSync(path.dirname(databasePath), { recursive: true });
|
|
26
|
+
const database = new DatabaseSync(databasePath);
|
|
27
|
+
try {
|
|
28
|
+
database.exec("PRAGMA busy_timeout = 5000");
|
|
29
|
+
database.exec(CREATE_CATALOG_SQL);
|
|
30
|
+
return operation(database);
|
|
31
|
+
}
|
|
32
|
+
finally {
|
|
33
|
+
database.close();
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
const withExistingDatabase = (fallback, operation) => {
|
|
37
|
+
if (!fs.existsSync(databasePath))
|
|
38
|
+
return fallback;
|
|
39
|
+
const database = new DatabaseSync(databasePath, { readOnly: true });
|
|
40
|
+
try {
|
|
41
|
+
database.exec("PRAGMA busy_timeout = 5000");
|
|
42
|
+
return operation(database);
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
if (error instanceof Error && /no such table/i.test(error.message))
|
|
46
|
+
return fallback;
|
|
47
|
+
throw error;
|
|
48
|
+
}
|
|
49
|
+
finally {
|
|
50
|
+
database.close();
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
const upsert = (coordinate, touchedAt) => {
|
|
54
|
+
withWritableDatabase((database) => {
|
|
55
|
+
database.prepare(`
|
|
56
|
+
INSERT INTO response_artifacts (id, stable_root, registered_at, last_seen_at)
|
|
57
|
+
VALUES (?, ?, ?, ?)
|
|
58
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
59
|
+
registered_at = CASE
|
|
60
|
+
WHEN response_artifacts.stable_root <> excluded.stable_root THEN excluded.registered_at
|
|
61
|
+
ELSE response_artifacts.registered_at
|
|
62
|
+
END,
|
|
63
|
+
stable_root = excluded.stable_root,
|
|
64
|
+
last_seen_at = excluded.last_seen_at
|
|
65
|
+
`).run(coordinate.id, path.resolve(coordinate.stableRoot), touchedAt, touchedAt);
|
|
66
|
+
});
|
|
67
|
+
};
|
|
68
|
+
const records = () => withExistingDatabase([], (database) => {
|
|
69
|
+
const rows = database.prepare(`
|
|
70
|
+
SELECT id, stable_root, registered_at, last_seen_at
|
|
71
|
+
FROM response_artifacts
|
|
72
|
+
ORDER BY id
|
|
73
|
+
`).all();
|
|
74
|
+
return rows.map(recordFromRow);
|
|
75
|
+
});
|
|
76
|
+
return {
|
|
77
|
+
register(coordinate) {
|
|
78
|
+
upsert(coordinate, timestamp());
|
|
79
|
+
},
|
|
80
|
+
touch(coordinate) {
|
|
81
|
+
upsert(coordinate, timestamp());
|
|
82
|
+
},
|
|
83
|
+
lookup(id) {
|
|
84
|
+
return withExistingDatabase(undefined, (database) => {
|
|
85
|
+
const row = database.prepare(`
|
|
86
|
+
SELECT id, stable_root, registered_at, last_seen_at
|
|
87
|
+
FROM response_artifacts
|
|
88
|
+
WHERE id = ?
|
|
89
|
+
`).get(id);
|
|
90
|
+
return row ? recordFromRow(row) : undefined;
|
|
91
|
+
});
|
|
92
|
+
},
|
|
93
|
+
removeDead(coordinate) {
|
|
94
|
+
if (!fs.existsSync(databasePath))
|
|
95
|
+
return;
|
|
96
|
+
withWritableDatabase((database) => {
|
|
97
|
+
database.prepare(`
|
|
98
|
+
DELETE FROM response_artifacts
|
|
99
|
+
WHERE id = ? AND stable_root = ?
|
|
100
|
+
`).run(coordinate.id, path.resolve(coordinate.stableRoot));
|
|
101
|
+
});
|
|
102
|
+
},
|
|
103
|
+
async diagnoseDead(exists) {
|
|
104
|
+
const result = [];
|
|
105
|
+
for (const record of records()) {
|
|
106
|
+
if (!(await exists(record)))
|
|
107
|
+
result.push(record);
|
|
108
|
+
}
|
|
109
|
+
return result;
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
}
|
package/build/core/seal.js
CHANGED
|
@@ -4,9 +4,9 @@ import { FlowError } from "../flow-error.js";
|
|
|
4
4
|
import { createGit, wrapGitError } from "../git/core.js";
|
|
5
5
|
import { KEIYAKU_FILE } from "../keiyaku.js";
|
|
6
6
|
import { activeLedgerRef, appendWithRetry, readLedger } from "./ledger.js";
|
|
7
|
-
import { renderContractView } from "./
|
|
7
|
+
import { renderContractView } from "./contract-view.js";
|
|
8
8
|
import { normalizeScopePath, pathsMatchingScope } from "./scope.js";
|
|
9
|
-
import { deriveContractState, isTerminalState } from "./status.js";
|
|
9
|
+
import { deriveContractState, isTerminalState } from "./status/lifecycle.js";
|
|
10
10
|
function findBind(entries) {
|
|
11
11
|
const bind = entries.find((entry) => entry.kind === "bind");
|
|
12
12
|
if (!bind)
|
|
@@ -116,10 +116,28 @@ function assertOperable(contractId, entries) {
|
|
|
116
116
|
throw new FlowError("INTERNAL_STATE", `contract ${contractId} cannot seal from state ${state.state}`);
|
|
117
117
|
}
|
|
118
118
|
}
|
|
119
|
-
|
|
119
|
+
/** Current seal intent: open explicit arc, else derived a0 while no explicit arc has ever existed. */
|
|
120
|
+
export function currentSealArc(entries) {
|
|
120
121
|
const open = findOpenArc(entries);
|
|
121
|
-
if (
|
|
122
|
-
return
|
|
122
|
+
if (open)
|
|
123
|
+
return open.data.arc;
|
|
124
|
+
if (getMaxArc(entries) === 0)
|
|
125
|
+
return 0;
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
/** Stable read-only coordinate naming the receipt source a petition would seal. */
|
|
129
|
+
export function petitionSealSource(entries) {
|
|
130
|
+
const arc = currentSealArc(entries);
|
|
131
|
+
const fence = getFence(entries);
|
|
132
|
+
return arc === null
|
|
133
|
+
? `no open arc · fence ${fence}`
|
|
134
|
+
: `a${arc} · fence ${fence}▸HEAD`;
|
|
135
|
+
}
|
|
136
|
+
/** Shared read-only owner for the seal capture and its compact changed-path evidence. */
|
|
137
|
+
export async function previewSeal(cwd, entries, head = "HEAD", waivePaths = []) {
|
|
138
|
+
const arc = currentSealArc(entries);
|
|
139
|
+
if (arc === null)
|
|
140
|
+
return { capture: null, paths: [] };
|
|
123
141
|
const oldFence = getFence(entries);
|
|
124
142
|
const newFence = await resolveCommit(cwd, head);
|
|
125
143
|
const [receipts, paths, stat] = await Promise.all([
|
|
@@ -132,17 +150,23 @@ export async function captureSeal(cwd, entries, head = "HEAD", waivePaths = [])
|
|
|
132
150
|
const waivedPaths = new Set(waivePaths.map(normalizeScopePath));
|
|
133
151
|
const unwaived = violations.filter((violation) => !waivedPaths.has(violation));
|
|
134
152
|
if (unwaived.length > 0) {
|
|
135
|
-
throw new FlowError("INTERNAL_STATE", `scope audit failed for contract ${
|
|
153
|
+
throw new FlowError("INTERNAL_STATE", `scope audit failed for contract ${findBind(entries).contract}: paths ${unwaived.join(", ")} outside ${scope.join(", ")}`);
|
|
136
154
|
}
|
|
137
155
|
return {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
156
|
+
capture: {
|
|
157
|
+
arc,
|
|
158
|
+
oldFence,
|
|
159
|
+
newFence,
|
|
160
|
+
receipts,
|
|
161
|
+
diffstat: stat,
|
|
162
|
+
scopeAudit: { violations, waived: violations.length > 0 },
|
|
163
|
+
},
|
|
164
|
+
paths,
|
|
144
165
|
};
|
|
145
166
|
}
|
|
167
|
+
export async function captureSeal(cwd, entries, head = "HEAD", waivePaths = []) {
|
|
168
|
+
return (await previewSeal(cwd, entries, head, waivePaths)).capture;
|
|
169
|
+
}
|
|
146
170
|
async function renderUpdatedContract(ledgerCwd, renderCwd, contractId) {
|
|
147
171
|
const ledger = await readLedger(ledgerCwd, contractId);
|
|
148
172
|
if (!ledger) {
|
|
@@ -150,7 +174,7 @@ async function renderUpdatedContract(ledgerCwd, renderCwd, contractId) {
|
|
|
150
174
|
}
|
|
151
175
|
await fs.writeFile(path.join(renderCwd, KEIYAKU_FILE), renderContractView({ id: contractId, entries: ledger.entries }), "utf8");
|
|
152
176
|
}
|
|
153
|
-
export async function
|
|
177
|
+
export async function sealCurrentArc(input) {
|
|
154
178
|
const ledger = await readLedger(input.cwd, input.contractId);
|
|
155
179
|
if (!ledger) {
|
|
156
180
|
throw new FlowError("INTERNAL_STATE", `contract ${input.contractId} not found or not active`);
|
|
@@ -162,10 +186,14 @@ export async function sealOpenArc(input) {
|
|
|
162
186
|
const at = new Date(input.nowMs).toISOString();
|
|
163
187
|
const result = await appendWithRetry(input.cwd, activeLedgerRef(input.contractId), (_head, entries) => {
|
|
164
188
|
assertOperable(input.contractId, entries);
|
|
165
|
-
const
|
|
166
|
-
if (
|
|
189
|
+
const currentArc = currentSealArc(entries);
|
|
190
|
+
if (currentArc === null)
|
|
167
191
|
return null;
|
|
168
|
-
|
|
192
|
+
// a0 is legal only while no explicit arc has ever existed; otherwise treat as concurrent change.
|
|
193
|
+
if (capture.arc === 0 && getMaxArc(entries) > 0) {
|
|
194
|
+
throw new FlowError("INTERNAL_STATE", `contract ${input.contractId} changed while sealing arc a${capture.arc}`);
|
|
195
|
+
}
|
|
196
|
+
if (currentArc !== capture.arc || getFence(entries) !== capture.oldFence) {
|
|
169
197
|
throw new FlowError("INTERNAL_STATE", `contract ${input.contractId} changed while sealing arc a${capture.arc}`);
|
|
170
198
|
}
|
|
171
199
|
return {
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
import { loadKeiyakuSettings } from "../../config/settings/loader.js";
|
|
2
|
+
import { isGitRepo } from "../../git/branches.js";
|
|
3
|
+
import { listContractIds, readLedger } from "../ledger.js";
|
|
4
|
+
import { readProjectionStatusBoard } from "../projection-status.js";
|
|
5
|
+
import { deriveQueueReadModel, queueMembership, } from "../queue.js";
|
|
6
|
+
import { findOpenArc, getMaxArc, receiptsAfterFence } from "../seal.js";
|
|
7
|
+
import { readTaskBoard } from "../task.js";
|
|
8
|
+
import { openTaskStore } from "../task-git-store.js";
|
|
9
|
+
import { taskRepositoryFromStore } from "../task-store-repository.js";
|
|
10
|
+
import { deriveBootstrapDiagnostics, deriveContractState, hasCurrentPetitionVerdict, isTerminalState, } from "./lifecycle.js";
|
|
11
|
+
import { commissionTarget, evaluateBaseDrift, latestBase, resolveDefaultBranchFromSettings, } from "./drift.js";
|
|
12
|
+
import { computeClaimReconciliation, } from "./reconciliation.js";
|
|
13
|
+
function compareTaskStatusRows(left, right) {
|
|
14
|
+
if (left.pri !== right.pri)
|
|
15
|
+
return left.pri - right.pri;
|
|
16
|
+
if (left.createdAt !== right.createdAt)
|
|
17
|
+
return left.createdAt < right.createdAt ? -1 : 1;
|
|
18
|
+
return left.id < right.id ? -1 : left.id > right.id ? 1 : 0;
|
|
19
|
+
}
|
|
20
|
+
async function readTaskStatusSummary(cwd) {
|
|
21
|
+
try {
|
|
22
|
+
const board = await readTaskBoard(taskRepositoryFromStore(await openTaskStore(cwd)));
|
|
23
|
+
const eligible = board.tasks.filter((task) => !task.contractId && (task.state === "open" || task.state === "in_progress"));
|
|
24
|
+
const inProgress = eligible.filter((task) => task.state === "in_progress").length;
|
|
25
|
+
const topTasks = [
|
|
26
|
+
...eligible.filter((task) => task.state === "in_progress").sort(compareTaskStatusRows),
|
|
27
|
+
...eligible.filter((task) => task.state === "open" && !task.blocked).sort(compareTaskStatusRows),
|
|
28
|
+
]
|
|
29
|
+
.slice(0, 5)
|
|
30
|
+
.map(({ id, title, pri, createdAt, state, blocked }) => ({ id, title, pri, createdAt, state, blocked }));
|
|
31
|
+
const ready = eligible.filter((task) => task.state === "open" && !task.blocked).length;
|
|
32
|
+
const blocked = eligible.filter((task) => task.state === "open" && task.blocked).length;
|
|
33
|
+
if (board.tasks.length === 0 && board.diseases.length === 0)
|
|
34
|
+
return undefined;
|
|
35
|
+
return { total: board.tasks.length, inProgress, ready, blocked, topTasks, diseases: board.diseases };
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
return {
|
|
39
|
+
total: 0,
|
|
40
|
+
inProgress: 0,
|
|
41
|
+
ready: 0,
|
|
42
|
+
blocked: 0,
|
|
43
|
+
topTasks: [],
|
|
44
|
+
diseases: [],
|
|
45
|
+
diagnostic: error instanceof Error ? error.message : String(error),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function isQueueHeadStall(input) {
|
|
50
|
+
return (input.queuePosition === 1 &&
|
|
51
|
+
input.queueLength > 1 &&
|
|
52
|
+
input.state.state === "petitioned" &&
|
|
53
|
+
!hasCurrentPetitionVerdict(input.entries));
|
|
54
|
+
}
|
|
55
|
+
function findBindEntry(entries) {
|
|
56
|
+
return entries.find((entry) => entry.kind === "bind");
|
|
57
|
+
}
|
|
58
|
+
function contractSlugFromId(id) {
|
|
59
|
+
const dot = id.indexOf(".");
|
|
60
|
+
if (dot > 0)
|
|
61
|
+
return id.slice(0, dot);
|
|
62
|
+
return id;
|
|
63
|
+
}
|
|
64
|
+
function deriveCurrentArc(entries, bindObjective, lifecycle) {
|
|
65
|
+
// No explicit arc has ever existed: derive a0 from bind objective.
|
|
66
|
+
if (getMaxArc(entries) === 0) {
|
|
67
|
+
const a0Open = lifecycle.state === "bound" || lifecycle.state === "active";
|
|
68
|
+
return {
|
|
69
|
+
number: 0,
|
|
70
|
+
objective: bindObjective ?? "",
|
|
71
|
+
state: a0Open ? "open" : "closed",
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
const arcObjectives = new Map();
|
|
75
|
+
let currentArc = null;
|
|
76
|
+
for (const entry of entries) {
|
|
77
|
+
if (entry.kind === "arc") {
|
|
78
|
+
arcObjectives.set(entry.data.arc, entry.data.objective);
|
|
79
|
+
currentArc = {
|
|
80
|
+
number: entry.data.arc,
|
|
81
|
+
objective: entry.data.objective,
|
|
82
|
+
state: "open",
|
|
83
|
+
};
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
if (entry.kind === "seal" && entry.data.arc > 0) {
|
|
87
|
+
currentArc = {
|
|
88
|
+
number: entry.data.arc,
|
|
89
|
+
objective: arcObjectives.get(entry.data.arc) ?? bindObjective ?? "",
|
|
90
|
+
state: "closed",
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (currentArc)
|
|
95
|
+
return currentArc;
|
|
96
|
+
return {
|
|
97
|
+
number: 0,
|
|
98
|
+
objective: bindObjective ?? "",
|
|
99
|
+
state: "closed",
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
function deriveNextStep(input) {
|
|
103
|
+
if (input.state.state === "corrupt") {
|
|
104
|
+
return { kind: "inspect-log", detail: "inspect log" };
|
|
105
|
+
}
|
|
106
|
+
const pendingAfter = input.after.filter((item) => !item.terminal).map((item) => contractSlugFromId(item.id));
|
|
107
|
+
if (pendingAfter.length > 0) {
|
|
108
|
+
return { kind: "wait-after", detail: `finish after ${pendingAfter.join(",")}` };
|
|
109
|
+
}
|
|
110
|
+
if (input.state.state === "petitioned") {
|
|
111
|
+
return { kind: "await-claim", detail: "await claim" };
|
|
112
|
+
}
|
|
113
|
+
if (input.baseDrift.state === "drift" || input.baseDrift.state === "orphaned") {
|
|
114
|
+
return { kind: "renew", detail: "renew" };
|
|
115
|
+
}
|
|
116
|
+
// Derived a0 (bound/active, no explicit arc) petitions directly; explicit open arcs still suggest arc work.
|
|
117
|
+
if (input.currentArc.state === "open" && input.currentArc.number === 0) {
|
|
118
|
+
return { kind: "petition", detail: "petition" };
|
|
119
|
+
}
|
|
120
|
+
if (input.currentArc.state === "open") {
|
|
121
|
+
return { kind: "arc", detail: "arc" };
|
|
122
|
+
}
|
|
123
|
+
if (input.state.state === "active" && input.currentArc.number > 0) {
|
|
124
|
+
return { kind: "petition", detail: "petition" };
|
|
125
|
+
}
|
|
126
|
+
return { kind: "arc", detail: "arc" };
|
|
127
|
+
}
|
|
128
|
+
function deriveLastActiveAge(entries, now) {
|
|
129
|
+
const last = entries.at(-1);
|
|
130
|
+
if (!last)
|
|
131
|
+
return {};
|
|
132
|
+
const atMs = Date.parse(last.at);
|
|
133
|
+
if (!Number.isFinite(atMs)) {
|
|
134
|
+
return { lastActivityAt: last.at };
|
|
135
|
+
}
|
|
136
|
+
return {
|
|
137
|
+
lastActivityAt: last.at,
|
|
138
|
+
lastActiveMs: Math.max(0, now - atMs),
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
export async function readKanshiBoard(cwd, now, opts) {
|
|
142
|
+
const targetFilter = opts?.target ?? null;
|
|
143
|
+
const loadedSettings = await loadKeiyakuSettings(cwd);
|
|
144
|
+
const tasks = await readTaskStatusSummary(cwd);
|
|
145
|
+
if (!(await isGitRepo(cwd))) {
|
|
146
|
+
return {
|
|
147
|
+
contracts: [],
|
|
148
|
+
projections: null,
|
|
149
|
+
queueLength: 0,
|
|
150
|
+
diseases: loadedSettings.diseases,
|
|
151
|
+
claimReconciliation: null,
|
|
152
|
+
targetFilter,
|
|
153
|
+
...(tasks ? { tasks } : {}),
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
// Status is a pure reader: use the same resolver, but retain its ability to
|
|
157
|
+
// open when the defaultBranch knob itself is diseased.
|
|
158
|
+
let defaultBranch = null;
|
|
159
|
+
try {
|
|
160
|
+
defaultBranch = await resolveDefaultBranchFromSettings(cwd, loadedSettings, false);
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
defaultBranch = null;
|
|
164
|
+
}
|
|
165
|
+
const contractIds = await listContractIds(cwd);
|
|
166
|
+
const terminalById = new Map();
|
|
167
|
+
const ledgers = [];
|
|
168
|
+
const readableLedgers = [];
|
|
169
|
+
for (const id of contractIds) {
|
|
170
|
+
try {
|
|
171
|
+
const ledger = await readLedger(cwd, id);
|
|
172
|
+
if (ledger) {
|
|
173
|
+
ledgers.push(ledger);
|
|
174
|
+
readableLedgers.push(ledger);
|
|
175
|
+
terminalById.set(id, isTerminalState(deriveContractState(ledger.entries)));
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
ledgers.push({ id, error: error instanceof Error ? error : new Error(String(error)) });
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
const queueModel = deriveQueueReadModel(readableLedgers);
|
|
183
|
+
const boardQueueLength = queueModel.targetQueues.reduce((sum, queue) => sum + queue.ordered.length, 0);
|
|
184
|
+
const contracts = [];
|
|
185
|
+
// Readable-ledger commission targets retained for post-assembly filter only.
|
|
186
|
+
const derivedTargetById = new Map();
|
|
187
|
+
for (const item of ledgers) {
|
|
188
|
+
if ("error" in item) {
|
|
189
|
+
// Unreadable ledgers have no derivable target and stay under every filter.
|
|
190
|
+
derivedTargetById.set(item.id, undefined);
|
|
191
|
+
contracts.push({
|
|
192
|
+
id: item.id,
|
|
193
|
+
slug: contractSlugFromId(item.id),
|
|
194
|
+
state: { state: "corrupt", at: 0, reason: item.error.message },
|
|
195
|
+
queueHeadStalled: false,
|
|
196
|
+
arcs: 0,
|
|
197
|
+
currentArc: { number: 0, objective: "", state: "closed" },
|
|
198
|
+
nextStep: { kind: "inspect-log", detail: "inspect log" },
|
|
199
|
+
baseDrift: { state: "unknown", label: "n/a (corrupt ledger)" },
|
|
200
|
+
after: [],
|
|
201
|
+
corruptReason: item.error.message,
|
|
202
|
+
bootstrapDiagnostics: [],
|
|
203
|
+
queueDiagnostics: [],
|
|
204
|
+
});
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
const bind = findBindEntry(item.entries);
|
|
208
|
+
derivedTargetById.set(item.contractId, commissionTarget(item.entries)?.ref);
|
|
209
|
+
const base = latestBase(item.entries);
|
|
210
|
+
const state = deriveContractState(item.entries);
|
|
211
|
+
if (isTerminalState(state))
|
|
212
|
+
continue;
|
|
213
|
+
const after = (bind?.data.after ?? []).map((id) => ({ id, terminal: terminalById.get(id) ?? false }));
|
|
214
|
+
const currentArc = deriveCurrentArc(item.entries, bind?.data.objective, state);
|
|
215
|
+
const baseDrift = base
|
|
216
|
+
? await evaluateBaseDrift(cwd, base, defaultBranch)
|
|
217
|
+
: { state: "unknown", label: "n/a (no default branch)" };
|
|
218
|
+
const nextStep = deriveNextStep({
|
|
219
|
+
id: item.contractId,
|
|
220
|
+
state,
|
|
221
|
+
currentArc,
|
|
222
|
+
baseDrift,
|
|
223
|
+
after,
|
|
224
|
+
});
|
|
225
|
+
if (nextStep.kind === "renew" && baseDrift.state === "drift") {
|
|
226
|
+
const openArc = findOpenArc(item.entries);
|
|
227
|
+
let receiptCount = 0;
|
|
228
|
+
if (openArc) {
|
|
229
|
+
try {
|
|
230
|
+
receiptCount = (await receiptsAfterFence(cwd, item.entries, `keiyaku/${item.contractId}`)).length;
|
|
231
|
+
}
|
|
232
|
+
catch {
|
|
233
|
+
// A ledger-only here contract may not have a delivery ref yet.
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
const sealForecast = openArc ? `seals a${openArc.data.arc} (${receiptCount} receipts), ` : "";
|
|
237
|
+
nextStep.hint = `run: keiyaku @${item.contractId} renew — ${sealForecast}follows ${baseDrift.defaultBranch} ▲${baseDrift.behindBy}`;
|
|
238
|
+
}
|
|
239
|
+
const membership = queueMembership(queueModel, item.contractId);
|
|
240
|
+
const queuePosition = membership?.position ?? null;
|
|
241
|
+
const targetQueueLength = membership?.queueLength ?? 0;
|
|
242
|
+
contracts.push({
|
|
243
|
+
id: item.contractId,
|
|
244
|
+
slug: contractSlugFromId(item.contractId),
|
|
245
|
+
state,
|
|
246
|
+
queuePosition,
|
|
247
|
+
queueHeadStalled: isQueueHeadStall({
|
|
248
|
+
entries: item.entries,
|
|
249
|
+
queuePosition,
|
|
250
|
+
queueLength: targetQueueLength,
|
|
251
|
+
state,
|
|
252
|
+
}),
|
|
253
|
+
arcs: getMaxArc(item.entries),
|
|
254
|
+
currentArc,
|
|
255
|
+
nextStep,
|
|
256
|
+
...deriveLastActiveAge(item.entries, now),
|
|
257
|
+
place: bind?.data.place ?? null,
|
|
258
|
+
base,
|
|
259
|
+
baseDrift,
|
|
260
|
+
after,
|
|
261
|
+
bootstrapDiagnostics: deriveBootstrapDiagnostics(item.entries),
|
|
262
|
+
queueDiagnostics: membership ? [...membership.diagnostics] : [],
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
const filteredContracts = targetFilter === null
|
|
266
|
+
? contracts
|
|
267
|
+
: contracts.filter((contract) => {
|
|
268
|
+
const derived = derivedTargetById.get(contract.id);
|
|
269
|
+
return derived === undefined || derived === targetFilter;
|
|
270
|
+
});
|
|
271
|
+
const queueLength = targetFilter === null
|
|
272
|
+
? boardQueueLength
|
|
273
|
+
: (queueModel.targetQueues.find((queue) => queue.target === targetFilter)?.ordered.length ?? 0);
|
|
274
|
+
const claimReconciliation = await computeClaimReconciliation(cwd, defaultBranch);
|
|
275
|
+
return {
|
|
276
|
+
contracts: filteredContracts,
|
|
277
|
+
projections: readProjectionStatusBoard(cwd, now, { showAll: opts?.showAllProjections }),
|
|
278
|
+
queueLength,
|
|
279
|
+
defaultBranch: defaultBranch?.branch,
|
|
280
|
+
defaultHead: defaultBranch?.head,
|
|
281
|
+
diseases: loadedSettings.diseases,
|
|
282
|
+
claimReconciliation,
|
|
283
|
+
targetFilter,
|
|
284
|
+
...(tasks ? { tasks } : {}),
|
|
285
|
+
};
|
|
286
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { assertSettingsKnobUsable } from "../../config/settings/disease.js";
|
|
2
|
+
import { loadKeiyakuSettings } from "../../config/settings/loader.js";
|
|
3
|
+
import { FlowError } from "../../flow-error.js";
|
|
4
|
+
import { SETTINGS_FILE } from "../../keiyaku.js";
|
|
5
|
+
import { createGit, errorContainsAnyPattern, MISSING_HEAD_PATTERNS, wrapGitError } from "../../git/core.js";
|
|
6
|
+
function findBindEntry(entries) {
|
|
7
|
+
return entries.find((entry) => entry.kind === "bind");
|
|
8
|
+
}
|
|
9
|
+
export function latestBase(entries) {
|
|
10
|
+
for (let index = entries.length - 1; index >= 0; index -= 1) {
|
|
11
|
+
const entry = entries[index];
|
|
12
|
+
if (entry.kind === "renew")
|
|
13
|
+
return entry.data.newBase;
|
|
14
|
+
}
|
|
15
|
+
return entries.find((entry) => entry.kind === "bind")?.data.base;
|
|
16
|
+
}
|
|
17
|
+
/** Commission settlement target from the bind entry. Schema guarantees refs/heads/<name>. */
|
|
18
|
+
export function commissionTarget(entries) {
|
|
19
|
+
const bind = findBindEntry(entries);
|
|
20
|
+
if (!bind)
|
|
21
|
+
return undefined;
|
|
22
|
+
const ref = bind.data.target;
|
|
23
|
+
return {
|
|
24
|
+
ref,
|
|
25
|
+
branch: ref.slice("refs/heads/".length),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
export async function resolveCommit(cwd, ref) {
|
|
29
|
+
const git = createGit(cwd);
|
|
30
|
+
try {
|
|
31
|
+
return (await git.raw(["rev-parse", "--verify", `${ref}^{commit}`])).trim();
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
if (errorContainsAnyPattern(error, MISSING_HEAD_PATTERNS))
|
|
35
|
+
return null;
|
|
36
|
+
throw wrapGitError(`rev-parse ${ref}`, error, cwd);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
export async function readSettingsDiseases(cwd) {
|
|
40
|
+
const loaded = await loadKeiyakuSettings(cwd);
|
|
41
|
+
return loaded.diseases;
|
|
42
|
+
}
|
|
43
|
+
export async function readDefaultBranchOverride(cwd) {
|
|
44
|
+
const loaded = await loadKeiyakuSettings(cwd);
|
|
45
|
+
return loaded.knobs?.defaultBranch;
|
|
46
|
+
}
|
|
47
|
+
export async function resolveDefaultBranchFromSettings(cwd, loaded, rejectDiseasedOverride) {
|
|
48
|
+
if (rejectDiseasedOverride)
|
|
49
|
+
assertSettingsKnobUsable(loaded, "defaultBranch");
|
|
50
|
+
const override = loaded.knobs?.defaultBranch;
|
|
51
|
+
if (override) {
|
|
52
|
+
const head = await resolveCommit(cwd, override);
|
|
53
|
+
if (!head)
|
|
54
|
+
throw new FlowError("INVALID_SETTINGS", `${SETTINGS_FILE} defaultBranch '${override}' does not resolve`);
|
|
55
|
+
return { branch: override, head };
|
|
56
|
+
}
|
|
57
|
+
const git = createGit(cwd);
|
|
58
|
+
for (const branch of ["main", "master"]) {
|
|
59
|
+
const head = await resolveCommit(cwd, branch);
|
|
60
|
+
if (head)
|
|
61
|
+
return { branch, head };
|
|
62
|
+
}
|
|
63
|
+
try {
|
|
64
|
+
const originHead = (await git.raw(["symbolic-ref", "--quiet", "--short", "refs/remotes/origin/HEAD"])).trim();
|
|
65
|
+
if (originHead) {
|
|
66
|
+
const head = await resolveCommit(cwd, originHead);
|
|
67
|
+
if (head)
|
|
68
|
+
return { branch: originHead, head };
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
// Fall through to local branch heuristics.
|
|
73
|
+
}
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
export async function resolveDefaultBranch(cwd) {
|
|
77
|
+
return resolveDefaultBranchFromSettings(cwd, await loadKeiyakuSettings(cwd), true);
|
|
78
|
+
}
|
|
79
|
+
export async function evaluateBaseDrift(cwd, base, defaultBranch) {
|
|
80
|
+
if (!defaultBranch)
|
|
81
|
+
return { state: "unknown", label: "n/a (no default branch)" };
|
|
82
|
+
if (base === defaultBranch.head) {
|
|
83
|
+
return { state: "green", defaultBranch: defaultBranch.branch, defaultHead: defaultBranch.head };
|
|
84
|
+
}
|
|
85
|
+
const git = createGit(cwd);
|
|
86
|
+
try {
|
|
87
|
+
await git.raw(["merge-base", "--is-ancestor", base, defaultBranch.head]);
|
|
88
|
+
const behindBy = Number.parseInt((await git.raw(["rev-list", "--count", `${base}..${defaultBranch.head}`])).trim(), 10);
|
|
89
|
+
return {
|
|
90
|
+
state: "drift",
|
|
91
|
+
defaultBranch: defaultBranch.branch,
|
|
92
|
+
defaultHead: defaultBranch.head,
|
|
93
|
+
behindBy: Number.isFinite(behindBy) ? behindBy : 0,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
return { state: "orphaned", defaultBranch: defaultBranch.branch, defaultHead: defaultBranch.head };
|
|
98
|
+
}
|
|
99
|
+
}
|