@astrosheep/keiyaku 4.0.0 → 4.0.1
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 +2 -9
- package/build/integrations/marketplace/plugins/keiyaku/.claude-plugin/plugin.json +2 -2
- package/build/integrations/marketplace/plugins/keiyaku/.codex-plugin/plugin.json +4 -4
- package/build/integrations/marketplace/plugins/keiyaku/opencode.js +2 -2
- package/build/integrations/marketplace/plugins/keiyaku/package.json +2 -2
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku/SKILL.md +11 -3
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-akuma/SKILL.md +9 -6
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-bind/SKILL.md +94 -0
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-bind/agents/openai.yaml +4 -0
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-task/SKILL.md +41 -6
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-workflow/SKILL.md +162 -45
- package/build/src/akuma/akuma.d.ts +33 -25
- package/build/src/akuma/akuma.js +93 -61
- package/build/src/akuma/archetype.d.ts +8 -0
- package/build/src/akuma/archetype.js +28 -9
- package/build/src/akuma/body.js +196 -84
- package/build/src/akuma/coordinate.d.ts +9 -0
- package/build/src/akuma/coordinate.js +22 -0
- package/build/src/akuma/heart/facts.d.ts +67 -15
- package/build/src/akuma/heart/facts.js +3 -3
- package/build/src/akuma/heart/index.d.ts +41 -29
- package/build/src/akuma/heart/index.js +72 -193
- package/build/src/akuma/heart/rows.d.ts +30 -53
- package/build/src/akuma/heart/rows.js +91 -111
- package/build/src/akuma/heart/schema.d.ts +1 -1
- package/build/src/akuma/heart/schema.js +55 -8
- package/build/src/akuma/heart/storage.d.ts +33 -0
- package/build/src/akuma/heart/storage.js +164 -0
- package/build/src/akuma/heart/tells.d.ts +12 -0
- package/build/src/akuma/heart/tells.js +79 -0
- package/build/src/akuma/heart/timeline.d.ts +15 -0
- package/build/src/akuma/heart/timeline.js +86 -0
- package/build/src/akuma/index.d.ts +4 -2
- package/build/src/akuma/index.js +1 -1
- package/build/src/akuma/projection.d.ts +97 -0
- package/build/src/akuma/projection.js +145 -0
- package/build/src/akuma/provider.d.ts +57 -11
- package/build/src/akuma/provider.js +57 -37
- package/build/src/akuma/providers/{claude.d.ts → claude/events.d.ts} +9 -41
- package/build/src/akuma/providers/claude/events.js +185 -0
- package/build/src/akuma/providers/claude/index.d.ts +46 -0
- package/build/src/akuma/providers/claude/index.js +265 -0
- package/build/src/akuma/providers/claude/input.d.ts +13 -0
- package/build/src/akuma/providers/claude/input.js +109 -0
- package/build/src/akuma/providers/codex-app-server/events.d.ts +1 -1
- package/build/src/akuma/providers/codex-app-server/events.js +12 -12
- package/build/src/akuma/providers/codex-app-server/index.d.ts +13 -11
- package/build/src/akuma/providers/codex-app-server/index.js +73 -22
- package/build/src/akuma/providers/index.js +22 -8
- package/build/src/akuma/providers/opencode-sdk/events.d.ts +48 -0
- package/build/src/akuma/providers/opencode-sdk/events.js +116 -0
- package/build/src/akuma/providers/opencode-sdk/index.d.ts +32 -0
- package/build/src/akuma/providers/opencode-sdk/index.js +203 -0
- package/build/src/akuma/providers/opencode-sdk/session.d.ts +41 -0
- package/build/src/akuma/providers/opencode-sdk/session.js +62 -0
- package/build/src/akuma/providers/pi/events.d.ts +33 -0
- package/build/src/akuma/providers/pi/events.js +128 -0
- package/build/src/akuma/providers/pi/index.d.ts +11 -0
- package/build/src/akuma/providers/pi/index.js +162 -0
- package/build/src/akuma/requests.d.ts +1 -0
- package/build/src/akuma/requests.js +5 -2
- package/build/src/alias/index.d.ts +4 -3
- package/build/src/alias/index.js +2 -3
- package/build/src/body/render.js +2 -1
- package/build/src/body/shape.js +7 -1
- package/build/src/body/verification.js +19 -4
- package/build/src/cli/accepted.d.ts +2 -0
- package/build/src/cli/accepted.js +2 -1
- package/build/src/cli/commands/akuma-invoke.d.ts +42 -18
- package/build/src/cli/commands/akuma-invoke.js +94 -50
- package/build/src/cli/commands/akuma.d.ts +15 -7
- package/build/src/cli/commands/akuma.js +94 -56
- package/build/src/cli/commands/contract.d.ts +142 -0
- package/build/src/cli/commands/contract.js +14 -0
- package/build/src/cli/commands/install.js +2 -0
- package/build/src/cli/commands/task-invoke.d.ts +3 -1
- package/build/src/cli/commands/task-invoke.js +27 -2
- package/build/src/cli/commands/task.js +2 -2
- package/build/src/cli/invoke.d.ts +6 -1
- package/build/src/cli/invoke.js +241 -43
- package/build/src/cli/main.js +20 -1
- package/build/src/cli/parse.d.ts +13 -116
- package/build/src/cli/parse.js +51 -78
- package/build/src/cli/render/akuma-tool.d.ts +2 -0
- package/build/src/cli/render/akuma-tool.js +6 -2
- package/build/src/cli/render/akuma.d.ts +2 -1
- package/build/src/cli/render/akuma.js +157 -217
- package/build/src/cli/render/catalog.d.ts +2 -0
- package/build/src/cli/render/catalog.js +16 -0
- package/build/src/cli/render/contract.d.ts +1 -1
- package/build/src/cli/render/contract.js +38 -5
- package/build/src/cli/render/kanshi.js +53 -5
- package/build/src/cli/render/refusal.js +17 -0
- package/build/src/cli/render/terminal.d.ts +13 -0
- package/build/src/cli/render/terminal.js +75 -0
- package/build/src/cli/render/text.js +5 -0
- package/build/src/cli/result.d.ts +15 -3
- package/build/src/contract-worktree.d.ts +43 -0
- package/build/src/contract-worktree.js +213 -0
- package/build/src/coordination/durable-file.d.ts +3 -0
- package/build/src/coordination/durable-file.js +44 -1
- package/build/src/core/facts/codec.js +23 -4
- package/build/src/core/facts/eligibility.d.ts +1 -10
- package/build/src/core/facts/eligibility.js +0 -49
- package/build/src/core/facts/observation.d.ts +3 -0
- package/build/src/core/facts/observation.js +12 -0
- package/build/src/core/facts/types.d.ts +10 -3
- package/build/src/core/subject.js +2 -2
- package/build/src/core/verbs/amend.js +12 -16
- package/build/src/core/verbs/bind.js +2 -16
- package/build/src/core/verbs/deliver.js +20 -4
- package/build/src/core/verbs/placement.js +3 -8
- package/build/src/dispatch/index.d.ts +3 -1
- package/build/src/dispatch/index.js +14 -5
- package/build/src/duration.d.ts +10 -0
- package/build/src/duration.js +22 -0
- package/build/src/git/admission.d.ts +2 -2
- package/build/src/git/admission.js +26 -16
- package/build/src/git/hooks.d.ts +15 -0
- package/build/src/git/hooks.js +57 -5
- package/build/src/git/identity.d.ts +5 -1
- package/build/src/git/identity.js +23 -4
- package/build/src/git/integration.d.ts +49 -0
- package/build/src/git/integration.js +214 -0
- package/build/src/git/observe.d.ts +25 -13
- package/build/src/git/observe.js +175 -72
- package/build/src/git/read-observation.d.ts +41 -0
- package/build/src/git/read-observation.js +361 -0
- package/build/src/git/reconcile.d.ts +15 -5
- package/build/src/git/reconcile.js +238 -85
- package/build/src/git/repository.d.ts +19 -4
- package/build/src/git/repository.js +77 -43
- package/build/src/git/scratch.d.ts +13 -0
- package/build/src/git/scratch.js +66 -0
- package/build/src/git/target-placement.d.ts +56 -0
- package/build/src/git/target-placement.js +255 -0
- package/build/src/git/tender.d.ts +48 -0
- package/build/src/git/tender.js +82 -0
- package/build/src/git/terminal-seal.d.ts +24 -0
- package/build/src/git/terminal-seal.js +87 -0
- package/build/src/git/tree.d.ts +2 -2
- package/build/src/git/tree.js +4 -0
- package/build/src/git/workspace.d.ts +21 -0
- package/build/src/git/workspace.js +104 -0
- package/build/src/identity/selector.d.ts +6 -0
- package/build/src/identity/selector.js +19 -0
- package/build/src/index.d.ts +4 -2
- package/build/src/index.js +2 -1
- package/build/src/kanshi/index.d.ts +1 -1
- package/build/src/kanshi/read.d.ts +5 -2
- package/build/src/kanshi/read.js +154 -45
- package/build/src/kanshi/report.d.ts +31 -6
- package/build/src/kanshi/select.js +1 -1
- package/build/src/library/address.d.ts +48 -0
- package/build/src/library/address.js +134 -0
- package/build/src/library/akuma-creation.d.ts +10 -6
- package/build/src/library/akuma-creation.js +12 -11
- package/build/src/library/bind.d.ts +21 -0
- package/build/src/library/bind.js +56 -0
- package/build/src/library/catalog.d.ts +58 -0
- package/build/src/library/catalog.js +72 -0
- package/build/src/library/configuration.d.ts +6 -15
- package/build/src/library/configuration.js +14 -17
- package/build/src/library/contract.d.ts +21 -46
- package/build/src/library/contract.js +193 -183
- package/build/src/library/delivery.d.ts +23 -0
- package/build/src/library/delivery.js +21 -0
- package/build/src/library/fleet.d.ts +65 -0
- package/build/src/library/fleet.js +189 -0
- package/build/src/library/input.d.ts +3 -0
- package/build/src/library/input.js +19 -0
- package/build/src/library/keiyaku.d.ts +16 -2
- package/build/src/library/keiyaku.js +11 -1
- package/build/src/library/mutation.d.ts +38 -0
- package/build/src/library/mutation.js +39 -0
- package/build/src/library/refusal.d.ts +21 -0
- package/build/src/library/refusal.js +29 -0
- package/build/src/library/region.d.ts +2 -1
- package/build/src/library/region.js +3 -3
- package/build/src/library/repo.d.ts +4 -1
- package/build/src/library/repo.js +31 -16
- package/build/src/protocol/attempt.d.ts +16 -3
- package/build/src/protocol/attempt.js +39 -8
- package/build/src/protocol/bind.d.ts +9 -2
- package/build/src/protocol/bind.js +47 -39
- package/build/src/protocol/intent.d.ts +42 -18
- package/build/src/protocol/intent.js +36 -75
- package/build/src/protocol/operations.d.ts +93 -35
- package/build/src/protocol/operations.js +235 -123
- package/build/src/protocol/outcome.d.ts +7 -2
- package/build/src/protocol/outcome.js +9 -3
- package/build/src/protocol/placement.d.ts +30 -0
- package/build/src/protocol/placement.js +114 -0
- package/build/src/protocol/read/audit.d.ts +9 -3
- package/build/src/protocol/read/audit.js +25 -9
- package/build/src/protocol/read/documents.d.ts +2 -2
- package/build/src/protocol/read/documents.js +4 -4
- package/build/src/protocol/read/status.d.ts +11 -5
- package/build/src/protocol/read/status.js +28 -15
- package/build/src/protocol/run.d.ts +49 -7
- package/build/src/protocol/run.js +53 -25
- package/build/src/runtime/proc/line-rpc.d.ts +1 -0
- package/build/src/runtime/proc/line-rpc.js +21 -0
- package/build/src/runtime/proc/run.d.ts +18 -2
- package/build/src/runtime/proc/run.js +34 -8
- package/build/src/settings.d.ts +6 -0
- package/build/src/settings.js +29 -14
- package/build/src/settlement/fence.d.ts +9 -0
- package/build/src/settlement/fence.js +23 -0
- package/build/src/settlement/holder.d.ts +25 -4
- package/build/src/settlement/holder.js +102 -28
- package/build/src/settlement/settle.d.ts +8 -0
- package/build/src/settlement/settle.js +102 -28
- package/build/src/task/board.d.ts +20 -2
- package/build/src/task/board.js +11 -1
- package/build/src/task/compose.d.ts +2 -2
- package/build/src/task/compose.js +2 -2
- package/build/src/task/context.js +2 -7
- package/build/src/task/index.d.ts +9 -9
- package/build/src/task/index.js +15 -12
- package/build/src/task/operations.d.ts +31 -12
- package/build/src/task/operations.js +8 -4
- package/build/src/task/store.d.ts +4 -6
- package/build/src/task/store.js +3 -3
- package/build/src/verification/declaration.d.ts +1 -0
- package/build/src/verification/execution.d.ts +49 -0
- package/build/src/verification/execution.js +104 -0
- package/build/src/world.d.ts +13 -0
- package/build/src/world.js +103 -0
- package/package.json +7 -4
- package/build/src/akuma/activity.d.ts +0 -66
- package/build/src/akuma/activity.js +0 -139
- package/build/src/akuma/providers/claude.js +0 -305
- package/build/src/context-root.d.ts +0 -4
- package/build/src/context-root.js +0 -15
- package/build/src/git/delivery.d.ts +0 -20
- package/build/src/git/delivery.js +0 -109
- package/build/src/git/verification.d.ts +0 -14
- package/build/src/git/verification.js +0 -56
- package/build/src/verification/producer.d.ts +0 -23
- package/build/src/verification/producer.js +0 -54
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type TaskId } from "./identity.js";
|
|
2
|
-
import {
|
|
2
|
+
import type { WorldRoot } from "../world.js";
|
|
3
3
|
import type { TaskRefusal, TaskRetry } from "./operations.js";
|
|
4
4
|
type TaskDocumentChange = Readonly<{
|
|
5
5
|
taskId: TaskId;
|
|
@@ -21,5 +21,5 @@ export type TaskCompositionResult = Readonly<{
|
|
|
21
21
|
}>;
|
|
22
22
|
draft: string;
|
|
23
23
|
}>;
|
|
24
|
-
export declare function composeTasks(world:
|
|
24
|
+
export declare function composeTasks(world: WorldRoot, markdown: string, signal?: AbortSignal): Promise<TaskCompositionResult>;
|
|
25
25
|
export {};
|
|
@@ -195,9 +195,9 @@ export async function composeTasks(world, markdown, signal) {
|
|
|
195
195
|
if ("kind" in sketch)
|
|
196
196
|
return { kind: "refused", refusal: sketch };
|
|
197
197
|
const at = currentTimestamp();
|
|
198
|
-
const context = sketch.namespace ?? readNamespaceContext(world
|
|
198
|
+
const context = sketch.namespace ?? readNamespaceContext(world);
|
|
199
199
|
if (context === "malformed")
|
|
200
|
-
return { kind: "refused", refusal: { kind: "invalid-namespace-context", path: `${world
|
|
200
|
+
return { kind: "refused", refusal: { kind: "invalid-namespace-context", path: `${world}/.keiyaku/namespace/current` } };
|
|
201
201
|
const namespace = context === "absent" ? [] : context;
|
|
202
202
|
const initial = readBoard(world);
|
|
203
203
|
const planned = plan(sketch, initial.board, namespace, at);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { lstatSync, mkdirSync, readFileSync, } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
-
import { replaceFileDurably } from "../coordination/durable-file.js";
|
|
3
|
+
import { repairDerivedFile, replaceFileDurably } from "../coordination/durable-file.js";
|
|
4
4
|
import { normalizeIdentityStem } from "../identity/normalize.js";
|
|
5
5
|
function directory(root) { return join(root, ".keiyaku", "namespace"); }
|
|
6
6
|
function currentPath(root) { return join(directory(root), "current"); }
|
|
@@ -38,12 +38,7 @@ export function installNamespaceContext(root, segments) {
|
|
|
38
38
|
replaceFileDurably(currentPath(root), `${segments.join("/")}\n`);
|
|
39
39
|
}
|
|
40
40
|
function installIgnore(root) {
|
|
41
|
-
|
|
42
|
-
const stat = lstatSync(ignore, { throwIfNoEntry: false });
|
|
43
|
-
if (stat !== undefined && (!stat.isFile() || stat.isSymbolicLink()))
|
|
44
|
-
throw new Error(`namespace ignore is not a regular file: ${ignore}`);
|
|
45
|
-
if (stat === undefined || readFileSync(ignore, "utf8") !== "*\n")
|
|
46
|
-
replaceFileDurably(ignore, "*\n");
|
|
41
|
+
repairDerivedFile(ignorePath(root), "*\n");
|
|
47
42
|
}
|
|
48
43
|
export function repairNamespaceContext(root, segments) {
|
|
49
44
|
const current = readNamespaceContext(root);
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import
|
|
1
|
+
import type { WorldRoot } from "../world.js";
|
|
2
|
+
export type { WorldRoot } from "../world.js";
|
|
3
|
+
import { type BlockedTaskRow, type TaskDetailFacts, type TaskDoctorIssue, type TaskRef, type TaskRow, type TaskTreeNode } from "./board.js";
|
|
2
4
|
import { type TaskCompositionResult } from "./compose.js";
|
|
3
5
|
import { TaskAuthorityCorruptionError, type TaskPriority, type TaskState } from "./document.js";
|
|
4
6
|
import { type TaskId } from "./identity.js";
|
|
5
7
|
import { type AddTaskDocumentInput, type AddTaskInput, type TaskBatchResult, type TaskMutationResult, type TaskOutcome, type TaskRefusal, type TaskRetry, type TaskUpdateResult, type TaskView, type UpdateTaskInput } from "./operations.js";
|
|
6
|
-
import { type TaskWorld } from "./store.js";
|
|
7
8
|
export type TaskDetail = Omit<TaskDetailFacts, "task"> & Readonly<{
|
|
8
9
|
task: TaskView;
|
|
9
10
|
}>;
|
|
@@ -14,12 +15,12 @@ export type TaskDoctorReport = Readonly<{
|
|
|
14
15
|
issues: readonly TaskDoctorIssue[];
|
|
15
16
|
}>;
|
|
16
17
|
export type TaskDependencyTree = TaskOutcome<TaskTreeNode>;
|
|
17
|
-
export type { AddTaskDocumentInput, AddTaskInput, BlockedTaskRow, TaskBatchResult, TaskCompositionResult, TaskDoctorIssue, TaskId, TaskMutationResult, TaskOutcome, TaskPriority, TaskRefusal, TaskRetry, TaskRow, TaskState, TaskUpdateResult, TaskView, UpdateTaskInput };
|
|
18
|
+
export type { AddTaskDocumentInput, AddTaskInput, BlockedTaskRow, TaskBatchResult, TaskCompositionResult, TaskDoctorIssue, TaskId, TaskMutationResult, TaskOutcome, TaskPriority, TaskRef, TaskRefusal, TaskRetry, TaskRow, TaskState, TaskUpdateResult, TaskView, UpdateTaskInput };
|
|
18
19
|
export { TaskAuthorityCorruptionError };
|
|
19
20
|
declare class TaskHandle {
|
|
20
21
|
readonly id: TaskId;
|
|
21
22
|
private readonly world;
|
|
22
|
-
constructor(id: TaskId, world:
|
|
23
|
+
constructor(id: TaskId, world: WorldRoot);
|
|
23
24
|
read(): Promise<TaskDetail | null>;
|
|
24
25
|
tree(input?: Readonly<{
|
|
25
26
|
full?: boolean;
|
|
@@ -39,6 +40,7 @@ declare class TaskHandle {
|
|
|
39
40
|
signal?: AbortSignal;
|
|
40
41
|
}): Promise<TaskMutationResult>;
|
|
41
42
|
done(input?: {
|
|
43
|
+
note?: string;
|
|
42
44
|
signal?: AbortSignal;
|
|
43
45
|
}): Promise<TaskMutationResult>;
|
|
44
46
|
drop(input?: {
|
|
@@ -48,9 +50,9 @@ declare class TaskHandle {
|
|
|
48
50
|
}
|
|
49
51
|
export type Task = TaskHandle;
|
|
50
52
|
declare class TasksHandle {
|
|
51
|
-
readonly root: string;
|
|
52
53
|
private readonly world;
|
|
53
|
-
|
|
54
|
+
readonly root: WorldRoot;
|
|
55
|
+
constructor(world: WorldRoot);
|
|
54
56
|
namespace(): Promise<TaskNamespaceResult>;
|
|
55
57
|
setNamespace(input: Readonly<{
|
|
56
58
|
namespace: readonly string[];
|
|
@@ -84,7 +86,5 @@ declare class TasksHandle {
|
|
|
84
86
|
}
|
|
85
87
|
export type Tasks = TasksHandle;
|
|
86
88
|
export declare const Tasks: Readonly<{
|
|
87
|
-
|
|
88
|
-
path?: string;
|
|
89
|
-
}>): Tasks;
|
|
89
|
+
of(root: WorldRoot): Tasks;
|
|
90
90
|
}>;
|
package/build/src/task/index.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { resolveContextRoot } from "../context-root.js";
|
|
2
1
|
import { buildTree, diagnoseBoard, projectDetailFacts } from "./board.js";
|
|
3
2
|
import { composeTasks } from "./compose.js";
|
|
4
3
|
import { TaskAuthorityCorruptionError } from "./document.js";
|
|
@@ -135,8 +134,8 @@ class TaskHandle {
|
|
|
135
134
|
update(input) { return updateTask(this.world, this.id, updateInput(input)); }
|
|
136
135
|
lifecycle(verb, input) {
|
|
137
136
|
const value = record(input ?? {}, `${verb} input`);
|
|
138
|
-
closed(value, verb === "drop" ? ["note", "signal"] : ["signal"], `${verb} input`);
|
|
139
|
-
const note = verb === "drop" ? text(value.note, "note") : undefined;
|
|
137
|
+
closed(value, verb === "drop" || verb === "done" ? ["note", "signal"] : ["signal"], `${verb} input`);
|
|
138
|
+
const note = verb === "drop" || verb === "done" ? text(value.note, "note") : undefined;
|
|
140
139
|
return lifecycleTask(this.world, this.id, verb, signal(value.signal), note);
|
|
141
140
|
}
|
|
142
141
|
start(input) { return this.lifecycle("start", input); }
|
|
@@ -147,12 +146,11 @@ class TaskHandle {
|
|
|
147
146
|
drop(input) { return this.lifecycle("drop", input); }
|
|
148
147
|
}
|
|
149
148
|
class TasksHandle {
|
|
150
|
-
root;
|
|
151
149
|
world;
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
this.world =
|
|
155
|
-
this.root =
|
|
150
|
+
root;
|
|
151
|
+
constructor(world) {
|
|
152
|
+
this.world = world;
|
|
153
|
+
this.root = world;
|
|
156
154
|
}
|
|
157
155
|
async namespace() { const value = currentNamespace(this.world); return "kind" in value ? { kind: "refused", refusal: value } : { kind: "accepted", value }; }
|
|
158
156
|
async setNamespace(input) { const v = record(input, "setNamespace input"); closed(v, ["namespace"], "setNamespace input"); const ns = namespace(v.namespace); if (ns === undefined)
|
|
@@ -170,10 +168,15 @@ class TasksHandle {
|
|
|
170
168
|
throw new TypeError("scope must be namespace or world"); return blockedTasks(this.world, v.scope); }
|
|
171
169
|
async doctor() { return { issues: diagnoseBoard(readBoard(this.world).board) }; }
|
|
172
170
|
batch(input) { const v = record(input, "batch input"); closed(v, ["verb", "ids", "note", "signal"], "batch input"); const verb = v.verb; if (verb !== "done" && verb !== "drop" && verb !== "hold")
|
|
173
|
-
throw new TypeError("batch verb is invalid"); const note = text(v.note, "note"); if (note !== undefined && verb !== "drop")
|
|
174
|
-
throw new TypeError("batch note is valid only for drop"); return batchTasks(this.world, verb, taskIds(v.ids, "ids") ?? [], signal(v.signal), note); }
|
|
171
|
+
throw new TypeError("batch verb is invalid"); const note = text(v.note, "note"); if (note !== undefined && verb !== "done" && verb !== "drop")
|
|
172
|
+
throw new TypeError("batch note is valid only for done or drop"); return batchTasks(this.world, verb, taskIds(v.ids, "ids") ?? [], signal(v.signal), note); }
|
|
175
173
|
compose(input) { const v = record(input, "compose input"); closed(v, ["markdown", "signal"], "compose input"); const markdown = text(v.markdown, "markdown"); if (markdown === undefined)
|
|
176
174
|
throw new TypeError("markdown is required"); return composeTasks(this.world, markdown, signal(v.signal)); }
|
|
177
175
|
}
|
|
178
|
-
export const Tasks = Object.freeze({
|
|
179
|
-
|
|
176
|
+
export const Tasks = Object.freeze({
|
|
177
|
+
of(root) {
|
|
178
|
+
if (typeof root !== "string")
|
|
179
|
+
throw new TypeError("Tasks.of root must be a WorldRoot");
|
|
180
|
+
return new TasksHandle(root);
|
|
181
|
+
},
|
|
182
|
+
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type BlockedTaskRow, type TaskRow } from "./board.js";
|
|
2
2
|
import { type TaskDocument, type TaskPriority, type TaskState } from "./document.js";
|
|
3
3
|
import { type TaskId } from "./identity.js";
|
|
4
|
-
import {
|
|
4
|
+
import type { WorldRoot } from "../world.js";
|
|
5
5
|
export type TaskView = Readonly<TaskDocument & {
|
|
6
6
|
namespace: readonly string[];
|
|
7
7
|
}>;
|
|
@@ -102,14 +102,33 @@ export type UpdateTaskInput = Readonly<{
|
|
|
102
102
|
signal?: AbortSignal;
|
|
103
103
|
}>;
|
|
104
104
|
export declare function taskView(document: TaskDocument): TaskView;
|
|
105
|
-
export declare function addTask(world:
|
|
106
|
-
export declare function addTaskDocument(world:
|
|
107
|
-
export declare function updateTask(world:
|
|
108
|
-
export declare function lifecycleTask(world:
|
|
109
|
-
export declare function batchTasks(world:
|
|
110
|
-
export declare function settleTask(world:
|
|
111
|
-
export declare function listTasks(world:
|
|
112
|
-
export declare function readyTasks(world:
|
|
113
|
-
export declare function blockedTasks(world:
|
|
114
|
-
|
|
115
|
-
export declare function
|
|
105
|
+
export declare function addTask(world: WorldRoot, input: AddTaskInput): Promise<TaskMutationResult>;
|
|
106
|
+
export declare function addTaskDocument(world: WorldRoot, input: AddTaskDocumentInput): Promise<TaskMutationResult>;
|
|
107
|
+
export declare function updateTask(world: WorldRoot, id: TaskId, input: UpdateTaskInput): Promise<TaskUpdateResult>;
|
|
108
|
+
export declare function lifecycleTask(world: WorldRoot, id: TaskId, verb: TaskLifecycleVerb, signal?: AbortSignal, note?: string): Promise<TaskMutationResult>;
|
|
109
|
+
export declare function batchTasks(world: WorldRoot, verb: "done" | "drop" | "hold", ids: readonly TaskId[], signal?: AbortSignal, note?: string): Promise<TaskBatchResult>;
|
|
110
|
+
export declare function settleTask(world: WorldRoot, id: TaskId, desired: "done" | "open-from-done"): Promise<SettledTaskResult>;
|
|
111
|
+
export declare function listTasks(world: WorldRoot, selection: "active" | "closed" | "all", scope?: "namespace" | "world"): TaskOutcome<readonly TaskRow[]>;
|
|
112
|
+
export declare function readyTasks(world: WorldRoot, scope?: "namespace" | "world"): TaskOutcome<readonly TaskRow[]>;
|
|
113
|
+
export declare function blockedTasks(world: WorldRoot, scope?: "namespace" | "world"): TaskOutcome<readonly BlockedTaskRow[]>;
|
|
114
|
+
/** Internal composite observation from one complete Task board read. */
|
|
115
|
+
export declare function observeTaskStatusRows(world: WorldRoot): (Readonly<{
|
|
116
|
+
id: TaskId;
|
|
117
|
+
title: string;
|
|
118
|
+
state: TaskState;
|
|
119
|
+
priority: TaskPriority;
|
|
120
|
+
disposition: import("./board.js").TaskDisposition;
|
|
121
|
+
}> | {
|
|
122
|
+
blockers: readonly Readonly<{
|
|
123
|
+
id: TaskId;
|
|
124
|
+
title: string | null;
|
|
125
|
+
state: TaskState | "missing";
|
|
126
|
+
}>[];
|
|
127
|
+
id: TaskId;
|
|
128
|
+
title: string;
|
|
129
|
+
state: TaskState;
|
|
130
|
+
priority: TaskPriority;
|
|
131
|
+
disposition: import("./board.js").TaskDisposition;
|
|
132
|
+
})[];
|
|
133
|
+
export declare function setCurrentNamespace(world: WorldRoot, namespace: readonly string[]): void;
|
|
134
|
+
export declare function currentNamespace(world: WorldRoot): readonly string[] | TaskRefusal;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { documentDiff } from "../markdown/diff.js";
|
|
2
2
|
import { installNamespaceContext, readNamespaceContext } from "./context.js";
|
|
3
|
-
import { relationProblem, projectBlocked, projectReady, projectRows } from "./board.js";
|
|
3
|
+
import { relationProblem, projectBlocked, projectReady, projectRows, projectStatusRows } from "./board.js";
|
|
4
4
|
import { parseTaskCreationDocument, serializeTaskDocument } from "./document.js";
|
|
5
5
|
import { allocateLocalId, deriveLocalStem, formatTaskId, parseTaskId, sameNamespace } from "./identity.js";
|
|
6
6
|
import { authorityPath, readBoard, replaceAuthority, withTaskLocks, } from "./store.js";
|
|
@@ -12,8 +12,8 @@ function retry(reason) { return { kind: "retry", reason }; }
|
|
|
12
12
|
function context(world, explicit) {
|
|
13
13
|
if (explicit !== undefined)
|
|
14
14
|
return explicit;
|
|
15
|
-
const current = readNamespaceContext(world
|
|
16
|
-
return current === "malformed" ? { kind: "invalid-namespace-context", path: `${world
|
|
15
|
+
const current = readNamespaceContext(world);
|
|
16
|
+
return current === "malformed" ? { kind: "invalid-namespace-context", path: `${world}/.keiyaku/namespace/current` }
|
|
17
17
|
: current === "absent" ? [] : current;
|
|
18
18
|
}
|
|
19
19
|
function occupied(board, namespace) {
|
|
@@ -187,7 +187,11 @@ export function blockedTasks(world, scope) {
|
|
|
187
187
|
return { kind: "refused", refusal: selected };
|
|
188
188
|
return { kind: "accepted", value: projectBlocked(readBoard(world).board, selected) };
|
|
189
189
|
}
|
|
190
|
-
|
|
190
|
+
/** Internal composite observation from one complete Task board read. */
|
|
191
|
+
export function observeTaskStatusRows(world) {
|
|
192
|
+
return projectStatusRows(readBoard(world).board, null);
|
|
193
|
+
}
|
|
194
|
+
export function setCurrentNamespace(world, namespace) { installNamespaceContext(world, namespace); }
|
|
191
195
|
export function currentNamespace(world) {
|
|
192
196
|
const selected = context(world);
|
|
193
197
|
return selected;
|
|
@@ -1,22 +1,20 @@
|
|
|
1
|
+
import type { WorldRoot } from "../world.js";
|
|
1
2
|
import { type TaskId } from "./identity.js";
|
|
2
3
|
import type { TaskBoard } from "./board.js";
|
|
3
|
-
export type TaskWorld = Readonly<{
|
|
4
|
-
root: string;
|
|
5
|
-
}>;
|
|
6
4
|
export type BoardSnapshot = Readonly<{
|
|
7
5
|
board: TaskBoard;
|
|
8
6
|
bytes: ReadonlyMap<TaskId, Uint8Array>;
|
|
9
7
|
}>;
|
|
10
|
-
export declare function readBoard(world:
|
|
8
|
+
export declare function readBoard(world: WorldRoot): BoardSnapshot;
|
|
11
9
|
export declare function replaceAuthority(input: Readonly<{
|
|
12
10
|
path: string;
|
|
13
11
|
expected: Uint8Array | null;
|
|
14
12
|
next: Uint8Array;
|
|
15
13
|
}>): "replaced" | "concurrent-modification";
|
|
16
14
|
export declare function withTaskLocks<T>(input: Readonly<{
|
|
17
|
-
world:
|
|
15
|
+
world: WorldRoot;
|
|
18
16
|
allocation: boolean;
|
|
19
17
|
ids: readonly TaskId[];
|
|
20
18
|
signal?: AbortSignal;
|
|
21
19
|
}>, action: () => Promise<T>): Promise<T | "busy">;
|
|
22
|
-
export declare function authorityPath(world:
|
|
20
|
+
export declare function authorityPath(world: WorldRoot, id: TaskId): string;
|
package/build/src/task/store.js
CHANGED
|
@@ -4,7 +4,7 @@ import { dirname, relative, resolve, sep } from "node:path";
|
|
|
4
4
|
import { acquireSqliteTransactionLock, SqliteTransactionLockError } from "../coordination/sqlite-transaction-lock.js";
|
|
5
5
|
import { parseTaskDocument } from "./document.js";
|
|
6
6
|
import { formatTaskId, parseTaskId, taskAuthorityPath } from "./identity.js";
|
|
7
|
-
function tasksDirectory(world) { return resolve(world
|
|
7
|
+
function tasksDirectory(world) { return resolve(world, ".keiyaku", "tasks"); }
|
|
8
8
|
function authorityFiles(directory) {
|
|
9
9
|
if (!existsSync(directory))
|
|
10
10
|
return [];
|
|
@@ -88,11 +88,11 @@ export function replaceAuthority(input) {
|
|
|
88
88
|
}
|
|
89
89
|
function lockPath(world, id) {
|
|
90
90
|
const coordinate = parseTaskId(id);
|
|
91
|
-
return resolve(world
|
|
91
|
+
return resolve(world, ".keiyaku", "locks", "task", ...coordinate.namespace, `${coordinate.localId}.sqlite`);
|
|
92
92
|
}
|
|
93
93
|
export async function withTaskLocks(input, action) {
|
|
94
94
|
const paths = [
|
|
95
|
-
...(input.allocation ? [resolve(input.world
|
|
95
|
+
...(input.allocation ? [resolve(input.world, ".keiyaku", "locks", "task-allocation.sqlite")] : []),
|
|
96
96
|
...[...new Set(input.ids)].sort((a, b) => Buffer.compare(Buffer.from(a), Buffer.from(b))).map((id) => lockPath(input.world, id)),
|
|
97
97
|
];
|
|
98
98
|
const held = [];
|
|
@@ -4,6 +4,7 @@ export type VerificationExecutor = "bash" | "zsh" | "pwsh";
|
|
|
4
4
|
export type VerificationDeclaration = Readonly<{
|
|
5
5
|
readonly executor: VerificationExecutor;
|
|
6
6
|
readonly script: string;
|
|
7
|
+
readonly timeoutMs?: number;
|
|
7
8
|
}>;
|
|
8
9
|
export type VerificationDefinition = Readonly<{
|
|
9
10
|
readonly segment: DocumentSegmentKey;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { SnapshotId } from "../core/facts/types.js";
|
|
2
|
+
import { type HookFailure } from "../git/hooks.js";
|
|
3
|
+
import type { MaterializedScratchCandidate, WorktreeLeak } from "../git/scratch.js";
|
|
4
|
+
import type { GitRepository } from "../git/repository.js";
|
|
5
|
+
import type { Settings } from "../settings.js";
|
|
6
|
+
import type { VerificationDeclaration } from "./declaration.js";
|
|
7
|
+
type VerificationVerdict = "satisfied" | "unsatisfied";
|
|
8
|
+
export type VerificationTerminalOutcome = Readonly<{
|
|
9
|
+
kind: "terminal";
|
|
10
|
+
verdict: VerificationVerdict;
|
|
11
|
+
summary?: string;
|
|
12
|
+
}>;
|
|
13
|
+
export type VerificationNonterminalOutcome = Readonly<{
|
|
14
|
+
kind: "unknown-exit";
|
|
15
|
+
}> | Readonly<{
|
|
16
|
+
kind: "cancelled";
|
|
17
|
+
}> | Readonly<{
|
|
18
|
+
kind: "spawn-error";
|
|
19
|
+
diagnostic: string;
|
|
20
|
+
}>;
|
|
21
|
+
export type VerificationExecutionStop = VerificationNonterminalOutcome | Readonly<{
|
|
22
|
+
kind: "candidate-unavailable" | "environment-failure";
|
|
23
|
+
diagnostic: string;
|
|
24
|
+
}> | Readonly<{
|
|
25
|
+
kind: "environment-failure";
|
|
26
|
+
command: number;
|
|
27
|
+
detail: HookFailure;
|
|
28
|
+
}>;
|
|
29
|
+
export type VerificationExecution = Readonly<{
|
|
30
|
+
outcome: VerificationTerminalOutcome | VerificationExecutionStop;
|
|
31
|
+
cleanup?: Readonly<{
|
|
32
|
+
phase: "destroy";
|
|
33
|
+
command: number;
|
|
34
|
+
detail: HookFailure;
|
|
35
|
+
}>;
|
|
36
|
+
leak?: WorktreeLeak;
|
|
37
|
+
}>;
|
|
38
|
+
export type ExecuteVerificationInput = Readonly<{
|
|
39
|
+
repository: GitRepository;
|
|
40
|
+
candidate: SnapshotId;
|
|
41
|
+
declarations: readonly VerificationDeclaration[];
|
|
42
|
+
environment: NodeJS.ProcessEnv;
|
|
43
|
+
materializeScratchCandidate: (repository: GitRepository, candidate: SnapshotId) => MaterializedScratchCandidate;
|
|
44
|
+
projectSettings: (root: string) => Settings;
|
|
45
|
+
signal?: AbortSignal;
|
|
46
|
+
}>;
|
|
47
|
+
/** Execute one disposable Verification attempt over the exact integration snapshot. */
|
|
48
|
+
export declare function executeVerification(input: ExecuteVerificationInput): Promise<VerificationExecution>;
|
|
49
|
+
export {};
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { runHookCommands, worktreeHooksFrom } from "../git/hooks.js";
|
|
2
|
+
import { runProcess } from "../runtime/proc/run.js";
|
|
3
|
+
const SUMMARY_BYTES = 32 * 1024;
|
|
4
|
+
function argvFor(declaration) {
|
|
5
|
+
const args = declaration.executor === "pwsh" ? ["-Command", declaration.script] : ["-c", declaration.script];
|
|
6
|
+
return [declaration.executor, ...args];
|
|
7
|
+
}
|
|
8
|
+
function processDiagnostic(declaration, index, outcome) {
|
|
9
|
+
if (outcome.code === 0 && outcome.stdout.length === 0 && outcome.stderr.length === 0 && !outcome.truncated) {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
return [
|
|
13
|
+
`[${index + 1} ${declaration.executor} exit ${outcome.code}${outcome.truncated ? " output-truncated" : ""}]`,
|
|
14
|
+
...(outcome.stdout.length === 0 ? [] : [`stdout:\n${outcome.stdout}`]),
|
|
15
|
+
...(outcome.stderr.length === 0 ? [] : [`stderr:\n${outcome.stderr}`]),
|
|
16
|
+
].join("\n");
|
|
17
|
+
}
|
|
18
|
+
function appendSummary(current, diagnostic) {
|
|
19
|
+
if (diagnostic === null)
|
|
20
|
+
return current;
|
|
21
|
+
const combined = Buffer.from(current === undefined ? diagnostic : `${current}\n\n${diagnostic}`);
|
|
22
|
+
if (combined.length <= SUMMARY_BYTES)
|
|
23
|
+
return combined.toString("utf8");
|
|
24
|
+
const marker = Buffer.from("[earlier output truncated]\n");
|
|
25
|
+
let start = combined.length - (SUMMARY_BYTES - marker.length);
|
|
26
|
+
while (start < combined.length && (combined[start] & 0xc0) === 0x80)
|
|
27
|
+
start += 1;
|
|
28
|
+
return Buffer.concat([marker, combined.subarray(start)]).toString("utf8");
|
|
29
|
+
}
|
|
30
|
+
async function executeDeclarations(input) {
|
|
31
|
+
let verdict = "satisfied";
|
|
32
|
+
let summary;
|
|
33
|
+
for (const [index, declaration] of input.declarations.entries()) {
|
|
34
|
+
const outcome = await runProcess({
|
|
35
|
+
argv: argvFor(declaration),
|
|
36
|
+
cwd: input.cwd,
|
|
37
|
+
env: input.environment,
|
|
38
|
+
...(input.signal === undefined ? {} : { signal: input.signal }),
|
|
39
|
+
...(declaration.timeoutMs === undefined ? {} : { timeoutMs: declaration.timeoutMs }),
|
|
40
|
+
});
|
|
41
|
+
if (outcome.kind === "timeout") {
|
|
42
|
+
verdict = "unsatisfied";
|
|
43
|
+
summary = appendSummary(summary, `[${index + 1} ${declaration.executor} timeout after ${declaration.timeoutMs}ms]`);
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (outcome.kind === "spawn-error" || outcome.kind === "unknown-exit" || outcome.kind === "cancelled")
|
|
47
|
+
return outcome;
|
|
48
|
+
if (outcome.code !== 0)
|
|
49
|
+
verdict = "unsatisfied";
|
|
50
|
+
summary = appendSummary(summary, processDiagnostic(declaration, index, outcome));
|
|
51
|
+
}
|
|
52
|
+
return { kind: "terminal", verdict, ...(summary === undefined ? {} : { summary }) };
|
|
53
|
+
}
|
|
54
|
+
/** Execute one disposable Verification attempt over the exact integration snapshot. */
|
|
55
|
+
export async function executeVerification(input) {
|
|
56
|
+
let scratch;
|
|
57
|
+
try {
|
|
58
|
+
scratch = input.materializeScratchCandidate(input.repository, input.candidate);
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
return { outcome: { kind: "candidate-unavailable", diagnostic: error instanceof Error ? error.message : String(error) } };
|
|
62
|
+
}
|
|
63
|
+
let outcome;
|
|
64
|
+
let cleanup;
|
|
65
|
+
let leak = null;
|
|
66
|
+
let destroy;
|
|
67
|
+
try {
|
|
68
|
+
try {
|
|
69
|
+
const hooks = worktreeHooksFrom({ settings: input.projectSettings(scratch.cwd) });
|
|
70
|
+
destroy = hooks.destroy;
|
|
71
|
+
const readiness = await runHookCommands(scratch.cwd, hooks.create, input.signal);
|
|
72
|
+
outcome = readiness.kind === "cancelled"
|
|
73
|
+
? { kind: "cancelled" }
|
|
74
|
+
: readiness.kind === "failed"
|
|
75
|
+
? { kind: "environment-failure", command: readiness.command, detail: readiness.failure }
|
|
76
|
+
: await executeDeclarations({
|
|
77
|
+
declarations: input.declarations,
|
|
78
|
+
cwd: scratch.cwd,
|
|
79
|
+
environment: input.environment,
|
|
80
|
+
...(input.signal === undefined ? {} : { signal: input.signal }),
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
outcome = { kind: "environment-failure", diagnostic: error instanceof Error ? error.message : String(error) };
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
finally {
|
|
88
|
+
if (destroy !== undefined) {
|
|
89
|
+
const result = await runHookCommands(scratch.cwd, destroy);
|
|
90
|
+
if (result.kind === "cancelled")
|
|
91
|
+
throw new Error("scratch destroy cancelled without a signal");
|
|
92
|
+
if (result.kind === "failed")
|
|
93
|
+
cleanup = { phase: "destroy", command: result.command, detail: result.failure };
|
|
94
|
+
}
|
|
95
|
+
leak = scratch.dispose();
|
|
96
|
+
}
|
|
97
|
+
if (outcome === undefined)
|
|
98
|
+
throw new Error("Verification ended without an outcome");
|
|
99
|
+
return {
|
|
100
|
+
outcome,
|
|
101
|
+
...(cleanup === undefined ? {} : { cleanup }),
|
|
102
|
+
...(leak === null ? {} : { leak }),
|
|
103
|
+
};
|
|
104
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
declare const WORLD_BRAND: unique symbol;
|
|
2
|
+
export type WorldRoot = string & {
|
|
3
|
+
readonly [WORLD_BRAND]: true;
|
|
4
|
+
};
|
|
5
|
+
export declare class WorldError extends Error {
|
|
6
|
+
readonly kind: "invalid-world" | "home-world" | "root-world";
|
|
7
|
+
constructor(kind: "invalid-world" | "home-world" | "root-world", message: string);
|
|
8
|
+
}
|
|
9
|
+
export declare const World: Readonly<{
|
|
10
|
+
locate(input: string): WorldRoot | null;
|
|
11
|
+
at(input: string): WorldRoot;
|
|
12
|
+
}>;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, realpathSync, statSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { dirname, join, parse, resolve } from "node:path";
|
|
4
|
+
const WORLD_BRAND = Symbol("keiyaku.world");
|
|
5
|
+
export class WorldError extends Error {
|
|
6
|
+
kind;
|
|
7
|
+
constructor(kind, message) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.name = "WorldError";
|
|
10
|
+
this.kind = kind;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function homeRoot() {
|
|
14
|
+
try {
|
|
15
|
+
return realpathSync(resolve(homedir()));
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return resolve(homedir());
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function directory(input, label) {
|
|
22
|
+
if (typeof input !== "string" || input.trim().length === 0) {
|
|
23
|
+
throw new TypeError(`${label} must be a nonblank path`);
|
|
24
|
+
}
|
|
25
|
+
const path = resolve(input);
|
|
26
|
+
let real;
|
|
27
|
+
try {
|
|
28
|
+
real = realpathSync(path);
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
throw new WorldError("invalid-world", `world path is not an existing directory: ${path}`);
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
if (!statSync(real).isDirectory())
|
|
35
|
+
throw new Error("not a directory");
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
throw new WorldError("invalid-world", `world path is not a directory: ${real}`);
|
|
39
|
+
}
|
|
40
|
+
return real;
|
|
41
|
+
}
|
|
42
|
+
function brand(path) { return path; }
|
|
43
|
+
function marker(root) { return join(root, ".keiyaku"); }
|
|
44
|
+
function rejectReservedRoot(root) {
|
|
45
|
+
if (root === homeRoot()) {
|
|
46
|
+
throw new WorldError("home-world", "the user home directory cannot be a Keiyaku world");
|
|
47
|
+
}
|
|
48
|
+
if (root === parse(root).root) {
|
|
49
|
+
throw new WorldError("root-world", "the filesystem root cannot be a Keiyaku world");
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function ensureMarker(root) {
|
|
53
|
+
const path = marker(root);
|
|
54
|
+
if (existsSync(path)) {
|
|
55
|
+
try {
|
|
56
|
+
if (!statSync(path).isDirectory())
|
|
57
|
+
throw new Error("not a directory");
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
throw new WorldError("invalid-world", `world marker is not a directory: ${path}`);
|
|
61
|
+
}
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
mkdirSync(path, { recursive: true });
|
|
65
|
+
}
|
|
66
|
+
function exact(input) {
|
|
67
|
+
const root = directory(input, "world");
|
|
68
|
+
rejectReservedRoot(root);
|
|
69
|
+
ensureMarker(root);
|
|
70
|
+
return brand(root);
|
|
71
|
+
}
|
|
72
|
+
function locateInput(input) {
|
|
73
|
+
return directory(input, "world location");
|
|
74
|
+
}
|
|
75
|
+
export const World = Object.freeze({
|
|
76
|
+
locate(input) {
|
|
77
|
+
let candidate = locateInput(input);
|
|
78
|
+
const home = homeRoot();
|
|
79
|
+
for (;;) {
|
|
80
|
+
const filesystemRoot = parse(candidate).root;
|
|
81
|
+
if (candidate !== home && candidate !== filesystemRoot && existsSync(marker(candidate))) {
|
|
82
|
+
try {
|
|
83
|
+
if (!statSync(marker(candidate)).isDirectory()) {
|
|
84
|
+
throw new WorldError("invalid-world", `world marker is not a directory: ${marker(candidate)}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
if (error instanceof WorldError)
|
|
89
|
+
throw error;
|
|
90
|
+
throw new WorldError("invalid-world", `world marker is not a directory: ${marker(candidate)}`);
|
|
91
|
+
}
|
|
92
|
+
return brand(candidate);
|
|
93
|
+
}
|
|
94
|
+
const parent = dirname(candidate);
|
|
95
|
+
if (parent === candidate || candidate === filesystemRoot)
|
|
96
|
+
return null;
|
|
97
|
+
candidate = parent;
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
at(input) {
|
|
101
|
+
return exact(input);
|
|
102
|
+
},
|
|
103
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astrosheep/keiyaku",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.1",
|
|
4
4
|
"files": [
|
|
5
5
|
"build"
|
|
6
6
|
],
|
|
@@ -34,10 +34,11 @@
|
|
|
34
34
|
"scripts": {
|
|
35
35
|
"build": "node --input-type=module -e \"import { rmSync } from 'node:fs'; rmSync('build', { recursive: true, force: true })\" && tsc -p tsconfig.json && node scripts/copy-integrations.js && node --input-type=module -e \"import { chmodSync } from 'node:fs'; chmodSync('build/src/cli/index.js', 0o755)\"",
|
|
36
36
|
"review:model-impact": "node --import tsx scripts/model-change-impact.ts",
|
|
37
|
-
"test": "npm run test:architecture && npm run test:maintainability && npm run test:reachability && node --import tsx
|
|
37
|
+
"test": "npm run test:architecture && npm run test:maintainability && npm run test:reachability && node --import tsx scripts/run-tests.mjs",
|
|
38
38
|
"test:architecture": "node --import tsx scripts/check-architecture.ts",
|
|
39
|
-
"test:
|
|
39
|
+
"test:e2e": "npm run build && node --import tsx --test tests/packaged-cli.e2e.ts",
|
|
40
40
|
"test:maintainability": "node scripts/check-maintainability.js",
|
|
41
|
+
"test:focused": "node --import tsx --test --test-reporter=dot",
|
|
41
42
|
"test:reachability": "knip --include files,dependencies,unlisted,unresolved,duplicates",
|
|
42
43
|
"test:typecheck": "tsc -p tsconfig.json --noEmit"
|
|
43
44
|
},
|
|
@@ -51,10 +52,12 @@
|
|
|
51
52
|
},
|
|
52
53
|
"dependencies": {
|
|
53
54
|
"@anthropic-ai/claude-agent-sdk": "^0.3.226",
|
|
55
|
+
"@earendil-works/pi-coding-agent": "0.80.10",
|
|
56
|
+
"@opencode-ai/sdk": "1.18.3",
|
|
54
57
|
"diff": "^8.0.4",
|
|
55
58
|
"yaml": "^2.9.0"
|
|
56
59
|
},
|
|
57
|
-
"description": "Keiyaku
|
|
60
|
+
"description": "Keiyaku is an agent-facing contract workflow.",
|
|
58
61
|
"directories": {
|
|
59
62
|
"doc": "docs",
|
|
60
63
|
"test": "tests"
|