@astrosheep/keiyaku 4.5.6 → 4.5.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/integrations/marketplace/plugins/keiyaku/opencode.js +1 -1
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku/SKILL.md +1 -0
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-babysit/SKILL.md +114 -0
- package/build/src/akuma/body.d.ts +1 -1
- package/build/src/akuma/body.js +20 -33
- package/build/src/akuma/heart/index.d.ts +2 -2
- package/build/src/akuma/heart/index.js +2 -2
- package/build/src/akuma/heart/request-authority.d.ts +1 -0
- package/build/src/akuma/heart/request-authority.js +11 -1
- package/build/src/akuma/heart/storage.d.ts +0 -1
- package/build/src/akuma/heart/storage.js +0 -54
- package/build/src/akuma/publication.js +75 -13
- package/build/src/akuma/request-lifecycle.js +9 -22
- package/build/src/akuma/request-serve.js +17 -2
- package/build/src/cli/commands/task-invoke.d.ts +4 -2
- package/build/src/cli/commands/task-invoke.js +48 -22
- package/build/src/cli/commands/task.d.ts +1 -1
- package/build/src/cli/commands/task.js +7 -3
- package/build/src/cli/coordinates.d.ts +2 -0
- package/build/src/cli/coordinates.js +18 -2
- package/build/src/cli/render/task.js +6 -5
- package/build/src/cli/square-edge.js +2 -2
- package/build/src/git/repository.d.ts +2 -0
- package/build/src/git/repository.js +34 -21
- package/build/src/git/target-placement.js +0 -6
- package/build/src/protocol/attempt.js +9 -4
- package/build/src/task/compose-language.d.ts +4 -0
- package/build/src/task/compose-language.js +7 -0
- package/build/src/task/compose-parser.d.ts +1 -0
- package/build/src/task/compose-parser.js +4 -2
- package/build/src/task/compose.d.ts +1 -0
- package/build/src/task/compose.js +2 -1
- package/build/src/task/context.d.ts +7 -1
- package/build/src/task/context.js +25 -6
- package/build/src/task/index.d.ts +6 -1
- package/package.json +2 -2
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
/* eslint-disable max-lines -- Task edge keeps native reads, forwarding, and local dispatch together. */
|
|
1
2
|
import { Tasks, } from "../../task/index.js";
|
|
3
|
+
import { taskCompositionNamespaceHeader } from "../../task/compose-language.js";
|
|
2
4
|
import { observeTaskDetails } from "../../task/operations.js";
|
|
3
5
|
import { injectedBodyRequests, requestBodyTask } from "../../akuma/requests.js";
|
|
4
6
|
import { decodeTaskMutationRequest } from "../../task/mutation.js";
|
|
@@ -191,11 +193,11 @@ function lifecycleMutationRequest(command) {
|
|
|
191
193
|
throw new Error(`task action cannot forward: ${command.action}`);
|
|
192
194
|
}
|
|
193
195
|
}
|
|
194
|
-
async function mutationRequest(command, input, current) {
|
|
196
|
+
async function mutationRequest(command, input, current, composeMarkdown) {
|
|
195
197
|
if (command.action === "add")
|
|
196
198
|
return await addMutationRequest(command, input, current);
|
|
197
199
|
if (command.action === "compose")
|
|
198
|
-
return { action: "task.compose", markdown: await input.readStdin(), namespace: current };
|
|
200
|
+
return { action: "task.compose", markdown: composeMarkdown ?? (await input.readStdin()), namespace: current };
|
|
199
201
|
if (command.action === "update")
|
|
200
202
|
return await updateMutationRequest(command, input);
|
|
201
203
|
return lifecycleMutationRequest(command);
|
|
@@ -296,22 +298,23 @@ function missingWorld(command) {
|
|
|
296
298
|
return { kind: "absent" };
|
|
297
299
|
if (command.action === "compose" && command.flags.plan === true)
|
|
298
300
|
return { kind: "absent" };
|
|
299
|
-
if (command.action === "
|
|
300
|
-
return { kind: "accepted", value: [] };
|
|
301
|
+
if (command.action === "context")
|
|
302
|
+
return { kind: "accepted", value: { namespace: [], source: "default-root" } };
|
|
301
303
|
if (command.action === "hold" || command.action === "done" || command.action === "drop") {
|
|
302
304
|
return { items: command.positionals.map((id) => ({ id: id, outcome: missing(id) })) };
|
|
303
305
|
}
|
|
304
306
|
return missing(command.positionals[0]);
|
|
305
307
|
}
|
|
306
|
-
async function invokeLocalMutation(
|
|
308
|
+
async function invokeLocalMutation(input) {
|
|
309
|
+
const { tasks, command, edge, current, composeMarkdown, composeNamespace } = input;
|
|
307
310
|
if (isWorldObservation(command))
|
|
308
311
|
return observeWorldRead(tasks, command, current);
|
|
309
312
|
if (command.action === "show" || command.action === "tree")
|
|
310
313
|
return invokeRead(tasks, command, current);
|
|
311
314
|
if (command.action === "add")
|
|
312
|
-
return await invokeAdd(tasks, command,
|
|
315
|
+
return await invokeAdd(tasks, command, edge, current ?? []);
|
|
313
316
|
if (command.action === "update")
|
|
314
|
-
return await invokeUpdate(tasks, command,
|
|
317
|
+
return await invokeUpdate(tasks, command, edge.readStdin);
|
|
315
318
|
const id = command.positionals[0];
|
|
316
319
|
switch (command.action) {
|
|
317
320
|
case "start":
|
|
@@ -330,17 +333,22 @@ async function invokeLocalMutation(tasks, command, input, current) {
|
|
|
330
333
|
const note = value(command, "note");
|
|
331
334
|
return tasks.batch({ verb: "drop", ids: command.positionals, ...(note === undefined ? {} : { note }) });
|
|
332
335
|
}
|
|
333
|
-
case "
|
|
336
|
+
case "context": {
|
|
334
337
|
const selected = namespace(command.positionals[0]);
|
|
335
|
-
if (selected !== undefined)
|
|
336
|
-
await writeTaskNamespaceContext(
|
|
337
|
-
|
|
338
|
+
if (selected !== undefined) {
|
|
339
|
+
await writeTaskNamespaceContext(edge.context.writeRoot ?? edge.context.directory, selected);
|
|
340
|
+
const resolved = await resolveTaskNamespaceContext(edge.context);
|
|
341
|
+
if (typeof resolved === "object" && "kind" in resolved)
|
|
342
|
+
return { kind: "refused", refusal: resolved };
|
|
343
|
+
return { kind: "accepted", value: resolved };
|
|
344
|
+
}
|
|
345
|
+
return { kind: "accepted", value: { namespace: current ?? [], source: "default-root" } };
|
|
338
346
|
}
|
|
339
347
|
case "compose":
|
|
340
348
|
return tasks.compose({
|
|
341
|
-
markdown: await
|
|
342
|
-
namespace: current ?? [],
|
|
343
|
-
...(
|
|
349
|
+
markdown: composeMarkdown ?? (await edge.readStdin()),
|
|
350
|
+
namespace: composeNamespace ?? current ?? [],
|
|
351
|
+
...(edge.actor === undefined ? {} : { actor: edge.actor }),
|
|
344
352
|
...(command.flags.plan === true ? { plan: true } : {}),
|
|
345
353
|
});
|
|
346
354
|
default:
|
|
@@ -350,24 +358,32 @@ async function invokeLocalMutation(tasks, command, input, current) {
|
|
|
350
358
|
function establishesWorld(command) {
|
|
351
359
|
return (command.action === "add" ||
|
|
352
360
|
(command.action === "compose" && command.flags.plan !== true) ||
|
|
353
|
-
(command.action === "
|
|
361
|
+
(command.action === "context" && command.positionals.length > 0));
|
|
354
362
|
}
|
|
355
363
|
function forwardsMutation(command) {
|
|
356
364
|
return (command.action !== "show" &&
|
|
357
365
|
command.action !== "tree" &&
|
|
358
|
-
command.action !== "
|
|
366
|
+
command.action !== "context" &&
|
|
359
367
|
!(command.action === "compose" && command.flags.plan === true) &&
|
|
360
368
|
!isWorldObservation(command));
|
|
361
369
|
}
|
|
370
|
+
// eslint-disable-next-line complexity -- this is the single CLI edge ordering world, context, forwarding, and local execution.
|
|
362
371
|
export async function invokeTask(command, input) {
|
|
363
372
|
const planOnly = command.action === "compose" && command.flags.plan === true;
|
|
373
|
+
const composeMarkdown = command.action === "compose" ? await input.readStdin() : undefined;
|
|
374
|
+
const composition = composeMarkdown === undefined ? undefined : taskCompositionNamespaceHeader(composeMarkdown);
|
|
375
|
+
const explicitNamespace = command.action === "add"
|
|
376
|
+
? namespace(value(command, "namespace"))
|
|
377
|
+
: command.action === "compose" && composition?.specified
|
|
378
|
+
? composition.namespace
|
|
379
|
+
: undefined;
|
|
364
380
|
const world = input.world ?? (planOnly ? input.candidate : establishesWorld(command) ? await input.establish() : null);
|
|
365
381
|
if (world === null)
|
|
366
382
|
return missingWorld(command);
|
|
367
383
|
const tasks = Tasks.of(world);
|
|
368
|
-
const contextSensitive = command.action === "
|
|
369
|
-
command.action === "add" ||
|
|
370
|
-
command.action === "compose" ||
|
|
384
|
+
const contextSensitive = command.action === "context" ||
|
|
385
|
+
(command.action === "add" && explicitNamespace === undefined) ||
|
|
386
|
+
(command.action === "compose" && composition?.specified !== true) ||
|
|
371
387
|
((command.action === "ls" ||
|
|
372
388
|
command.action === "ready" ||
|
|
373
389
|
command.action === "blocked" ||
|
|
@@ -378,12 +394,22 @@ export async function invokeTask(command, input) {
|
|
|
378
394
|
const resolved = await resolveTaskNamespaceContext(input.context);
|
|
379
395
|
if (typeof resolved === "object" && "kind" in resolved)
|
|
380
396
|
return { kind: "refused", refusal: resolved };
|
|
381
|
-
current = resolved;
|
|
397
|
+
current = resolved.namespace;
|
|
398
|
+
if (command.action === "context" && command.positionals.length === 0) {
|
|
399
|
+
return { kind: "accepted", value: resolved };
|
|
400
|
+
}
|
|
382
401
|
}
|
|
383
402
|
const requests = injectedBodyRequests();
|
|
384
403
|
if (requests !== null && forwardsMutation(command)) {
|
|
385
|
-
const request = validatedMutationRequest(await mutationRequest(command, input, current ?? []));
|
|
404
|
+
const request = validatedMutationRequest(await mutationRequest(command, input, explicitNamespace ?? current ?? [], composeMarkdown));
|
|
386
405
|
return (await requestBodyTask({ directory: requests, world, request }));
|
|
387
406
|
}
|
|
388
|
-
return await invokeLocalMutation(
|
|
407
|
+
return await invokeLocalMutation({
|
|
408
|
+
tasks,
|
|
409
|
+
command,
|
|
410
|
+
edge: input,
|
|
411
|
+
...(current === undefined ? {} : { current }),
|
|
412
|
+
...(composeMarkdown === undefined ? {} : { composeMarkdown }),
|
|
413
|
+
...(explicitNamespace === undefined ? {} : { composeNamespace: explicitNamespace }),
|
|
414
|
+
});
|
|
389
415
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type TaskQueryExpression } from "./task-query.js";
|
|
2
|
-
export type TaskAction = "add" | "show" | "ls" | "ready" | "blocked" | "tree" | "doctor" | "update" | "query" | "start" | "stop" | "hold" | "resume" | "done" | "drop" | "
|
|
2
|
+
export type TaskAction = "add" | "show" | "ls" | "ready" | "blocked" | "tree" | "doctor" | "update" | "query" | "start" | "stop" | "hold" | "resume" | "done" | "drop" | "context" | "compose";
|
|
3
3
|
type TaskFlagValue = string | true | readonly string[];
|
|
4
4
|
type TaskStdin = "document" | "body" | "append" | "note" | "compose";
|
|
5
5
|
export type ParsedTaskCommand = Readonly<{
|
|
@@ -38,18 +38,21 @@ task add [--namespace <ns>] [--actor <actor>] -`,
|
|
|
38
38
|
flags: { ...COMMON, closed: "boolean", all: "boolean", world: "boolean", limit: "value" },
|
|
39
39
|
usage: "task ls [--closed | --all] [--world] [--limit <n>]",
|
|
40
40
|
purpose: "List Tasks in the selected scope.",
|
|
41
|
+
details: "--world lists every namespace in the current Task world.",
|
|
41
42
|
},
|
|
42
43
|
ready: {
|
|
43
44
|
arity: [0, 0],
|
|
44
45
|
flags: { ...COMMON, world: "boolean", parent: "value", limit: "value" },
|
|
45
46
|
usage: "task ready [--world] [--parent <TaskId>] [--limit <n>]",
|
|
46
47
|
purpose: "List open Tasks whose every need is terminal.",
|
|
48
|
+
details: "--world lists every namespace in the current Task world.",
|
|
47
49
|
},
|
|
48
50
|
blocked: {
|
|
49
51
|
arity: [0, 0],
|
|
50
52
|
flags: { ...COMMON, world: "boolean", parent: "value", limit: "value" },
|
|
51
53
|
usage: "task blocked [--world] [--parent <TaskId>] [--limit <n>]",
|
|
52
54
|
purpose: "List Tasks blocked by dependencies.",
|
|
55
|
+
details: "--world lists every namespace in the current Task world.",
|
|
53
56
|
},
|
|
54
57
|
query: {
|
|
55
58
|
arity: [0, 0],
|
|
@@ -60,6 +63,7 @@ task add [--namespace <ns>] [--actor <actor>] -`,
|
|
|
60
63
|
details: [
|
|
61
64
|
"fields: state priority title id parent under needs blocks ready blocked created updated",
|
|
62
65
|
"operators: = != < > <= >= ~ and or not ( )",
|
|
66
|
+
"--world lists every namespace in the current Task world.",
|
|
63
67
|
"examples:",
|
|
64
68
|
" keiyaku task query --where 'priority <= 1 and ready' --world",
|
|
65
69
|
" keiyaku task query --where 'updated < 2026-08-06T00:00:00.000Z' --world",
|
|
@@ -124,11 +128,11 @@ task add [--namespace <ns>] [--actor <actor>] -`,
|
|
|
124
128
|
usage: "task drop <TaskId>... [--note <text>]",
|
|
125
129
|
purpose: "Drop one or more Tasks.",
|
|
126
130
|
},
|
|
127
|
-
|
|
131
|
+
context: {
|
|
128
132
|
arity: [0, 1],
|
|
129
133
|
flags: COMMON,
|
|
130
|
-
usage: "task
|
|
131
|
-
purpose: "Read or replace the
|
|
134
|
+
usage: "task context [<namespace>]",
|
|
135
|
+
purpose: "Read or replace the directory Task context.",
|
|
132
136
|
},
|
|
133
137
|
compose: {
|
|
134
138
|
arity: [0, 0],
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { realpath } from "node:fs/promises";
|
|
1
|
+
import { lstat, realpath } from "node:fs/promises";
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
3
|
import { NoGitWorldError, Repo } from "../library/repo.js";
|
|
4
4
|
import { World, WorldError } from "../world.js";
|
|
@@ -24,6 +24,17 @@ async function discoverRepoAt(coordinate, gitPath) {
|
|
|
24
24
|
throw error;
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
|
+
async function managedContractWorktree(root) {
|
|
28
|
+
try {
|
|
29
|
+
const stat = await lstat(resolve(root, ".keiyaku", "KEIYAKU.md"));
|
|
30
|
+
return stat.isFile() && !stat.isSymbolicLink();
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
if (error.code === "ENOENT")
|
|
34
|
+
return false;
|
|
35
|
+
throw error;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
27
38
|
function repoFor(use, discovery) {
|
|
28
39
|
if (use === "none")
|
|
29
40
|
return undefined;
|
|
@@ -72,7 +83,12 @@ export async function resolveCliCoordinates(input) {
|
|
|
72
83
|
...(repo === undefined ? {} : { repo }),
|
|
73
84
|
world: world.root,
|
|
74
85
|
candidateWorld: world.candidate,
|
|
75
|
-
taskContext: {
|
|
86
|
+
taskContext: {
|
|
87
|
+
directory: cwd,
|
|
88
|
+
boundary,
|
|
89
|
+
writeRoot: worldRepo === undefined ? boundary : cwd,
|
|
90
|
+
managed: await managedContractWorktree(boundary),
|
|
91
|
+
},
|
|
76
92
|
establishWorld: world.establish,
|
|
77
93
|
};
|
|
78
94
|
}
|
|
@@ -319,11 +319,12 @@ function renderTaskValue(command, result, columns) {
|
|
|
319
319
|
}
|
|
320
320
|
if (command.action === "doctor")
|
|
321
321
|
return renderDoctor(result);
|
|
322
|
-
if (command.action === "
|
|
323
|
-
const
|
|
324
|
-
if (
|
|
325
|
-
return renderFailure(command.action,
|
|
326
|
-
|
|
322
|
+
if (command.action === "context") {
|
|
323
|
+
const context = result;
|
|
324
|
+
if (context.kind !== "accepted")
|
|
325
|
+
return renderFailure(command.action, context, columns);
|
|
326
|
+
const value = context.value.namespace.length === 0 ? "root" : context.value.namespace.join("/");
|
|
327
|
+
return `context ${value} · ${context.value.source}`;
|
|
327
328
|
}
|
|
328
329
|
if (command.action === "compose")
|
|
329
330
|
return renderCompose(result, columns);
|
|
@@ -44,13 +44,13 @@ export async function recognizeAndListen(worldRoot, environment, allocated) {
|
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
if (bound)
|
|
47
|
-
unbindCurrentParticipant(path, name, environment);
|
|
47
|
+
await unbindCurrentParticipant(path, name, environment);
|
|
48
48
|
};
|
|
49
49
|
try {
|
|
50
50
|
const joinedResult = await square.joinWithActivity(name);
|
|
51
51
|
joined = joinedResult.activity !== null;
|
|
52
52
|
const participant = joinedResult.participant;
|
|
53
|
-
bound = bindCurrentParticipant(path, name, environment).created;
|
|
53
|
+
bound = (await bindCurrentParticipant(path, name, environment)).created;
|
|
54
54
|
const listener = participant;
|
|
55
55
|
const change = await listener.listen(allocated.id);
|
|
56
56
|
listening = change.activity !== null;
|
|
@@ -42,6 +42,8 @@ export declare function commonGitDirectory(repository: GitRepository): string;
|
|
|
42
42
|
export declare function worktreeGitDirectory(repository: GitRepository, worktree: string): Promise<string>;
|
|
43
43
|
export declare function worktreeRoot(repository: GitRepository): Promise<string>;
|
|
44
44
|
export declare function readRef(repository: GitRepository, ref: string): Promise<GitOid | null>;
|
|
45
|
+
/** Read an explicit set of asserted refs from one fresh Git observation. */
|
|
46
|
+
export declare function readRefs(repository: GitRepository, refs: readonly string[]): Promise<Map<any, any>>;
|
|
45
47
|
export declare function readTreeEntries(repository: GitRepository, tree: GitOid): Promise<Map<string, TreeEntry>>;
|
|
46
48
|
export declare function readGit(repository: GitRepository): Promise<GitSnapshot>;
|
|
47
49
|
/** Read only the requested private Git paths from one immutable Git tree. */
|
|
@@ -113,9 +113,6 @@ export async function worktreeGitDirectory(repository, worktree) {
|
|
|
113
113
|
export async function worktreeRoot(repository) {
|
|
114
114
|
return await absoluteGitPath(repository, ["--show-toplevel"], "worktree root");
|
|
115
115
|
}
|
|
116
|
-
function assertOid(oid, label) {
|
|
117
|
-
gitObjectId(oid, label);
|
|
118
|
-
}
|
|
119
116
|
function assertRef(ref) {
|
|
120
117
|
if (!ref.startsWith("refs/") || /[\s\0]/.test(ref) || ref.endsWith("/") || ref.includes("..")) {
|
|
121
118
|
throw new Error(`invalid Git ref: ${ref}`);
|
|
@@ -129,10 +126,7 @@ export async function readRef(repository, ref) {
|
|
|
129
126
|
assertAssertionRef(ref);
|
|
130
127
|
try {
|
|
131
128
|
const oid = (await runGit(repository, ["rev-parse", "--verify", "--quiet", ref])).toString("utf8").trim();
|
|
132
|
-
|
|
133
|
-
return null;
|
|
134
|
-
assertOid(oid, `ref ${ref}`);
|
|
135
|
-
return oid;
|
|
129
|
+
return oid.length === 0 ? null : gitObjectId(oid, `ref ${ref}`);
|
|
136
130
|
}
|
|
137
131
|
catch (error) {
|
|
138
132
|
if (error instanceof GitPlumbingError && error.status === 1)
|
|
@@ -140,10 +134,29 @@ export async function readRef(repository, ref) {
|
|
|
140
134
|
throw error;
|
|
141
135
|
}
|
|
142
136
|
}
|
|
137
|
+
/** Read an explicit set of asserted refs from one fresh Git observation. */
|
|
138
|
+
export async function readRefs(repository, refs) {
|
|
139
|
+
const refsToRead = [...new Set(refs)];
|
|
140
|
+
for (const ref of refsToRead)
|
|
141
|
+
assertAssertionRef(ref);
|
|
142
|
+
if (refsToRead.length === 0)
|
|
143
|
+
return new Map();
|
|
144
|
+
const output = (await runGit(repository, ["cat-file", "--batch-check=%(objectname)"], `${refsToRead.join("\n")}\n`)).toString("utf8");
|
|
145
|
+
const records = output.split("\n");
|
|
146
|
+
if (records.pop() !== "" || records.length !== refsToRead.length)
|
|
147
|
+
throw new Error("Git ref batch output is malformed");
|
|
148
|
+
return new Map(refsToRead.map((ref, index) => {
|
|
149
|
+
const oid = records[index];
|
|
150
|
+
if (oid === `${ref} missing`)
|
|
151
|
+
return [ref, null];
|
|
152
|
+
gitObjectId(oid, `ref ${ref}`);
|
|
153
|
+
return [ref, oid];
|
|
154
|
+
}));
|
|
155
|
+
}
|
|
143
156
|
async function readTreeForCommit(repository, commit) {
|
|
144
|
-
|
|
157
|
+
gitObjectId(commit, "Git commit");
|
|
145
158
|
const tree = (await runGit(repository, ["show", "-s", "--format=%T", commit])).toString("utf8").trim();
|
|
146
|
-
|
|
159
|
+
gitObjectId(tree, "Git tree");
|
|
147
160
|
return tree;
|
|
148
161
|
}
|
|
149
162
|
function parseTreeEntries(output) {
|
|
@@ -159,13 +172,13 @@ function parseTreeEntries(output) {
|
|
|
159
172
|
if (mode === undefined || type === undefined || oid === undefined || path.length === 0) {
|
|
160
173
|
throw new GitPlumbingError({ stderr: record, status: null, message: `malformed ls-tree record: ${record}` });
|
|
161
174
|
}
|
|
162
|
-
|
|
175
|
+
gitObjectId(oid, `tree entry ${path}`);
|
|
163
176
|
entries.set(path, { mode, type, oid });
|
|
164
177
|
}
|
|
165
178
|
return entries;
|
|
166
179
|
}
|
|
167
180
|
export async function readTreeEntries(repository, tree) {
|
|
168
|
-
|
|
181
|
+
gitObjectId(tree, "tree");
|
|
169
182
|
const output = await runGit(repository, ["ls-tree", "-r", "-z", "--full-tree", tree]);
|
|
170
183
|
return parseTreeEntries(output);
|
|
171
184
|
}
|
|
@@ -193,11 +206,11 @@ export async function readGitPaths(repository, requestedPaths) {
|
|
|
193
206
|
}
|
|
194
207
|
export async function writeBlob(repository, bytes) {
|
|
195
208
|
const oid = (await runGit(repository, ["hash-object", "-w", "--stdin"], bytes)).toString("utf8").trim();
|
|
196
|
-
|
|
209
|
+
gitObjectId(oid, "written blob");
|
|
197
210
|
return oid;
|
|
198
211
|
}
|
|
199
212
|
export async function readBlob(repository, oid) {
|
|
200
|
-
|
|
213
|
+
gitObjectId(oid, "blob");
|
|
201
214
|
return await runGit(repository, ["cat-file", "blob", oid]);
|
|
202
215
|
}
|
|
203
216
|
function malformedBatchOutput(detail) {
|
|
@@ -210,7 +223,7 @@ function malformedBatchOutput(detail) {
|
|
|
210
223
|
async function readObjects(repository, oids) {
|
|
211
224
|
const unique = [...new Set(oids)];
|
|
212
225
|
for (const oid of unique)
|
|
213
|
-
|
|
226
|
+
gitObjectId(oid, "Git object");
|
|
214
227
|
if (unique.length === 0)
|
|
215
228
|
return new Map();
|
|
216
229
|
const output = await runGit(repository, ["cat-file", "--batch"], `${unique.join("\n")}\n`);
|
|
@@ -305,7 +318,7 @@ async function writePreparedTrees(repository, prepared) {
|
|
|
305
318
|
for (let index = 0; index < prepared.length; index += 1) {
|
|
306
319
|
const expected = prepared[index].oid;
|
|
307
320
|
const actual = written[index];
|
|
308
|
-
|
|
321
|
+
gitObjectId(actual, "written tree");
|
|
309
322
|
if (actual !== expected) {
|
|
310
323
|
throw new GitPlumbingError({ stderr: output, status: null, message: `mktree wrote unexpected tree: ${actual}` });
|
|
311
324
|
}
|
|
@@ -338,9 +351,9 @@ export async function updateGitTreeFromFrozenDirectories(repository, baseTree, d
|
|
|
338
351
|
}
|
|
339
352
|
async function writeCommitObject(input) {
|
|
340
353
|
const { repository, tree, parents, message, actor, email, at } = input;
|
|
341
|
-
|
|
354
|
+
gitObjectId(tree, "commit tree");
|
|
342
355
|
for (const parent of parents)
|
|
343
|
-
|
|
356
|
+
gitObjectId(parent, "commit parent");
|
|
344
357
|
const args = ["commit-tree", tree];
|
|
345
358
|
for (const parent of parents)
|
|
346
359
|
args.push("-p", parent);
|
|
@@ -353,7 +366,7 @@ async function writeCommitObject(input) {
|
|
|
353
366
|
}))
|
|
354
367
|
.toString("utf8")
|
|
355
368
|
.trim();
|
|
356
|
-
|
|
369
|
+
gitObjectId(commit, "written Git commit");
|
|
357
370
|
return commit;
|
|
358
371
|
}
|
|
359
372
|
export async function writeCommit(input) {
|
|
@@ -369,7 +382,7 @@ export async function writeCommit(input) {
|
|
|
369
382
|
}
|
|
370
383
|
function refAssertionLine(assertion) {
|
|
371
384
|
assertAssertionRef(assertion.ref);
|
|
372
|
-
|
|
385
|
+
gitObjectId(assertion.oid, `expected oid for ${assertion.ref}`);
|
|
373
386
|
return `${assertion.ref === "HEAD" ? "" : "option no-deref\n"}verify ${assertion.ref} ${assertion.oid}`;
|
|
374
387
|
}
|
|
375
388
|
function refUpdateLine(update, index) {
|
|
@@ -378,9 +391,9 @@ function refUpdateLine(update, index) {
|
|
|
378
391
|
throw new Error(`duplicate ref update: ${update.ref}`);
|
|
379
392
|
if (index > 0 && update.expectedOid === null)
|
|
380
393
|
throw new Error("target ref updates require non-null OIDs");
|
|
381
|
-
|
|
394
|
+
gitObjectId(update.newOid, `new oid for ${update.ref}`);
|
|
382
395
|
if (update.expectedOid !== null)
|
|
383
|
-
|
|
396
|
+
gitObjectId(update.expectedOid, `expected oid for ${update.ref}`);
|
|
384
397
|
return `option no-deref\nupdate ${update.ref} ${update.newOid} ${update.expectedOid ?? "0".repeat(update.newOid.length)}`;
|
|
385
398
|
}
|
|
386
399
|
export async function updateRefsAtomically(repository, updates, assertions = []) {
|
|
@@ -280,8 +280,6 @@ async function classifyCheckout(input) {
|
|
|
280
280
|
const candidateWorkspace = await workspaceMatchesTreeOnPaths(repository, path, candidateTree, workspaceTree, changedPaths);
|
|
281
281
|
if (candidateIndex && candidateWorkspace)
|
|
282
282
|
return { kind: "complete" };
|
|
283
|
-
if (workspaceTree === candidateTree)
|
|
284
|
-
return { kind: "recoverable", action: "candidate-index" };
|
|
285
283
|
const predecessorIndex = await indexMatchesTreeOnPaths(repository, path, predecessorTree, changedPaths);
|
|
286
284
|
if (predecessorIndex && candidateWorkspace)
|
|
287
285
|
return { kind: "recoverable", action: "index-merge" };
|
|
@@ -305,10 +303,6 @@ async function recoverCheckout(input) {
|
|
|
305
303
|
if (classified.kind === "retained")
|
|
306
304
|
return "retained";
|
|
307
305
|
const { repository, path, predecessor, candidate } = input;
|
|
308
|
-
if (classified.action === "candidate-index") {
|
|
309
|
-
await runGit(repository, ["-C", path, "read-tree", gitObjectIdForSnapshot(candidate)]);
|
|
310
|
-
return "recovered";
|
|
311
|
-
}
|
|
312
306
|
if (classified.action === "index-merge") {
|
|
313
307
|
await runGit(repository, [
|
|
314
308
|
"-C",
|
|
@@ -2,7 +2,7 @@ import { randomBytes } from "node:crypto";
|
|
|
2
2
|
import { encodeEntry } from "../core/facts/codec.js";
|
|
3
3
|
import { admit } from "../git/admission.js";
|
|
4
4
|
import { observeContractsForAdmissionAt } from "../git/observe.js";
|
|
5
|
-
import { GIT_REF,
|
|
5
|
+
import { GIT_REF, readRefs } from "../git/repository.js";
|
|
6
6
|
import { foldJournal } from "../core/facts/fold.js";
|
|
7
7
|
import { AuthorityCorruptionError } from "../core/facts/errors.js";
|
|
8
8
|
import { entryUlid, } from "../core/facts/types.js";
|
|
@@ -78,12 +78,17 @@ function recoveredAcceptance(observation, offer, primaryContract) {
|
|
|
78
78
|
};
|
|
79
79
|
}
|
|
80
80
|
async function publicationPremiseMoved(repository, observation, offer, assertions) {
|
|
81
|
-
|
|
81
|
+
const refs = await readRefs(repository, [
|
|
82
|
+
GIT_REF,
|
|
83
|
+
...assertions.map((assertion) => assertion.ref),
|
|
84
|
+
...(offer.target === undefined ? [] : [offer.target.target]),
|
|
85
|
+
]);
|
|
86
|
+
if (refs.get(GIT_REF) !== observation.admission.snapshot.commit)
|
|
82
87
|
return true;
|
|
83
88
|
for (const assertion of assertions)
|
|
84
|
-
if ((
|
|
89
|
+
if (refs.get(assertion.ref) !== assertion.oid)
|
|
85
90
|
return true;
|
|
86
|
-
return offer.target !== undefined && (
|
|
91
|
+
return offer.target !== undefined && refs.get(offer.target.target) !== offer.target.expectedOid;
|
|
87
92
|
}
|
|
88
93
|
/** Admit one decided offer without making another legal decision. */
|
|
89
94
|
export async function admitDecidedOffer(input) {
|
|
@@ -2,6 +2,10 @@ import type { TaskBoard } from "./board.js";
|
|
|
2
2
|
import { type TaskDocument } from "./document.js";
|
|
3
3
|
import { type TaskId } from "./identity.js";
|
|
4
4
|
import { type TaskCompositionDiagnostic } from "./operations.js";
|
|
5
|
+
export declare function taskCompositionNamespaceHeader(markdown: string): Readonly<{
|
|
6
|
+
specified: boolean;
|
|
7
|
+
namespace?: readonly string[];
|
|
8
|
+
}>;
|
|
5
9
|
export type TaskCompositionAlias = Readonly<{
|
|
6
10
|
alias: string;
|
|
7
11
|
taskId: TaskId;
|
|
@@ -2,6 +2,13 @@ import { serializeTaskDocument } from "./document.js";
|
|
|
2
2
|
import { allocateLocalId, deriveLocalStem, formatTaskId, parseTaskId, sameNamespace } from "./identity.js";
|
|
3
3
|
import { parseTaskComposition } from "./compose-parser.js";
|
|
4
4
|
import { advanceTaskTimestamp } from "./operations.js";
|
|
5
|
+
export function taskCompositionNamespaceHeader(markdown) {
|
|
6
|
+
const parsed = parseTaskComposition(markdown);
|
|
7
|
+
return {
|
|
8
|
+
specified: parsed.namespaceSpecified,
|
|
9
|
+
...(parsed.namespace === undefined ? {} : { namespace: parsed.namespace }),
|
|
10
|
+
};
|
|
11
|
+
}
|
|
5
12
|
function diagnostic(line, reason, token) {
|
|
6
13
|
return { line, reason, token };
|
|
7
14
|
}
|
|
@@ -32,8 +32,10 @@ function withoutFenceSeparator(value) {
|
|
|
32
32
|
return value;
|
|
33
33
|
}
|
|
34
34
|
function parseNamespace(value) {
|
|
35
|
-
if (value === "")
|
|
35
|
+
if (value === "/")
|
|
36
36
|
return [];
|
|
37
|
+
if (value === "")
|
|
38
|
+
throw new Error("empty compose namespace; use ns=/ for root");
|
|
37
39
|
return parseTaskId(`task/${value}/placeholder`).namespace;
|
|
38
40
|
}
|
|
39
41
|
function parseAssignment(text, line) {
|
|
@@ -174,5 +176,5 @@ export function parseTaskComposition(source) {
|
|
|
174
176
|
replaceNode(nodes, current);
|
|
175
177
|
}
|
|
176
178
|
}
|
|
177
|
-
return { ...(namespace === undefined ? {} : { namespace }), nodes, diagnostics };
|
|
179
|
+
return { ...(namespace === undefined ? {} : { namespace }), namespaceSpecified: sawNamespace, nodes, diagnostics };
|
|
178
180
|
}
|
|
@@ -3,6 +3,7 @@ import { type TaskCompositionAlias, type TaskCompositionBodyPreview } from "./co
|
|
|
3
3
|
import type { TaskId } from "./identity.js";
|
|
4
4
|
import type { TaskRefusal, TaskRetry } from "./operations.js";
|
|
5
5
|
export type { TaskCompositionAlias, TaskCompositionBodyPreview } from "./compose-language.js";
|
|
6
|
+
export { taskCompositionNamespaceHeader } from "./compose-language.js";
|
|
6
7
|
export type TaskDocumentChange = Readonly<{
|
|
7
8
|
taskId: TaskId;
|
|
8
9
|
kind: "created" | "updated";
|
|
@@ -2,6 +2,7 @@ import { documentDiff } from "../markdown/diff.js";
|
|
|
2
2
|
import { planTaskComposition, } from "./compose-language.js";
|
|
3
3
|
import { serializeTaskDocument } from "./document.js";
|
|
4
4
|
import { authorityPath, readBoard, replaceAuthority, withTaskLocks } from "./store.js";
|
|
5
|
+
export { taskCompositionNamespaceHeader } from "./compose-language.js";
|
|
5
6
|
function refusal(diagnostics) {
|
|
6
7
|
return { kind: "refused", refusal: { kind: "invalid-composition", diagnostics } };
|
|
7
8
|
}
|
|
@@ -46,7 +47,7 @@ function recoveryDraft(namespace, remaining) {
|
|
|
46
47
|
if (task.kind === "new" && task.alias !== undefined)
|
|
47
48
|
aliases.set(task.after.id, task.alias);
|
|
48
49
|
}
|
|
49
|
-
const lines = [`ns=${namespace.join("/")}`];
|
|
50
|
+
const lines = [`ns=${namespace.length === 0 ? "/" : namespace.join("/")}`];
|
|
50
51
|
for (const task of remaining)
|
|
51
52
|
lines.push("", ...taskDraft(task, aliases));
|
|
52
53
|
return `${lines.join("\n")}\n`;
|
|
@@ -5,11 +5,17 @@ export type NamespaceContextRead = readonly string[] | "absent" | Readonly<{
|
|
|
5
5
|
export type NamespaceContextInput = Readonly<{
|
|
6
6
|
directory: string;
|
|
7
7
|
boundary: string;
|
|
8
|
+
managed?: boolean;
|
|
9
|
+
}>;
|
|
10
|
+
export type NamespaceContextSource = "default-root" | "contract-installed" | "local-override";
|
|
11
|
+
export type ResolvedNamespaceContext = Readonly<{
|
|
12
|
+
namespace: readonly string[];
|
|
13
|
+
source: NamespaceContextSource;
|
|
8
14
|
}>;
|
|
9
15
|
export declare function readNamespaceContext(coordinates: NamespaceContextInput): Promise<NamespaceContextRead>;
|
|
10
16
|
export declare function installNamespaceContext(root: string, segments: readonly string[]): Promise<void>;
|
|
11
17
|
export declare function repairNamespaceContext(root: string, segments: readonly string[]): Promise<"kept" | "installed">;
|
|
12
|
-
export declare function resolveTaskNamespaceContext(input: NamespaceContextInput): Promise<
|
|
18
|
+
export declare function resolveTaskNamespaceContext(input: NamespaceContextInput): Promise<ResolvedNamespaceContext | Readonly<{
|
|
13
19
|
kind: "invalid-namespace-context";
|
|
14
20
|
path: string;
|
|
15
21
|
}>>;
|
|
@@ -40,6 +40,12 @@ async function readAt(root) {
|
|
|
40
40
|
const segments = content === "" ? [] : content.split("/");
|
|
41
41
|
return validNamespaceSegments(segments) ? segments : { kind: "malformed", path: currentPath(root) };
|
|
42
42
|
}
|
|
43
|
+
async function readAtWithPath(root) {
|
|
44
|
+
const selected = await readAt(root);
|
|
45
|
+
if (selected === "absent" || (typeof selected === "object" && "kind" in selected))
|
|
46
|
+
return selected;
|
|
47
|
+
return { value: selected, path: currentPath(root) };
|
|
48
|
+
}
|
|
43
49
|
export async function readNamespaceContext(coordinates) {
|
|
44
50
|
let current = coordinates.directory;
|
|
45
51
|
for (;;) {
|
|
@@ -75,12 +81,25 @@ export async function repairNamespaceContext(root, segments) {
|
|
|
75
81
|
return "installed";
|
|
76
82
|
}
|
|
77
83
|
export async function resolveTaskNamespaceContext(input) {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
+
let current = input.directory;
|
|
85
|
+
for (;;) {
|
|
86
|
+
const selected = await readAtWithPath(current);
|
|
87
|
+
if (selected !== "absent") {
|
|
88
|
+
if (typeof selected === "object" && "kind" in selected)
|
|
89
|
+
return { kind: "invalid-namespace-context", path: selected.path };
|
|
90
|
+
return {
|
|
91
|
+
namespace: "value" in selected ? selected.value : selected,
|
|
92
|
+
source: current === input.boundary && input.managed === true ? "contract-installed" : "local-override",
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
if (current === input.boundary)
|
|
96
|
+
return { namespace: [], source: "default-root" };
|
|
97
|
+
const resolvedParent = dirname(resolve(current));
|
|
98
|
+
if (resolvedParent === current || !resolvedParent.startsWith(`${input.boundary}/`)) {
|
|
99
|
+
return { namespace: [], source: "default-root" };
|
|
100
|
+
}
|
|
101
|
+
current = resolvedParent;
|
|
102
|
+
}
|
|
84
103
|
}
|
|
85
104
|
export async function writeTaskNamespaceContext(directory, value) {
|
|
86
105
|
await installNamespaceContext(directory, value);
|