@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.
Files changed (47) hide show
  1. package/build/.tsbuildinfo +1 -1
  2. package/build/cli/commands/akuma/list/handler.js +1 -1
  3. package/build/cli/commands/akuma/view/handler.js +1 -1
  4. package/build/cli/commands/contract/amend/handler.js +1 -1
  5. package/build/cli/commands/contract/arc/handler.js +1 -1
  6. package/build/cli/commands/contract/bind/handler.js +1 -1
  7. package/build/cli/commands/contract/forfeit/handler.js +1 -1
  8. package/build/cli/commands/contract/log/handler.js +1 -1
  9. package/build/cli/commands/contract/petition/handler.js +1 -1
  10. package/build/cli/commands/contract/renew/handler.js +1 -1
  11. package/build/cli/commands/projection/call/handler.js +5 -2
  12. package/build/cli/commands/projection/kill/handler.js +3 -1
  13. package/build/cli/commands/projection/revive/handler.js +6 -3
  14. package/build/cli/commands/projection/status/handler.js +4 -2
  15. package/build/cli/commands/projection/tell/handler.js +3 -1
  16. package/build/cli/commands/projection/wait/handler.js +3 -1
  17. package/build/cli/commands/shared.js +5 -5
  18. package/build/cli/commands/system/completion/handler.js +1 -1
  19. package/build/cli/parse-flags.js +197 -0
  20. package/build/cli/parse-metadata.js +129 -0
  21. package/build/cli/parse-selectors.js +65 -0
  22. package/build/cli/parse.js +19 -373
  23. package/build/cli/skills-install.js +1 -1
  24. package/build/config/akuma-loader.js +5 -2
  25. package/build/core/amend.js +2 -5
  26. package/build/core/arc.js +2 -4
  27. package/build/core/call/call.js +7 -2
  28. package/build/core/call/context.js +27 -14
  29. package/build/core/call-persist.js +9 -2
  30. package/build/core/path-coordinate.js +27 -0
  31. package/build/core/petition-run.js +2 -5
  32. package/build/core/projection-coordinate.js +14 -2
  33. package/build/core/renew.js +2 -4
  34. package/build/core/status/board.js +6 -3
  35. package/build/core/task-contract.js +13 -0
  36. package/build/core/task-git-runtime.js +5 -2
  37. package/build/core/task-git-store.js +31 -21
  38. package/build/core/worktree-path.js +9 -2
  39. package/build/flow-error.js +2 -0
  40. package/build/generated/version.js +1 -1
  41. package/build/git/branches.js +33 -9
  42. package/build/git/core.js +9 -0
  43. package/package.json +1 -1
  44. package/skills/keiyaku/SKILL.md +30 -13
  45. package/skills/keiyaku-akuma/SKILL.md +30 -62
  46. package/skills/keiyaku-task/SKILL.md +51 -0
  47. 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
- if (bare !== "false" || !commonDir || !path.isAbsolute(commonDir)) {
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
- const physicalRoot = path.dirname(commonDir);
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 },
@@ -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, isGitRepo } from "../git/branches.js";
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
- if (!(await isGitRepo(input.cwd))) {
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 { isGitRepo } from "../../git/branches.js";
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
- if (!(await isGitRepo(cwd))) {
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: null,
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 { isGitRepo } from "../git/branches.js";
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
- if (!(await isGitRepo(cwd)))
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 async function locateTaskBoard(cwd) {
62
- const probe = defaultRunGit(cwd, ["rev-parse", "--is-inside-work-tree", "--is-bare-repository"]);
63
- if (probe.error) {
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
- return { mode: "directory", root: nearestDirectoryBoard(cwd) };
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 = runGit(cwd, ["rev-parse", "--path-format=absolute", "--show-toplevel", "--git-common-dir"], undefined);
74
- const [checkoutRoot, gitDir] = facts.trimEnd().split(/\r?\n/);
75
- if (!checkoutRoot || !gitDir || !path.isAbsolute(checkoutRoot) || !path.isAbsolute(gitDir)) {
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: path.dirname(gitDir),
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
- if (!path.isAbsolute(commonDir)) {
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
- return path.dirname(commonDir);
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);
@@ -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.8.5";
2
+ export const VERSION = "2.9.1";
@@ -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 isGitRepo(cwd) {
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
- return await git.checkIsRepo();
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
- throw wrapGitError("check-is-repo", err, cwd);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrosheep/keiyaku",
3
- "version": "2.9.0",
3
+ "version": "2.9.2",
4
4
  "description": "CLI for running iterative keiyaku workflows with Codex subagents.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -1,28 +1,45 @@
1
1
  ---
2
2
  name: keiyaku
3
- description: Use when you need Keiyaku tools to coordinate Akuma work, organize parallel operations, review diffs, and create or manage worktrees.
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
- Keiyaku is a local CLI for contract-style coding work in git repositories.
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
- Prefer the CLI as the source of truth:
11
+ ## Mind model
12
12
 
13
- ```bash
14
- keiyaku guide
15
- keiyaku <command> --help
16
- ```
13
+ Three kinds of objects:
17
14
 
18
- Akuma call/tell/wait/revive lifecycle is owned by the `keiyaku-akuma` skill. Load that skill for helper execution.
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
- ## Basic Flow
19
+ ## Quick start
21
20
 
22
- The small/default workflow is bind, commit delivery work, then petition. Bind supplies derived default intent a0. Arc is optional for multiple intent iterations: the first explicit arc seals a0, and a later arc seals the current explicit arc. Renew when the base drifts; renew and petition seal the current a0 or open explicit arc. After an explicit arc closes, loose commits require another arc before renew or petition. Amend when the governing contract changes.
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
- Use `status` to inspect the Kanshi board and `forfeit` to archive abandoned work. Consult command help for their syntax and flags.
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
- ## Help
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
- Use `keiyaku <command> --help` for command syntax and flags, and `keiyaku guide` for long-form mechanics. Do not guess Markdown input sections or option defaults.
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 to dispatch an Akuma to do work for you — call a demon, check on it, yell more instructions, or continue from a prior turn.
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
- ```bash
10
- keiyaku akuma ls
11
- keiyaku <command> --help
12
- ```
9
+ Running devils. Contract lifecycle = `keiyaku-workflow` skill.
13
10
 
14
- Contract work = `keiyaku` skill. This skill = helpers only.
11
+ ## Mind model
15
12
 
16
- ## Two ids
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
- | Id | Means | Use with |
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
- ## Daily path
19
+ ## Common usage
24
20
 
25
21
  ```bash
26
- keiyaku call NAME "task..." # required literal BODY; launch + foreground join
27
- keiyaku call NAME --detach "task..." # return after launch; keep projection id
28
- keiyaku call NAME - < prompt.md # only literal `-` opens stdin
29
- keiyaku wait <projection-id> # join until terminal; prints activity list
30
- keiyaku wait <projection-id> --timeout 5m # bounded join; timeout keeps projection alive
31
- keiyaku tell <projection-id> "more..." # required literal BODY; mailbox only
32
- keiyaku tell <projection-id> - < direction.md # explicit stdin body; `-` is never delivered
33
- keiyaku tell <projection-id> --wait 5m "more..." # no mandatory `--` before BODY
34
- keiyaku wait <projection-id> --timeout 10m # after tell, rejoin to see progress/outcome
35
- keiyaku status # board + compact activity summary; rediscover ids
36
- keiyaku revive ARTIFACT_ID # omit body default continuation
37
- keiyaku revive ARTIFACT_ID "continue..." # literal BODY
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
- Long body: `keiyaku call NAME - < prompt.md` or `keiyaku tell PROJECTION - < direction.md`. Never omit call/tell body and hope for piped stdin.
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
- ## Other flags
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
- - `--akuma NAME` — start a session with the named agent (call only)
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
- Profiles: `keiyaku akuma ls|view`; user `<KEIYAKU_HOME>/akuma/`, project `.keiyaku/akuma/`.
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.