@astrosheep/keiyaku 4.0.2 → 4.0.3

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.
@@ -31,14 +31,6 @@ Bind is the last step of an investigation, never the first step of an idea.
31
31
  now. Cannot draft means unresolved design — "the worker will sort out the
32
32
  docs" is a design gap in disguise.
33
33
 
34
- Split complex work wherever independently acceptable delivery boundaries
35
- exist. When a complex Keiyaku cannot be split without breaking one acceptance
36
- boundary, bind one Contract and plan its fulfillment as explicit arcs. Treat
37
- each arc as a chapter as in a work of literature, not as a task sequence.
38
- Commission one current chapter at a time; never hand the whole undifferentiated
39
- Contract to one Deliverer and trust it to finish everything in one pass.
40
- Continue with `keiyaku-workflow` for the arc document and command.
41
-
42
34
  Two tests close the gate:
43
35
 
44
36
  - **Substitution.** Two workers who never met each deliver test-green from
@@ -67,8 +59,7 @@ Design read the same without a sentence here, delete it.>
67
59
 
68
60
  ## Objective
69
61
  <One observable end-state, judged done/not-done without reading Design.
70
- If "and" joins independently acceptable outcomes, bind two Contracts. A
71
- single outcome may still require several arcs during fulfillment.>
62
+ If you need "and", bind two Contracts.>
72
63
 
73
64
  ## Design
74
65
  <The closed decisions. A statement belongs here exactly when a test-green
@@ -121,17 +121,11 @@ the test. Give each resulting Contract coherent terms. Connect them with
121
121
  `--after` only when one must proceed from another's settled result or their
122
122
  intended work is unsafe to run concurrently.
123
123
 
124
- When a complex Keiyaku cannot be split without breaking one acceptance
125
- boundary, organize its fulfillment into arcs. Do not hand the whole
126
- undifferentiated Contract to one Deliverer and trust one pass to finish it.
127
- Record and work one current chapter at a time.
128
-
129
- An arc is a chapter as in a work of literature: one named part of the
130
- delivery's story, not a task list, progress slice, or claim that the work is
131
- mechanically sequential. Its title names the chapter, Objective states that
132
- chapter's aim, and Brief commissions work for that chapter. When an Arc is
133
- active, stay within that current chapter. `.keiyaku/KEIYAKU.md` renders the
134
- current Arc.
124
+ When one acceptance boundary still spans several coherent chapters,
125
+ organize them as arcs. An arc is a chapter of the delivery's
126
+ story, not a task list: its title names the chapter, Objective is the
127
+ chapter's aim, Brief opens the next chapter. When an Arc is active, stay
128
+ within that current chapter. `.keiyaku/KEIYAKU.md` renders the current Arc.
135
129
  Record the next chapter before entering it:
136
130
 
137
131
  ```bash
@@ -580,8 +580,16 @@ export async function driveAkumaBody(launch, adapter, runtime = defaultRuntime()
580
580
  }
581
581
  }
582
582
  catch (error) {
583
- if (await heartExists(launch.paths))
584
- throw error;
583
+ if (!await heartExists(launch.paths))
584
+ return;
585
+ try {
586
+ await leash.sealIfUnborn(launch.paths, {
587
+ evidence: error instanceof Error ? error.message : String(error),
588
+ at: runtime.now(),
589
+ });
590
+ }
591
+ catch { /* the original Body failure remains authoritative */ }
592
+ throw error;
585
593
  }
586
594
  finally {
587
595
  leash.release();
@@ -21,6 +21,7 @@ export declare class HeldAkumaLeash {
21
21
  evidence: string;
22
22
  at: string;
23
23
  }>): Promise<"born" | "sealed">;
24
+ readSeal(): ReturnType<typeof sealFact>;
24
25
  clearPause(paths: AkumaPaths): Promise<void>;
25
26
  clearStop(paths: AkumaPaths): Promise<void>;
26
27
  recordBody(paths: AkumaPaths, input: Readonly<{
@@ -136,6 +136,8 @@ export class HeldAkumaLeash {
136
136
  }));
137
137
  }
138
138
  async sealIfUnborn(paths, input) {
139
+ if (sealExists(this.database))
140
+ return "sealed";
139
141
  if (await withHeart(paths, (heart) => soulFact(heart)) !== null)
140
142
  return "born";
141
143
  insertSealFact(this.database, input);
@@ -144,6 +146,9 @@ export class HeldAkumaLeash {
144
146
  this.database.close();
145
147
  return "sealed";
146
148
  }
149
+ readSeal() {
150
+ return sealFact(this.database);
151
+ }
147
152
  async clearPause(paths) { await withHeart(paths, deletePauseControl); }
148
153
  async clearStop(paths) { await withHeart(paths, deleteStopControl); }
149
154
  async recordBody(paths, input) {
@@ -1,4 +1,4 @@
1
- import { HeldAkumaLeash, initializeHeart, readHeart, readSoul, } from "./heart/index.js";
1
+ import { HeldAkumaLeash, initializeHeart, readHeart, readSeal, readSoul, } from "./heart/index.js";
2
2
  import { allocateAkumaDirectory, } from "./identity.js";
3
3
  import { abortableDelay } from "./abort.js";
4
4
  const POLL_MS = 25;
@@ -10,15 +10,38 @@ async function settleTimedOutBirth(paths) {
10
10
  const leash = await HeldAkumaLeash.try(paths);
11
11
  if (leash === null)
12
12
  return null;
13
- const result = await leash.sealIfUnborn(paths, { evidence: "call-timeout", at: new Date().toISOString() });
14
- if (result === "sealed")
15
- throw new Error("Akuma body failed before birth");
16
- leash.release();
13
+ let result;
14
+ try {
15
+ result = await leash.sealIfUnborn(paths, {
16
+ evidence: "call-timeout",
17
+ at: new Date().toISOString(),
18
+ });
19
+ }
20
+ finally {
21
+ leash.release();
22
+ }
23
+ if (result === "sealed") {
24
+ const seal = await readSeal(paths);
25
+ throw new Error(seal?.evidence === "call-timeout" || seal === null
26
+ ? "Akuma body failed before birth"
27
+ : seal.evidence);
28
+ }
17
29
  const soul = await readSoul(paths);
18
30
  if (soul === null)
19
31
  throw new Error("Akuma birth settled without a soul");
20
32
  return soul;
21
33
  }
34
+ async function observedBirthFailure(paths) {
35
+ const leash = await HeldAkumaLeash.try(paths);
36
+ if (leash === null)
37
+ return null;
38
+ try {
39
+ return leash.readSeal()?.evidence ?? null;
40
+ }
41
+ finally {
42
+ leash.release();
43
+ }
44
+ }
22
45
  async function awaitBirth(paths, signal) {
23
46
  const deadline = performance.now() + BIRTH_TIMEOUT_MS;
24
47
  for (;;) {
@@ -26,6 +49,9 @@ async function awaitBirth(paths, signal) {
26
49
  const soul = await readSoul(paths);
27
50
  if (soul !== null)
28
51
  return soul;
52
+ const failure = await observedBirthFailure(paths);
53
+ if (failure !== null)
54
+ throw new Error(failure);
29
55
  if (performance.now() >= deadline) {
30
56
  const settled = await settleTimedOutBirth(paths);
31
57
  if (settled !== null)
@@ -63,7 +89,10 @@ async function sealLocalFailure(allocated, error) {
63
89
  if (leash === null)
64
90
  return;
65
91
  try {
66
- await leash.sealIfUnborn(allocated.paths, { evidence: diagnostic(error), at: new Date().toISOString() });
92
+ await leash.sealIfUnborn(allocated.paths, {
93
+ evidence: diagnostic(error),
94
+ at: new Date().toISOString(),
95
+ });
67
96
  }
68
97
  finally {
69
98
  leash.release();
@@ -1,4 +1,4 @@
1
- import { CliUsageError, isBlankInput, usageLine } from "../usage.js";
1
+ import { CliUsageError, isBlankInput, usageLine, withJsonAutomationHelp } from "../usage.js";
2
2
  import { archetypeName, parseAkuId } from "../../akuma/identity.js";
3
3
  import { parseDuration as decodeDuration } from "../../duration.js";
4
4
  import { parseAkumaAlias, parseAkumaGlob } from "../../identity/selector.js";
@@ -74,7 +74,7 @@ export function renderAkumaRootRows() {
74
74
  }
75
75
  export function renderAkumaHelp(action) {
76
76
  const spec = AKUMA_COMMAND_SPECS[action];
77
- return `${spec.purpose}\n\n${usageLine(spec.usage)}${spec.details === undefined ? "" : `\n\n${spec.details}`}`;
77
+ return withJsonAutomationHelp(`${spec.purpose}\n\n${usageLine(spec.usage)}${spec.details === undefined ? "" : `\n\n${spec.details}`}`);
78
78
  }
79
79
  export function renderAkumaUsage(action) {
80
80
  return usageLine(AKUMA_COMMAND_SPECS[action].usage);
@@ -1,7 +1,7 @@
1
1
  import { dirname, resolve } from "node:path";
2
2
  import { fileURLToPath } from "node:url";
3
3
  import { runProcess } from "../../runtime/proc/run.js";
4
- import { CliUsageError, usageLine } from "../usage.js";
4
+ import { CliUsageError, usageLine, withJsonAutomationHelp } from "../usage.js";
5
5
  export const HARNESS_NAMES = ["codex", "claude", "opencode", "pi"];
6
6
  export const INSTALL_USAGE = "install <codex|claude|opencode|pi> [--json]\n install --all [--json]";
7
7
  function isHarness(value) {
@@ -43,7 +43,7 @@ export function parseInstallCommand(argv) {
43
43
  };
44
44
  }
45
45
  export function renderInstallHelp() {
46
- return `Install the Keiyaku skills into one or more agent harnesses.\n\n${usageLine(INSTALL_USAGE)}`;
46
+ return withJsonAutomationHelp(`Install the Keiyaku skills into one or more agent harnesses.\n\n${usageLine(INSTALL_USAGE)}`);
47
47
  }
48
48
  const INSTALL_TIMEOUT_MS = 5 * 60 * 1000;
49
49
  const ASSET_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "../../../integrations/marketplace");
@@ -1,4 +1,4 @@
1
- import { CliUsageError, isBlankInput, usageLine } from "../usage.js";
1
+ import { CliUsageError, isBlankInput, usageLine, withJsonAutomationHelp } from "../usage.js";
2
2
  import { parseTaskQueryExpression, validateTaskLimit, validateTaskParent, } from "./task-query.js";
3
3
  const COMMON = { json: "boolean" };
4
4
  const TASK_COMMAND_SPECS = {
@@ -46,14 +46,14 @@ export function isTaskAction(value) {
46
46
  export function renderTaskHelp(action) {
47
47
  if (action !== undefined) {
48
48
  const spec = TASK_COMMAND_SPECS[action];
49
- return `${spec.purpose}\n\n${usageLine(spec.usage)}`;
49
+ return withJsonAutomationHelp(`${spec.purpose}\n\n${usageLine(spec.usage)}`);
50
50
  }
51
- return [
51
+ return withJsonAutomationHelp([
52
52
  "usage: keiyaku task <command> ...",
53
53
  "",
54
54
  "commands:",
55
55
  ...Object.values(TASK_COMMAND_SPECS).flatMap((spec) => spec.usage.split("\n").map((line) => ` ${line}`).concat(` ${spec.purpose}`)),
56
- ].join("\n");
56
+ ].join("\n"));
57
57
  }
58
58
  export function renderTaskUsage(action) {
59
59
  return usageLine(TASK_COMMAND_SPECS[action].usage);
@@ -2,11 +2,11 @@ import { isTaskAction, parseTaskCommand, renderTaskUsage, } from "./commands/tas
2
2
  import { isAkumaAction, parseAkumaCatalogPath, parseAkumaCommand, renderAkumaRootRows, renderAkumaUsage, } from "./commands/akuma.js";
3
3
  import { INSTALL_USAGE, parseInstallCommand, renderInstallHelp } from "./commands/install.js";
4
4
  import { AMEND_MINIMAL_STDIN_HELP, CONTRACT_COMMAND_SPECS } from "./commands/contract.js";
5
- import { CliUsageError, isBlankInput, usageLine } from "./usage.js";
5
+ import { CliUsageError, isBlankInput, usageLine, withJsonAutomationHelp } from "./usage.js";
6
6
  export { CliUsageError } from "./usage.js";
7
7
  const ROOT_USAGE = "usage: keiyaku [-C <path>] [--repo <path>] <command> [<contract>|@<contract>] [--flag ...] [-]";
8
8
  export function renderRootHelp() {
9
- return [
9
+ return withJsonAutomationHelp([
10
10
  ROOT_USAGE,
11
11
  "",
12
12
  "global options:",
@@ -20,12 +20,12 @@ export function renderRootHelp() {
20
20
  " task ...",
21
21
  " Task coordination; see `keiyaku task --help`.",
22
22
  ...renderAkumaRootRows(),
23
- ].join("\n");
23
+ ].join("\n"));
24
24
  }
25
25
  export function renderContractHelp(command) {
26
26
  const spec = CONTRACT_COMMAND_SPECS[command];
27
27
  const help = `${spec.purpose}\n\n${usageLine(spec.usage)}`;
28
- return command === "amend" ? `${help}\n\n${AMEND_MINIMAL_STDIN_HELP}` : help;
28
+ return withJsonAutomationHelp(command === "amend" ? `${help}\n\n${AMEND_MINIMAL_STDIN_HELP}` : help);
29
29
  }
30
30
  function contractUsage(command) {
31
31
  return usageLine(CONTRACT_COMMAND_SPECS[command].usage);
@@ -3,5 +3,7 @@ export declare class CliUsageError extends Error {
3
3
  readonly projection?: string | undefined;
4
4
  constructor(diagnostic: string, projection?: string | undefined);
5
5
  }
6
+ export declare const JSON_AUTOMATION_HELP = "--json is only for automation script input/output and should not be used for daily interactive use.";
7
+ export declare function withJsonAutomationHelp(help: string): string;
6
8
  export declare function isBlankInput(value: string): boolean;
7
9
  export declare function usageLine(usage: string): string;
@@ -8,6 +8,10 @@ export class CliUsageError extends Error {
8
8
  this.name = "CliUsageError";
9
9
  }
10
10
  }
11
+ export const JSON_AUTOMATION_HELP = "--json is only for automation script input/output and should not be used for daily interactive use.";
12
+ export function withJsonAutomationHelp(help) {
13
+ return help.includes("--json") ? `${help}\n\n${JSON_AUTOMATION_HELP}` : help;
14
+ }
11
15
  export function isBlankInput(value) {
12
16
  return value.trim().length === 0;
13
17
  }
@@ -39,6 +39,7 @@ function batchObjectReader(repository) {
39
39
  const child = spawn("git", ["cat-file", "--batch"], {
40
40
  cwd: repository.effectiveCwd,
41
41
  stdio: ["pipe", "pipe", "pipe"],
42
+ windowsHide: true,
42
43
  });
43
44
  const cursor = streamCursor(child.stdout);
44
45
  const cache = new Map();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrosheep/keiyaku",
3
- "version": "4.0.2",
3
+ "version": "4.0.3",
4
4
  "files": [
5
5
  "build"
6
6
  ],