@astrosheep/keiyaku 2.9.0 → 2.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/.tsbuildinfo +1 -1
- package/build/cli/commands/akuma/list/handler.js +1 -1
- package/build/cli/commands/akuma/view/handler.js +1 -1
- package/build/cli/commands/contract/amend/handler.js +1 -1
- package/build/cli/commands/contract/arc/handler.js +1 -1
- package/build/cli/commands/contract/bind/handler.js +1 -1
- package/build/cli/commands/contract/forfeit/handler.js +1 -1
- package/build/cli/commands/contract/log/handler.js +1 -1
- package/build/cli/commands/contract/petition/handler.js +1 -1
- package/build/cli/commands/contract/renew/handler.js +1 -1
- package/build/cli/commands/projection/call/handler.js +5 -2
- package/build/cli/commands/projection/kill/handler.js +3 -1
- package/build/cli/commands/projection/revive/handler.js +6 -3
- package/build/cli/commands/projection/status/handler.js +4 -2
- package/build/cli/commands/projection/tell/handler.js +3 -1
- package/build/cli/commands/projection/wait/handler.js +3 -1
- package/build/cli/commands/shared.js +5 -5
- package/build/cli/commands/system/completion/handler.js +1 -1
- package/build/cli/parse-flags.js +197 -0
- package/build/cli/parse-metadata.js +129 -0
- package/build/cli/parse-selectors.js +65 -0
- package/build/cli/parse.js +19 -373
- package/build/cli/skills-install.js +1 -1
- package/build/config/akuma-loader.js +5 -2
- package/build/core/amend.js +2 -5
- package/build/core/arc.js +2 -4
- package/build/core/call/call.js +7 -2
- package/build/core/call/context.js +27 -14
- package/build/core/call-persist.js +9 -2
- package/build/core/path-coordinate.js +27 -0
- package/build/core/petition-run.js +2 -5
- package/build/core/projection-coordinate.js +14 -2
- package/build/core/renew.js +2 -4
- package/build/core/status/board.js +6 -3
- package/build/core/task-contract.js +13 -0
- package/build/core/task-git-runtime.js +5 -2
- package/build/core/task-git-store.js +31 -21
- package/build/core/worktree-path.js +9 -2
- package/build/flow-error.js +2 -0
- package/build/generated/version.js +1 -1
- package/build/git/branches.js +33 -9
- package/build/git/core.js +9 -0
- package/package.json +1 -1
- package/skills/keiyaku/SKILL.md +30 -13
- package/skills/keiyaku-akuma/SKILL.md +30 -62
- package/skills/keiyaku-task/SKILL.md +51 -0
- package/skills/keiyaku-workflow/SKILL.md +71 -0
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { spawnSync } from "node:child_process";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { ProjectionStateError } from "./atomic-publish.js";
|
|
4
|
+
import { isGitExecutableUnavailable } from "../git/core.js";
|
|
5
|
+
import { gitCommonRootPath, resolveGitCommonRootSync } from "./path-coordinate.js";
|
|
4
6
|
/**
|
|
5
7
|
* Resolve projection storage root and authority from one Git probe.
|
|
6
8
|
*
|
|
@@ -19,6 +21,9 @@ export function resolveProjectionCoordinate(cwd) {
|
|
|
19
21
|
},
|
|
20
22
|
stdio: ["ignore", "pipe", "pipe"],
|
|
21
23
|
});
|
|
24
|
+
if (result.error && isGitExecutableUnavailable(result.error)) {
|
|
25
|
+
return { physicalRoot: cwd, authority: { kind: "user" } };
|
|
26
|
+
}
|
|
22
27
|
if (result.error) {
|
|
23
28
|
throw new ProjectionStateError(`cannot resolve projection repository coordinate: ${result.error.message}`);
|
|
24
29
|
}
|
|
@@ -30,10 +35,17 @@ export function resolveProjectionCoordinate(cwd) {
|
|
|
30
35
|
throw new ProjectionStateError(`cannot resolve projection repository coordinate${detail ? `: ${detail}` : ` (git exited ${String(result.status)})`}`);
|
|
31
36
|
}
|
|
32
37
|
const [bare, commonDir] = result.stdout.trim().split(/\r?\n/);
|
|
33
|
-
|
|
38
|
+
const stableRoot = commonDir === undefined ? undefined : gitCommonRootPath(commonDir);
|
|
39
|
+
if (bare !== "false" || !commonDir || !stableRoot || !path.isAbsolute(stableRoot)) {
|
|
34
40
|
return { physicalRoot: cwd, authority: { kind: "user" } };
|
|
35
41
|
}
|
|
36
|
-
|
|
42
|
+
let physicalRoot;
|
|
43
|
+
try {
|
|
44
|
+
physicalRoot = resolveGitCommonRootSync(commonDir);
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
throw new ProjectionStateError(`cannot resolve projection repository root ${stableRoot}`, { cause: error });
|
|
48
|
+
}
|
|
37
49
|
return {
|
|
38
50
|
physicalRoot,
|
|
39
51
|
authority: { kind: "repository", stableRoot: physicalRoot },
|
package/build/core/renew.js
CHANGED
|
@@ -10,7 +10,7 @@ import { captureSeal, findOpenArc, getMaxArc, receiptsAfterFence, sealCurrentArc
|
|
|
10
10
|
import { deriveContractState, isTerminalState } from "./status/lifecycle.js";
|
|
11
11
|
import { commissionTarget, latestBase } from "./status/drift.js";
|
|
12
12
|
import { findContractWorktreePath } from "./worktree-path.js";
|
|
13
|
-
import { getCurrentBranch,
|
|
13
|
+
import { getCurrentBranch, requireGitRepository } from "../git/branches.js";
|
|
14
14
|
import { resolveContractAddress, withResolvedAddress } from "./addressing.js";
|
|
15
15
|
import { ACTOR_IDENTITY } from "../keiyaku.js";
|
|
16
16
|
import { persistResponseHistory } from "./transcripts.js";
|
|
@@ -164,9 +164,7 @@ export async function renewContract(input) {
|
|
|
164
164
|
};
|
|
165
165
|
}
|
|
166
166
|
export async function renewKeiyaku(input) {
|
|
167
|
-
|
|
168
|
-
throw new FlowError("NOT_GIT_REPO", `${input.cwd} is not a git repository`);
|
|
169
|
-
}
|
|
167
|
+
await requireGitRepository(input.cwd, "renew");
|
|
170
168
|
const address = input.address ?? await resolveContractAddress(input.cwd, input.contractId, input.contractAddressSource);
|
|
171
169
|
return await withResolvedAddress(address, async () => {
|
|
172
170
|
const contractId = address.binding.commissionId;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { loadKeiyakuSettings } from "../../config/settings/loader.js";
|
|
2
|
-
import {
|
|
2
|
+
import { observeGitRepository } from "../../git/branches.js";
|
|
3
3
|
import { listContractIds, readLedger } from "../ledger.js";
|
|
4
4
|
import { readProjectionStatusBoard } from "../projection-status.js";
|
|
5
5
|
import { deriveQueueReadModel, queueMembership, } from "../queue.js";
|
|
@@ -142,10 +142,13 @@ export async function readKanshiBoard(cwd, now, opts) {
|
|
|
142
142
|
const targetFilter = opts?.target ?? null;
|
|
143
143
|
const loadedSettings = await loadKeiyakuSettings(cwd);
|
|
144
144
|
const tasks = await readTaskStatusSummary(cwd);
|
|
145
|
-
|
|
145
|
+
const repository = await observeGitRepository(cwd);
|
|
146
|
+
if (repository.capability === "unknown")
|
|
147
|
+
throw repository.error;
|
|
148
|
+
if (repository.capability !== "available" || repository.coordinate !== "repository") {
|
|
146
149
|
return {
|
|
147
150
|
contracts: [],
|
|
148
|
-
projections:
|
|
151
|
+
projections: readProjectionStatusBoard(cwd, now, { showAll: opts?.showAllProjections }),
|
|
149
152
|
queueLength: 0,
|
|
150
153
|
diseases: loadedSettings.diseases,
|
|
151
154
|
claimReconciliation: null,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { FlowError } from "../flow-error.js";
|
|
2
|
+
import { assertGitObservation, requireGitRepository } from "../git/branches.js";
|
|
2
3
|
import { withLockHeldTaskBoard } from "./task-git-store.js";
|
|
3
4
|
import { prepareTaskDocumentReplacements, taskRepositoryFromLockedBoard } from "./task-store-repository.js";
|
|
4
5
|
import { containsCommit, createLandingCommit, createTaskBoardCommit, inspectCurrentBranch, publishForwardTaskBoardCommit, readableRefHead, reconcileTaskIndex, refHead, sameTaskSourceBytes, taskContractAtCommit, taskWritesFromSources, updateBranch, updateTargetRef, } from "./task/settlement-git.js";
|
|
@@ -17,8 +18,12 @@ async function replaceTaskDocument(repository, replacement) {
|
|
|
17
18
|
}));
|
|
18
19
|
}
|
|
19
20
|
export async function settleTaskBoardForBind(input, deps = {}) {
|
|
21
|
+
await requireGitRepository(input.cwd, "task bind");
|
|
20
22
|
return withLockHeldTaskBoard(input.cwd, async (transaction) => {
|
|
21
23
|
if (transaction.location.mode !== "git") {
|
|
24
|
+
if (transaction.location.capability === "unavailable") {
|
|
25
|
+
assertGitObservation({ capability: "unavailable" }, "task bind");
|
|
26
|
+
}
|
|
22
27
|
throw new FlowError("NOT_GIT_REPO", "task bind requires Git task storage");
|
|
23
28
|
}
|
|
24
29
|
const hubCwd = transaction.location.root;
|
|
@@ -103,9 +108,13 @@ export async function settleTaskBoardForBind(input, deps = {}) {
|
|
|
103
108
|
});
|
|
104
109
|
}
|
|
105
110
|
export async function reconcileTaskForfeit(input) {
|
|
111
|
+
await requireGitRepository(input.cwd, "task forfeit");
|
|
106
112
|
const hubRoot = await stableRepoRoot(input.cwd);
|
|
107
113
|
return withLockHeldTaskBoard(hubRoot, async (transaction) => {
|
|
108
114
|
if (transaction.location.mode !== "git") {
|
|
115
|
+
if (transaction.location.capability === "unavailable") {
|
|
116
|
+
assertGitObservation({ capability: "unavailable" }, "task forfeit");
|
|
117
|
+
}
|
|
109
118
|
throw new FlowError("NOT_GIT_REPO", "task forfeit requires Git task storage");
|
|
110
119
|
}
|
|
111
120
|
const repository = taskRepositoryFromLockedBoard(transaction);
|
|
@@ -126,6 +135,7 @@ export async function reconcileTaskForfeit(input) {
|
|
|
126
135
|
* one complete snapshot of the common hub board held under its repository lock.
|
|
127
136
|
*/
|
|
128
137
|
export async function settleTaskBoardForLanding(input) {
|
|
138
|
+
await requireGitRepository(input.cwd, "task landing");
|
|
129
139
|
const hubRoot = await stableRepoRoot(input.cwd);
|
|
130
140
|
let lastTargetHead = input.targetHead;
|
|
131
141
|
let lastPublishError;
|
|
@@ -133,6 +143,9 @@ export async function settleTaskBoardForLanding(input) {
|
|
|
133
143
|
for (let attempt = 0; attempt < 4; attempt += 1) {
|
|
134
144
|
const result = await withLockHeldTaskBoard(hubRoot, async (transaction) => {
|
|
135
145
|
if (transaction.location.mode !== "git") {
|
|
146
|
+
if (transaction.location.capability === "unavailable") {
|
|
147
|
+
assertGitObservation({ capability: "unavailable" }, "task landing");
|
|
148
|
+
}
|
|
136
149
|
throw new FlowError("NOT_GIT_REPO", "task landing requires Git task storage");
|
|
137
150
|
}
|
|
138
151
|
// Each attempt reads fresh target and board facts while holding the
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { observeGitRepository } from "../git/branches.js";
|
|
2
2
|
import { listContractIds } from "./ledger.js";
|
|
3
3
|
import { contractBoundByEngineWorktree, stableRepoRoot } from "./worktree-path.js";
|
|
4
4
|
import { inspectTaskLock, openTaskStore } from "./task-git-store.js";
|
|
@@ -6,7 +6,10 @@ import { taskRepositoryFromStore } from "./task-store-repository.js";
|
|
|
6
6
|
import { withTaskWorktreePolicy } from "./task-worktree.js";
|
|
7
7
|
import { inspectTaskDoctor } from "./task-doctor.js";
|
|
8
8
|
export async function taskCommandContractId(cwd) {
|
|
9
|
-
|
|
9
|
+
const observation = await observeGitRepository(cwd);
|
|
10
|
+
if (observation.capability === "unknown")
|
|
11
|
+
throw observation.error;
|
|
12
|
+
if (observation.capability !== "available" || observation.coordinate !== "repository")
|
|
10
13
|
return undefined;
|
|
11
14
|
return contractBoundByEngineWorktree(cwd);
|
|
12
15
|
}
|
|
@@ -4,6 +4,8 @@ import * as fs from "node:fs";
|
|
|
4
4
|
import * as path from "node:path";
|
|
5
5
|
import { DatabaseSync } from "node:sqlite";
|
|
6
6
|
import { FlowError } from "../flow-error.js";
|
|
7
|
+
import { isGitExecutableUnavailable, NOT_GIT_REPOSITORY_PATTERNS } from "../git/core.js";
|
|
8
|
+
import { gitCommonRootPath, normalizeGitPath } from "./path-coordinate.js";
|
|
7
9
|
const TASK_ID = /^k-[0-9a-f]{12}$/;
|
|
8
10
|
const SQLITE_BUSY = 5;
|
|
9
11
|
const LOCK_FILE = "task-lock.sqlite";
|
|
@@ -35,17 +37,6 @@ function defaultRunGit(cwd, args, input) {
|
|
|
35
37
|
stdio: ["pipe", "pipe", "pipe"],
|
|
36
38
|
});
|
|
37
39
|
}
|
|
38
|
-
function runGit(cwd, args, input) {
|
|
39
|
-
const result = defaultRunGit(cwd, args, input);
|
|
40
|
-
if (result.error) {
|
|
41
|
-
throw new FlowError("INTERNAL_STATE", `git ${args[0]} failed: ${result.error.message}`, { cause: result.error });
|
|
42
|
-
}
|
|
43
|
-
if (result.status !== 0) {
|
|
44
|
-
const detail = result.stderr.trim() || `exit ${String(result.status)}`;
|
|
45
|
-
throw new FlowError("INTERNAL_STATE", `git ${args[0]} failed: ${detail}`);
|
|
46
|
-
}
|
|
47
|
-
return result.stdout;
|
|
48
|
-
}
|
|
49
40
|
function nearestDirectoryBoard(cwd) {
|
|
50
41
|
const original = path.resolve(cwd);
|
|
51
42
|
let candidate = original;
|
|
@@ -58,26 +49,45 @@ function nearestDirectoryBoard(cwd) {
|
|
|
58
49
|
candidate = parent;
|
|
59
50
|
}
|
|
60
51
|
}
|
|
61
|
-
export
|
|
62
|
-
|
|
63
|
-
|
|
52
|
+
export function taskBoardRootFromGitDir(rawGitDir, platform = process.platform) {
|
|
53
|
+
return gitCommonRootPath(rawGitDir, platform);
|
|
54
|
+
}
|
|
55
|
+
export async function locateTaskBoard(cwd, runGitProbe = defaultRunGit) {
|
|
56
|
+
const probe = runGitProbe(cwd, ["rev-parse", "--is-inside-work-tree", "--is-bare-repository"]);
|
|
57
|
+
if (probe.error && !isGitExecutableUnavailable(probe.error)) {
|
|
64
58
|
throw new FlowError("INTERNAL_STATE", `cannot inspect task repository: ${probe.error.message}`, { cause: probe.error });
|
|
65
59
|
}
|
|
60
|
+
if (probe.error) {
|
|
61
|
+
return { mode: "directory", root: nearestDirectoryBoard(cwd), capability: "unavailable" };
|
|
62
|
+
}
|
|
66
63
|
if (probe.status !== 0) {
|
|
67
|
-
|
|
64
|
+
if (NOT_GIT_REPOSITORY_PATTERNS.some((pattern) => probe.stderr.includes(pattern))) {
|
|
65
|
+
return { mode: "directory", root: nearestDirectoryBoard(cwd) };
|
|
66
|
+
}
|
|
67
|
+
const detail = probe.stderr.trim() || `exit ${String(probe.status)}`;
|
|
68
|
+
throw new FlowError("INTERNAL_STATE", `cannot inspect task repository: ${detail}`);
|
|
68
69
|
}
|
|
69
70
|
const [insideWorktree, bare] = probe.stdout.trim().split(/\r?\n/);
|
|
70
71
|
if (bare === "true" || insideWorktree !== "true") {
|
|
71
72
|
throw new FlowError("NOT_GIT_REPO", "bare repositories are not supported for task storage");
|
|
72
73
|
}
|
|
73
|
-
const facts =
|
|
74
|
-
|
|
75
|
-
|
|
74
|
+
const facts = runGitProbe(cwd, ["rev-parse", "--path-format=absolute", "--show-toplevel", "--git-common-dir"]);
|
|
75
|
+
if (facts.error) {
|
|
76
|
+
throw new FlowError("INTERNAL_STATE", `git rev-parse failed: ${facts.error.message}`, { cause: facts.error });
|
|
77
|
+
}
|
|
78
|
+
if (facts.status !== 0) {
|
|
79
|
+
const detail = facts.stderr.trim() || `exit ${String(facts.status)}`;
|
|
80
|
+
throw new FlowError("INTERNAL_STATE", `git rev-parse failed: ${detail}`);
|
|
81
|
+
}
|
|
82
|
+
const [checkoutRoot, rawGitDir] = facts.stdout.trimEnd().split(/\r?\n/);
|
|
83
|
+
const gitDir = rawGitDir === undefined ? undefined : normalizeGitPath(rawGitDir);
|
|
84
|
+
const normalizedCheckoutRoot = checkoutRoot === undefined ? undefined : normalizeGitPath(checkoutRoot);
|
|
85
|
+
if (!normalizedCheckoutRoot || !gitDir || !path.isAbsolute(normalizedCheckoutRoot) || !path.isAbsolute(gitDir)) {
|
|
76
86
|
throw new FlowError("INTERNAL_STATE", "git returned incomplete task-board location facts");
|
|
77
87
|
}
|
|
78
88
|
return {
|
|
79
89
|
mode: "git",
|
|
80
|
-
root:
|
|
90
|
+
root: taskBoardRootFromGitDir(gitDir),
|
|
81
91
|
gitDir,
|
|
82
92
|
};
|
|
83
93
|
}
|
|
@@ -309,7 +319,7 @@ function assertTaskDirectorySafety(directory) {
|
|
|
309
319
|
}
|
|
310
320
|
}
|
|
311
321
|
export async function openTaskStore(cwd, deps = {}) {
|
|
312
|
-
const board = await locateTaskBoard(cwd);
|
|
322
|
+
const board = await locateTaskBoard(cwd, deps.runGit);
|
|
313
323
|
const directory = taskDirectory(board);
|
|
314
324
|
return {
|
|
315
325
|
mode: board.mode,
|
|
@@ -328,7 +338,7 @@ export async function openTaskStore(cwd, deps = {}) {
|
|
|
328
338
|
};
|
|
329
339
|
}
|
|
330
340
|
export async function withLockHeldTaskBoard(cwd, apply, deps = {}, locatedBoard) {
|
|
331
|
-
const board = locatedBoard ?? await locateTaskBoard(cwd);
|
|
341
|
+
const board = locatedBoard ?? await locateTaskBoard(cwd, deps.runGit);
|
|
332
342
|
const directory = taskDirectory(board);
|
|
333
343
|
assertKeiyakuDirectory(board);
|
|
334
344
|
const database = acquireTaskLock(taskLockPath(board));
|
|
@@ -5,6 +5,7 @@ import { createGit, wrapGitError } from "../git/core.js";
|
|
|
5
5
|
import { readLedger } from "./ledger.js";
|
|
6
6
|
import { listContractIds } from "./ledger.js";
|
|
7
7
|
import { deriveContractState, isTerminalState } from "./status/lifecycle.js";
|
|
8
|
+
import { gitCommonRootPath, resolveGitCommonRoot } from "./path-coordinate.js";
|
|
8
9
|
export function findBindEntry(entries) {
|
|
9
10
|
return entries.find((entry) => entry.kind === "bind");
|
|
10
11
|
}
|
|
@@ -58,10 +59,16 @@ export async function stableRepoRoot(cwd) {
|
|
|
58
59
|
catch (error) {
|
|
59
60
|
throw wrapGitError("rev-parse --path-format=absolute --git-common-dir", error, cwd);
|
|
60
61
|
}
|
|
61
|
-
|
|
62
|
+
const stableRoot = gitCommonRootPath(commonDir);
|
|
63
|
+
if (!path.isAbsolute(stableRoot)) {
|
|
62
64
|
throw new FlowError("INTERNAL_STATE", `git common directory is not absolute: ${commonDir}`);
|
|
63
65
|
}
|
|
64
|
-
|
|
66
|
+
try {
|
|
67
|
+
return await resolveGitCommonRoot(commonDir);
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
throw new FlowError("INTERNAL_STATE", `cannot resolve git common directory ${stableRoot}`, { cause: error });
|
|
71
|
+
}
|
|
65
72
|
}
|
|
66
73
|
export async function contractWorktreePathFromBind(cwd, contractId, bind) {
|
|
67
74
|
return path.join(await stableRepoRoot(cwd), ".keiyaku", "wt", bind?.data.place ?? contractId);
|
package/build/flow-error.js
CHANGED
|
@@ -21,6 +21,8 @@ function resolveFlowErrorHint(code, context) {
|
|
|
21
21
|
switch (code) {
|
|
22
22
|
case "NOT_GIT_REPO":
|
|
23
23
|
return "The provided `cwd` is not a git repository.";
|
|
24
|
+
case "GIT_UNAVAILABLE":
|
|
25
|
+
return "Install Git, or use an explicitly bare command that does not require repository authority.";
|
|
24
26
|
case "ACTIVE_KEIYAKU_EXISTS":
|
|
25
27
|
return `Next: open an arc with \`${context.arcToolName}\`, then use \`${context.renewToolName}\` when delivery must follow the current base, or \`${context.closeToolName}\` to settle it.`;
|
|
26
28
|
case "CONTRACT_AMBIGUOUS":
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// Auto-generated by scripts/generate-version.mjs
|
|
2
|
-
export const VERSION = "2.
|
|
2
|
+
export const VERSION = "2.9.1";
|
package/build/git/branches.js
CHANGED
|
@@ -1,17 +1,41 @@
|
|
|
1
1
|
import { simpleGit } from "simple-git";
|
|
2
2
|
import { FlowError } from "../flow-error.js";
|
|
3
|
-
import { createGit, wrapGitError } from "./core.js";
|
|
4
|
-
export async function
|
|
5
|
-
const git = simpleGit(cwd);
|
|
6
|
-
git.env("GIT_TERMINAL_PROMPT", "0");
|
|
7
|
-
git.env("GCM_INTERACTIVE", "Never");
|
|
8
|
-
git.env("GIT_MERGE_AUTOEDIT", "no");
|
|
9
|
-
git.env("LC_ALL", "C");
|
|
3
|
+
import { createGit, errorContainsAnyPattern, isGitExecutableUnavailable, NOT_GIT_REPOSITORY_PATTERNS, wrapGitError, } from "./core.js";
|
|
4
|
+
export async function observeGitRepository(cwd) {
|
|
10
5
|
try {
|
|
11
|
-
|
|
6
|
+
const git = simpleGit(cwd);
|
|
7
|
+
git.env("GIT_TERMINAL_PROMPT", "0");
|
|
8
|
+
git.env("GCM_INTERACTIVE", "Never");
|
|
9
|
+
git.env("GIT_MERGE_AUTOEDIT", "no");
|
|
10
|
+
git.env("LC_ALL", "C");
|
|
11
|
+
git.env("PATH", process.env.PATH ?? "");
|
|
12
|
+
const output = await git.raw(["rev-parse", "--is-inside-work-tree", "--is-bare-repository"]);
|
|
13
|
+
const [insideWorktree, bare] = output.trim().split(/\r?\n/);
|
|
14
|
+
return {
|
|
15
|
+
capability: "available",
|
|
16
|
+
coordinate: insideWorktree === "true" && bare !== "true" ? "repository" : "non-repository",
|
|
17
|
+
};
|
|
12
18
|
}
|
|
13
19
|
catch (err) {
|
|
14
|
-
|
|
20
|
+
if (isGitExecutableUnavailable(err))
|
|
21
|
+
return { capability: "unavailable" };
|
|
22
|
+
if (errorContainsAnyPattern(err, NOT_GIT_REPOSITORY_PATTERNS)) {
|
|
23
|
+
return { capability: "available", coordinate: "non-repository" };
|
|
24
|
+
}
|
|
25
|
+
return { capability: "unknown", error: wrapGitError("check-is-repo", err, cwd) };
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export function assertGitObservation(observation, operation) {
|
|
29
|
+
if (observation.capability === "unavailable") {
|
|
30
|
+
throw new FlowError("GIT_UNAVAILABLE", `Git executable is unavailable; ${operation} requires repository authority.`);
|
|
31
|
+
}
|
|
32
|
+
if (observation.capability === "unknown")
|
|
33
|
+
throw observation.error;
|
|
34
|
+
return observation.coordinate;
|
|
35
|
+
}
|
|
36
|
+
export async function requireGitRepository(cwd, operation) {
|
|
37
|
+
if (assertGitObservation(await observeGitRepository(cwd), operation) === "non-repository") {
|
|
38
|
+
throw new FlowError("NOT_GIT_REPO", `${cwd} is not a git repository`);
|
|
15
39
|
}
|
|
16
40
|
}
|
|
17
41
|
export async function getCurrentBranch(cwd) {
|
package/build/git/core.js
CHANGED
|
@@ -10,6 +10,14 @@ export const MISSING_HEAD_PATTERNS = [
|
|
|
10
10
|
"unknown revision or path not in the working tree",
|
|
11
11
|
"bad revision 'HEAD'",
|
|
12
12
|
];
|
|
13
|
+
/** Git is an optional adapter for directory-local product surfaces. */
|
|
14
|
+
export function isGitExecutableUnavailable(error) {
|
|
15
|
+
const source = (error ?? {});
|
|
16
|
+
if (source.code === "ENOENT")
|
|
17
|
+
return true;
|
|
18
|
+
const message = typeof source.message === "string" ? source.message : String(error);
|
|
19
|
+
return /spawn\s+git\s+ENOENT|git(?:\.exe)?["']?\s*:\s*(?:not found|no such file)/i.test(message);
|
|
20
|
+
}
|
|
13
21
|
export function compactText(input, maxChars = GIT_ERROR_COMPACT_MAX_CHARS) {
|
|
14
22
|
const text = input.trim();
|
|
15
23
|
if (!text)
|
|
@@ -58,5 +66,6 @@ export function createGit(cwd) {
|
|
|
58
66
|
git.env("GCM_INTERACTIVE", "Never");
|
|
59
67
|
git.env("GIT_MERGE_AUTOEDIT", "no");
|
|
60
68
|
git.env("LC_ALL", "C");
|
|
69
|
+
git.env("PATH", process.env.PATH ?? "");
|
|
61
70
|
return git;
|
|
62
71
|
}
|
package/package.json
CHANGED
package/skills/keiyaku/SKILL.md
CHANGED
|
@@ -1,28 +1,45 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: keiyaku
|
|
3
|
-
description: Use when
|
|
3
|
+
description: Use when working in a Keiyaku repository — contract-style coding where changes run on isolated branches and nothing lands without your sign-off.
|
|
4
4
|
allowed-tools: Bash(keiyaku *)
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Keiyaku
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
CLI for contract-style coding in git repos. The mind model, Chainsaw Man rules: you make a **contract** to get work done, you can **call a devil** (akuma) to do it, and words mean nothing — only the commits left behind count. Nothing merges to main without your sign-off.
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
## Mind model
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
keiyaku guide
|
|
15
|
-
keiyaku <command> --help
|
|
16
|
-
```
|
|
13
|
+
Three kinds of objects:
|
|
17
14
|
|
|
18
|
-
|
|
15
|
+
- **task** — a planning note in the repo. No branch, no execution. → `keiyaku-task` skill
|
|
16
|
+
- **contract** — a unit of work with its own branch + worktree. Born by `bind`, dies by `claim` (merged) or `forfeit` (abandoned). Who works inside it — you, an akuma, anyone — is not keiyaku's business; only the result is. → `keiyaku-workflow` skill
|
|
17
|
+
- **akuma / projection** — a callable worker and its live run. `call` starts one, `wait` watches it, `tell` sends more instructions. → `keiyaku-akuma` skill
|
|
19
18
|
|
|
20
|
-
##
|
|
19
|
+
## Quick start
|
|
21
20
|
|
|
22
|
-
|
|
21
|
+
```bash
|
|
22
|
+
keiyaku task add "fix the flaky pump test" # write the idea down → k-3f9a21c4b0d2
|
|
23
|
+
keiyaku bind --task k-3f9a21c4b0d2 # start it: branch + worktree
|
|
24
|
+
cd .keiyaku/wt/<place> # work and commit here — yourself,
|
|
25
|
+
# or: keiyaku call <akuma> "fix it"
|
|
26
|
+
keiyaku status # is anything stuck? what's next?
|
|
27
|
+
keiyaku petition <<'EOF' # done: seal + settle; the oath is required
|
|
28
|
+
## Oath
|
|
29
|
+
I fixed the flaky test; suite is green.
|
|
30
|
+
EOF
|
|
31
|
+
# passes → merged to main; a gate fails → keep working, petition again
|
|
32
|
+
```
|
|
23
33
|
|
|
24
|
-
|
|
34
|
+
That's the whole loop. Skip the task when the work needs no planning:
|
|
35
|
+
`keiyaku bind --place NAME --objective TEXT --scope GLOB --checks CHECK` (or the same as Markdown on stdin — `bind --help` shows the shape).
|
|
25
36
|
|
|
26
|
-
##
|
|
37
|
+
## Getting oriented
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
keiyaku status # the whole board: contracts, running work, ready tasks
|
|
41
|
+
keiyaku guide # long-form mechanics
|
|
42
|
+
keiyaku <command> --help # exact syntax — never guess flags or stdin formats
|
|
43
|
+
```
|
|
27
44
|
|
|
28
|
-
|
|
45
|
+
`status` is where you go when you lose track of anything: projection ids, contract state, what's ready.
|
|
@@ -1,80 +1,48 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: keiyaku-akuma
|
|
3
|
-
description: Use when you need
|
|
3
|
+
description: Use when you need an Akuma to work for you — call a devil, watch it, send more instructions, kill it, or revive it from its record.
|
|
4
4
|
allowed-tools: Bash(keiyaku *)
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Keiyaku Akuma
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
keiyaku akuma ls
|
|
11
|
-
keiyaku <command> --help
|
|
12
|
-
```
|
|
9
|
+
Running devils. Contract lifecycle = `keiyaku-workflow` skill.
|
|
13
10
|
|
|
14
|
-
|
|
11
|
+
## Mind model
|
|
15
12
|
|
|
16
|
-
|
|
13
|
+
- **profile** — a devil you can call (`keiyaku akuma ls`). Markdown file: frontmatter = provider/model config, body = its instructions. Project `.keiyaku/akuma/` > user `~/.keiyaku/akuma/` > builtin.
|
|
14
|
+
- **projection** (`name/8hex`) — one live run of a devil. This is the id for `wait` / `tell` / `kill`.
|
|
15
|
+
- **artifact** (`rsp_…`) — what a finished run left behind. This is the id for `revive`.
|
|
17
16
|
|
|
18
|
-
|
|
19
|
-
| --- | --- | --- |
|
|
20
|
-
| projection id | live who | `wait`, `tell`, `status` |
|
|
21
|
-
| artifact id | saved answer | `revive` |
|
|
17
|
+
Rule of thumb: alive → projection id; want to continue a finished/dead one → artifact id.
|
|
22
18
|
|
|
23
|
-
##
|
|
19
|
+
## Common usage
|
|
24
20
|
|
|
25
21
|
```bash
|
|
26
|
-
keiyaku
|
|
27
|
-
keiyaku call NAME
|
|
28
|
-
keiyaku call NAME
|
|
29
|
-
keiyaku
|
|
30
|
-
keiyaku
|
|
31
|
-
keiyaku
|
|
32
|
-
keiyaku
|
|
33
|
-
keiyaku tell <
|
|
34
|
-
keiyaku
|
|
35
|
-
keiyaku
|
|
36
|
-
keiyaku
|
|
37
|
-
keiyaku
|
|
38
|
-
keiyaku revive ARTIFACT_ID - < cont.md # explicit `-` stdin; blank fails
|
|
22
|
+
keiyaku akuma ls # who's callable
|
|
23
|
+
keiyaku call NAME "do the thing" # run in foreground until it finishes
|
|
24
|
+
keiyaku call NAME --wait 10m "do the thing" # bounded: after 10m you get a snapshot, it keeps running
|
|
25
|
+
keiyaku call NAME --detach "do the thing" # fire and forget; note the projection id
|
|
26
|
+
keiyaku call NAME - < prompt.md # long prompt: literal `-` reads stdin
|
|
27
|
+
keiyaku wait <proj-id> # join a running one; prints its activity
|
|
28
|
+
keiyaku wait <proj-id> --timeout 5m # peek for 5m; timeout = snapshot, not death
|
|
29
|
+
keiyaku tell <proj-id> "also do X" # drop instructions in its mailbox
|
|
30
|
+
keiyaku tell <proj-id> --wait 5m "also do X" # tell, then watch the response
|
|
31
|
+
keiyaku revive rsp_XXXX "continue..." # new run continuing from a record
|
|
32
|
+
keiyaku kill <proj-id> # stop it; record survives, revive still works
|
|
33
|
+
keiyaku status # forgot an id? it's on the board
|
|
39
34
|
```
|
|
40
35
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
## Timeouts (do not confuse)
|
|
44
|
-
|
|
45
|
-
| Surface | Behavior |
|
|
46
|
-
| --- | --- |
|
|
47
|
-
| `call` / `revive` | **No** CLI `--timeout`. Foreground call joins until terminal or signal. |
|
|
48
|
-
| `call --detach` | Returns after launch. Bounded watching is done with `wait --timeout`. |
|
|
49
|
-
| `wait --timeout DURATION` | Only wait command. Forms: `45s`, `5m`, `2h` (max `24h`). Omit = wait until terminal. Timeout exit is a snapshot, not death. |
|
|
50
|
-
| Worker kill (env) | `KEIYAKU_SUBAGENT_EXEC_TIMEOUT_MS` (default 2700000), `KEIYAKU_SUBAGENT_EXEC_IDLE_TIMEOUT_MS` (default 600000). Not CLI flags. |
|
|
51
|
-
| Startup stall | Adoption timeout → wait/status can show `startup timed out` (launch never adopted). |
|
|
52
|
-
|
|
53
|
-
## Tell vs wait
|
|
54
|
-
|
|
55
|
-
- `tell` writes mailbox intent and may wake when eligible. It does **not** join.
|
|
56
|
-
- After tell: run `wait` (optionally `--timeout`) for outcome and activity.
|
|
57
|
-
- While pending tells exist, wait header can show `N tells waiting`.
|
|
58
|
-
|
|
59
|
-
## Activity list
|
|
60
|
-
|
|
61
|
-
No separate `activity` command.
|
|
62
|
-
|
|
63
|
-
- **`wait`**: full activity timeline (tools/turns) under the projection header.
|
|
64
|
-
- **`status`**: under `akuma` section, compact per-projection activity summary + tell counts.
|
|
65
|
-
|
|
66
|
-
Lost the outer handle → `status` to find projection id → `wait` for the list.
|
|
67
|
-
|
|
68
|
-
## Infrastructure
|
|
69
|
-
|
|
70
|
-
- `-C` / `--cwd DIR` — effective command directory (all akuma commands)
|
|
36
|
+
## Expectations
|
|
71
37
|
|
|
72
|
-
|
|
38
|
+
- `tell` is silent on success: exit 0, zero output. Silence **is** the receipt — do not re-send. Effects show up in `wait`/`status`.
|
|
39
|
+
- Body is required for `call`/`tell`. Piping stdin without a literal `-` does nothing.
|
|
40
|
+
- A `--wait`/`--timeout` expiry prints a live snapshot and exits 0 — the run continues; rejoin with `wait` anytime.
|
|
41
|
+
- `wait` is repeatable: on a finished run it shows the final transcript again.
|
|
42
|
+
- `--contract ADDR` attaches a call to a contract (by place, slug, or full id); `--bare` skips attachment explicitly. `call` only — `revive` takes neither.
|
|
73
43
|
|
|
74
|
-
|
|
75
|
-
- `--incognito` — no response artifact, no later revive (call only)
|
|
76
|
-
- `@addr` / `--contract` — attach commission/arc context when present (call, wait, tell)
|
|
77
|
-
- `--bare` — skip contract attachment only (call only)
|
|
78
|
-
- `--effort LEVEL` — override opaque provider-native effort value (call, revive, tell); must match profile allowlist when set
|
|
44
|
+
## Occasional flags
|
|
79
45
|
|
|
80
|
-
|
|
46
|
+
- `--incognito` — no artifact written, no revive possible (call)
|
|
47
|
+
- `--effort LEVEL` — provider effort override; needs a model set (call, revive, tell)
|
|
48
|
+
- `-C DIR` — run relative to another directory (all akuma commands)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: keiyaku-task
|
|
3
|
+
description: Use when planning work in a Keiyaku repo — add tasks, wire dependencies, track what's ready, promote a task into a contract.
|
|
4
|
+
allowed-tools: Bash(keiyaku *)
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Keiyaku Task
|
|
8
|
+
|
|
9
|
+
Planning notes that live in the repo. Executing them is `keiyaku-workflow` (`bind --task`).
|
|
10
|
+
|
|
11
|
+
## Mind model
|
|
12
|
+
|
|
13
|
+
A task is a Markdown file in `.keiyaku/tasks/` — id `k-<12hex>`, priority 0–3 (0 highest, default 2), optional body. It has no branch and runs nothing; it's the queue you pull from. States:
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
open ⇄ in_progress → done | drop
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
**Ready** = open + every `needs` dependency done. Ready is computed for you — `status` and `task ls` surface it.
|
|
20
|
+
|
|
21
|
+
## Common usage
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
keiyaku task add "fix the flaky pump test" # body: pipe stdin for details
|
|
25
|
+
keiyaku task add "title" --pri 1 --needs k-aaa # priority + dependency at birth
|
|
26
|
+
keiyaku task ls # all open, priority order
|
|
27
|
+
keiyaku task view k-xxxx # full detail incl. needs/blocks
|
|
28
|
+
keiyaku task start k-xxxx # mark in_progress (start ≠ execute)
|
|
29
|
+
keiyaku task stop k-xxxx # back to open
|
|
30
|
+
keiyaku task done k-xxxx
|
|
31
|
+
keiyaku task drop k-xxxx # won't do; kept in history, not deleted
|
|
32
|
+
keiyaku task update k-xxxx --pri 0 # also --title, --body TEXT|-, --needs,
|
|
33
|
+
# --drop-needs — see --help
|
|
34
|
+
keiyaku task doctor # check board integrity
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Dependencies
|
|
38
|
+
|
|
39
|
+
`--needs` stores the forward edge; `blocks` on the other task is derived automatically. Cycles are rejected at write time. Use needs to express "don't start B before A lands" — nothing more.
|
|
40
|
+
|
|
41
|
+
## Promoting to a contract
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
keiyaku bind --task k-xxxx
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Only a **ready** task binds. in_progress → `task stop` first; blocked → finish its needs first. Binding links the task to the contract; the contract's intent you still write yourself — the title doesn't auto-expand into a spec.
|
|
48
|
+
|
|
49
|
+
## Gotcha
|
|
50
|
+
|
|
51
|
+
Task files are ordinary tracked files: checking out another branch shows *that branch's* board. Don't panic when states differ across branches — you're reading a different branch's truth, and the old one is still in git.
|