@astrosheep/keiyaku 2.9.4 → 2.9.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/.tsbuildinfo +1 -1
- package/build/agents/call-terms.js +1 -0
- package/build/agents/harness/execution-handle.js +1 -0
- package/build/agents/harness/outcome.js +31 -5
- package/build/agents/harness/projection.js +151 -78
- package/build/agents/harness/router.js +2 -1
- package/build/agents/providers/claude-agent-sdk/adapter.js +13 -1
- package/build/agents/providers/claude-agent-sdk/registration.js +2 -1
- package/build/agents/providers/codex-app-server/adapter.js +43 -10
- package/build/agents/providers/codex-app-server/events.js +23 -0
- package/build/agents/providers/codex-app-server/registration.js +2 -1
- package/build/agents/providers/opencode-sdk/adapter.js +7 -0
- package/build/agents/providers/opencode-sdk/registration.js +2 -1
- package/build/agents/providers/pi/adapter.js +10 -0
- package/build/agents/providers/pi/events.js +4 -3
- package/build/agents/providers/pi/registration.js +3 -1
- package/build/agents/types.js +2 -0
- package/build/cli/commands/akuma/akuma/meta.js +1 -1
- package/build/cli/commands/akuma/list/meta-ls.js +1 -1
- package/build/cli/commands/akuma/view/meta.js +1 -1
- package/build/cli/commands/contract/amend/meta.js +4 -4
- package/build/cli/commands/contract/arc/handler.js +1 -1
- package/build/cli/commands/contract/arc/meta.js +4 -4
- package/build/cli/commands/contract/audit/handler.js +16 -0
- package/build/cli/commands/contract/audit/meta.js +16 -0
- package/build/cli/commands/contract/bind/meta.js +20 -11
- package/build/cli/commands/contract/catalog.js +2 -0
- package/build/cli/commands/contract/forfeit/meta.js +1 -1
- package/build/cli/commands/contract/log/meta.js +1 -1
- package/build/cli/commands/contract/petition/handler.js +1 -2
- package/build/cli/commands/contract/petition/meta.js +6 -6
- package/build/cli/commands/contract/renew/handler.js +2 -2
- package/build/cli/commands/contract/renew/meta.js +4 -4
- package/build/cli/commands/metadata.js +3 -2
- package/build/cli/commands/projection/call/meta.js +4 -1
- package/build/cli/commands/projection/kill/meta.js +1 -1
- package/build/cli/commands/projection/revive/meta.js +4 -1
- package/build/cli/commands/projection/status/meta.js +1 -1
- package/build/cli/commands/projection/tell/handler.js +1 -1
- package/build/cli/commands/projection/tell/meta.js +4 -1
- package/build/cli/commands/projection/wait/meta.js +1 -1
- package/build/cli/commands/skills/install/meta.js +1 -1
- package/build/cli/commands/skills/skills/meta.js +1 -1
- package/build/cli/commands/system/completion/meta.js +1 -1
- package/build/cli/commands/system/dump-env/meta.js +1 -1
- package/build/cli/commands/system/guide/meta.js +1 -1
- package/build/cli/commands/task/add/handler.js +2 -2
- package/build/cli/commands/task/add/meta.js +4 -1
- package/build/cli/commands/task/doctor/meta.js +1 -1
- package/build/cli/commands/task/done/meta.js +1 -1
- package/build/cli/commands/task/drop/meta.js +1 -1
- package/build/cli/commands/task/ls/handler.js +10 -4
- package/build/cli/commands/task/ls/meta.js +3 -3
- package/build/cli/commands/task/show/meta.js +1 -1
- package/build/cli/commands/task/start/meta.js +1 -1
- package/build/cli/commands/task/stop/meta.js +1 -1
- package/build/cli/commands/task/task/meta.js +1 -1
- package/build/cli/commands/task/update/handler.js +2 -3
- package/build/cli/commands/task/update/meta.js +4 -1
- package/build/cli/completion.js +9 -2
- package/build/cli/flags.js +19 -3
- package/build/cli/help.js +3 -3
- package/build/cli/index.js +59 -75
- package/build/cli/output-stream-error-policy.js +19 -5
- package/build/cli/parse-flags.js +19 -2
- package/build/cli/parse-metadata.js +1 -7
- package/build/cli/parse-selectors.js +1 -0
- package/build/cli/parse.js +16 -7
- package/build/cli/projection-address.js +6 -1
- package/build/cli/render/audit.js +121 -0
- package/build/cli/render/errors.js +18 -1
- package/build/cli/render/format.js +5 -2
- package/build/cli/render/misc.js +3 -1
- package/build/cli/render/projection-activity.js +12 -6
- package/build/cli/render/status.js +28 -18
- package/build/cli/render/success-response.js +6 -5
- package/build/cli/render/task.js +12 -43
- package/build/cli/render/terminal-failure.js +7 -2
- package/build/cli/render/wait.js +36 -8
- package/build/config/env-keys.js +2 -0
- package/build/config/env.js +3 -0
- package/build/core/amend.js +20 -94
- package/build/core/arc.js +14 -6
- package/build/core/audit/candidate.js +48 -0
- package/build/core/audit/coordinates.js +180 -0
- package/build/core/audit/evidence.js +158 -0
- package/build/core/audit/facade.js +42 -0
- package/build/core/audit/readiness.js +4 -0
- package/build/core/audit/report.js +38 -0
- package/build/core/audit.js +1 -0
- package/build/core/bind.js +1 -1
- package/build/core/call/execution.js +1 -0
- package/build/core/contract-view.js +2 -3
- package/build/core/draft.js +33 -20
- package/build/core/entry.js +3 -4
- package/build/core/markdown/lex.js +2 -1
- package/build/core/projection/generation/database.js +41 -16
- package/build/core/projection/generation/model.js +22 -2
- package/build/core/projection/generation/projection-generation-execution.js +20 -7
- package/build/core/projection/generation/projection-generation-launcher.js +4 -4
- package/build/core/projection/generation/projection-generation-runner.js +1 -0
- package/build/core/projection/generation/store.js +1 -1
- package/build/core/projection/generation/transitions.js +19 -3
- package/build/core/projection/index.js +2 -2
- package/build/core/projection/projection-execution-observer.js +35 -21
- package/build/core/projection/projection-life-observer.js +16 -2
- package/build/core/projection/projection-status.js +67 -22
- package/build/core/projection/projection-terminal-failure.js +2 -0
- package/build/core/projection/projection-wait.js +6 -9
- package/build/core/projection/projection-wake.js +30 -6
- package/build/core/renew.js +41 -22
- package/build/core/scope.js +129 -2
- package/build/core/seal.js +36 -15
- package/build/core/settlement/petition-claim-gates.js +4 -33
- package/build/core/settlement/petition-preview.js +1 -1
- package/build/core/settlement/petition.js +10 -3
- package/build/core/settlement/settlement.js +3 -10
- package/build/core/settlement/verification.js +59 -0
- package/build/core/status/board.js +48 -28
- package/build/core/stored-agent-event.js +3 -2
- package/build/core/task/board.js +1 -0
- package/build/core/task/commands.js +20 -12
- package/build/core/task/document.js +5 -0
- package/build/core/task/index.js +1 -1
- package/build/core/task/model.js +8 -1
- package/build/core/task/query.js +214 -0
- package/build/core/task/settlement-git.js +115 -16
- package/build/core/task/settlement-policy.js +17 -9
- package/build/core/task/source-board.js +1 -0
- package/build/core/task/task-contract.js +125 -51
- package/build/core/task/task-store-repository.js +1 -0
- package/build/core/task/task.js +2 -0
- package/build/flow-error.js +2 -4
- package/build/generated/version.js +2 -2
- package/build/git/diff/parsers.js +23 -13
- package/build/git/diff/structured.js +127 -0
- package/build/index.js +7 -7
- package/build/telemetry/logger.js +4 -2
- package/package.json +1 -1
- package/skills/keiyaku/SKILL.md +2 -2
- package/skills/keiyaku-akuma/SKILL.md +17 -6
- package/skills/keiyaku-task/SKILL.md +16 -7
- package/skills/keiyaku-workflow/SKILL.md +48 -13
|
@@ -166,12 +166,6 @@ export async function trackedTaskPaths(cwd, commit) {
|
|
|
166
166
|
export async function refHead(cwd, ref) {
|
|
167
167
|
return (await git(cwd, ["rev-parse", ref])).trim();
|
|
168
168
|
}
|
|
169
|
-
export async function readableRefHead(cwd, ref) {
|
|
170
|
-
const result = await gitResult(cwd, ["rev-parse", ref]);
|
|
171
|
-
if (result.status !== 0)
|
|
172
|
-
return undefined;
|
|
173
|
-
return result.stdout.trim();
|
|
174
|
-
}
|
|
175
169
|
export async function taskContractAtCommit(cwd, commit, id) {
|
|
176
170
|
const sourcePath = `.keiyaku/tasks/${id}.md`;
|
|
177
171
|
const result = await gitResult(cwd, ["show", `${commit}:${sourcePath}`]);
|
|
@@ -179,6 +173,37 @@ export async function taskContractAtCommit(cwd, commit, id) {
|
|
|
179
173
|
return undefined;
|
|
180
174
|
return parseTaskDocument({ path: sourcePath, text: result.stdout }).contractId;
|
|
181
175
|
}
|
|
176
|
+
/** Bound task ids and their complete parent ancestry from the immutable bind base. */
|
|
177
|
+
export async function taskSettlementOwnershipAtCommit(cwd, commit, contractId) {
|
|
178
|
+
const documents = new Map();
|
|
179
|
+
for (const entry of await taskTreeEntries(cwd, commit)) {
|
|
180
|
+
const text = await git(cwd, ["cat-file", "blob", entry.oid]);
|
|
181
|
+
const document = parseTaskDocument({ path: entry.path, text });
|
|
182
|
+
documents.set(document.id, document);
|
|
183
|
+
}
|
|
184
|
+
const taskIds = [...documents.values()]
|
|
185
|
+
.filter((document) => document.contractId === contractId)
|
|
186
|
+
.map((document) => document.id)
|
|
187
|
+
.sort();
|
|
188
|
+
const ownedIds = new Set();
|
|
189
|
+
for (const taskId of taskIds) {
|
|
190
|
+
let current = taskId;
|
|
191
|
+
while (current !== null) {
|
|
192
|
+
if (ownedIds.has(current))
|
|
193
|
+
break;
|
|
194
|
+
ownedIds.add(current);
|
|
195
|
+
const document = documents.get(current);
|
|
196
|
+
if (!document) {
|
|
197
|
+
throw new FlowError("INTERNAL_STATE", `bind base ${commit} lost task ${current} from the owned ancestry`);
|
|
198
|
+
}
|
|
199
|
+
current = document.parent;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return {
|
|
203
|
+
taskIds,
|
|
204
|
+
paths: [...ownedIds].map((id) => `.keiyaku/tasks/${id}.md`).sort(),
|
|
205
|
+
};
|
|
206
|
+
}
|
|
182
207
|
export async function updateBranch(input, deps) {
|
|
183
208
|
if (deps.updateBranch)
|
|
184
209
|
await deps.updateBranch(input);
|
|
@@ -216,7 +241,7 @@ async function treeWithoutTasks(cwd, commit, gitDir) {
|
|
|
216
241
|
fs.rmSync(temporaryDirectory, { recursive: true, force: true });
|
|
217
242
|
}
|
|
218
243
|
}
|
|
219
|
-
async function composeLandingTree(input) {
|
|
244
|
+
export async function composeLandingTree(input) {
|
|
220
245
|
const baseTree = await treeWithoutTasks(input.cwd, input.base, input.gitDir);
|
|
221
246
|
const targetTree = await treeWithoutTasks(input.cwd, input.target, input.gitDir);
|
|
222
247
|
const deliveryTree = await treeWithoutTasks(input.cwd, input.delivery, input.gitDir);
|
|
@@ -263,24 +288,98 @@ async function composeLandingTree(input) {
|
|
|
263
288
|
fs.rmSync(temporaryDirectory, { recursive: true, force: true });
|
|
264
289
|
}
|
|
265
290
|
}
|
|
291
|
+
function landingCommitMessage(contractId, targetBranch) {
|
|
292
|
+
return `keiyaku: claim ${contractId}\n\nMerge contract ${contractId} into ${targetBranch}.\n`;
|
|
293
|
+
}
|
|
294
|
+
async function isAncestor(cwd, ancestor, descendant) {
|
|
295
|
+
return (await gitResult(cwd, ["merge-base", "--is-ancestor", ancestor, descendant])).status === 0;
|
|
296
|
+
}
|
|
297
|
+
export async function taskSourcesAtCommit(cwd, root, commit, paths) {
|
|
298
|
+
const sources = [];
|
|
299
|
+
for (const sourcePath of paths) {
|
|
300
|
+
const result = await gitResult(cwd, ["show", `${commit}:${sourcePath}`]);
|
|
301
|
+
if (result.status !== 0)
|
|
302
|
+
return undefined;
|
|
303
|
+
sources.push({ path: path.join(root, ...sourcePath.split("/")), text: result.stdout });
|
|
304
|
+
}
|
|
305
|
+
return sources;
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Find the durable landing receipt on the target's first-parent history.
|
|
309
|
+
* Equivalent trees and lookalike messages are insufficient: the commit must
|
|
310
|
+
* have our parent shape, settle every bind-base-owned task, and exactly match
|
|
311
|
+
* a recomposed landing tree over its own first parent.
|
|
312
|
+
*/
|
|
313
|
+
export async function findAcceptedLandingCommit(input) {
|
|
314
|
+
const commits = (await git(input.cwd, ["rev-list", "--first-parent", input.target, `^${input.base}`]))
|
|
315
|
+
.trim()
|
|
316
|
+
.split("\n")
|
|
317
|
+
.filter(Boolean);
|
|
318
|
+
for (const commit of commits) {
|
|
319
|
+
const message = await git(input.cwd, ["show", "-s", "--format=%B", commit]);
|
|
320
|
+
if (message.trimEnd() !== landingCommitMessage(input.contractId, input.targetBranch).trimEnd())
|
|
321
|
+
continue;
|
|
322
|
+
const record = (await git(input.cwd, ["rev-list", "--parents", "-n", "1", commit])).trim().split(/\s+/);
|
|
323
|
+
const parents = record.slice(1);
|
|
324
|
+
const parentShapeAccepted = parents.length === 2
|
|
325
|
+
? parents[1] === input.delivery
|
|
326
|
+
: parents.length === 1 && await isAncestor(input.cwd, input.delivery, parents[0]);
|
|
327
|
+
if (!parentShapeAccepted)
|
|
328
|
+
continue;
|
|
329
|
+
const sources = await taskSourcesAtCommit(input.cwd, input.root, commit, input.ownedPaths);
|
|
330
|
+
if (!sources)
|
|
331
|
+
continue;
|
|
332
|
+
let settled = false;
|
|
333
|
+
try {
|
|
334
|
+
const sourceByPath = new Map(sources.map((source) => [boardPath(input.root, source), source]));
|
|
335
|
+
settled = input.settledPaths.every((settledPath) => {
|
|
336
|
+
const source = sourceByPath.get(settledPath);
|
|
337
|
+
if (!source)
|
|
338
|
+
return false;
|
|
339
|
+
const document = parseTaskDocument(source);
|
|
340
|
+
return document.state === "done" && document.contractId === undefined;
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
catch {
|
|
344
|
+
// A malformed lookalike is not this operation's durable receipt.
|
|
345
|
+
}
|
|
346
|
+
if (!settled)
|
|
347
|
+
continue;
|
|
348
|
+
const expectedTree = await composeLandingTree({
|
|
349
|
+
cwd: input.cwd,
|
|
350
|
+
gitDir: input.gitDir,
|
|
351
|
+
root: input.root,
|
|
352
|
+
base: input.base,
|
|
353
|
+
target: parents[0],
|
|
354
|
+
delivery: input.delivery,
|
|
355
|
+
sources,
|
|
356
|
+
});
|
|
357
|
+
const actualTree = (await git(input.cwd, ["rev-parse", `${commit}^{tree}`])).trim();
|
|
358
|
+
if (actualTree === expectedTree)
|
|
359
|
+
return commit;
|
|
360
|
+
}
|
|
361
|
+
return undefined;
|
|
362
|
+
}
|
|
363
|
+
export async function containsCommit(cwd, ancestor, descendant) {
|
|
364
|
+
return await isAncestor(cwd, ancestor, descendant);
|
|
365
|
+
}
|
|
366
|
+
/** Create the landing commit over a previously composed candidate tree. */
|
|
266
367
|
export async function createLandingCommit(input) {
|
|
267
|
-
|
|
368
|
+
// Once delivery is already reachable from target, a single-parent landing
|
|
369
|
+
// records this claim without fabricating a redundant merge parent.
|
|
370
|
+
const parents = input.target === input.delivery || await isAncestor(input.cwd, input.delivery, input.target)
|
|
371
|
+
? ["-p", input.target]
|
|
372
|
+
: ["-p", input.target, "-p", input.delivery];
|
|
268
373
|
return (await git(input.cwd, [
|
|
269
374
|
"commit-tree",
|
|
270
|
-
tree,
|
|
271
|
-
|
|
272
|
-
input.target,
|
|
273
|
-
"-p",
|
|
274
|
-
input.delivery,
|
|
375
|
+
input.tree,
|
|
376
|
+
...parents,
|
|
275
377
|
"-m",
|
|
276
378
|
`keiyaku: claim ${input.contractId}`,
|
|
277
379
|
"-m",
|
|
278
380
|
`Merge contract ${input.contractId} into ${input.targetBranch}.`,
|
|
279
381
|
])).trim();
|
|
280
382
|
}
|
|
281
|
-
export async function containsCommit(cwd, ancestor, descendant) {
|
|
282
|
-
return (await gitResult(cwd, ["merge-base", "--is-ancestor", ancestor, descendant])).status === 0;
|
|
283
|
-
}
|
|
284
383
|
export async function updateTargetRef(cwd, ref, next, expected) {
|
|
285
384
|
await git(cwd, ["update-ref", ref, next, expected]);
|
|
286
385
|
}
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { FlowError } from "../../flow-error.js";
|
|
2
2
|
import { resolveTaskReadId } from "./board.js";
|
|
3
|
+
import { advancedTaskUpdatedAt } from "./model.js";
|
|
3
4
|
export function requireHealthySettlementBoard(board) {
|
|
4
5
|
if (board.diseases.length > 0) {
|
|
5
6
|
throw new FlowError("INTERNAL_STATE", `authoritative task board is diseased: ${board.diseases.map((disease) => disease.message).join("; ")}`);
|
|
6
7
|
}
|
|
7
8
|
return board;
|
|
8
9
|
}
|
|
9
|
-
function settlementDocument(task, contractId) {
|
|
10
|
+
function settlementDocument(task, contractId, updatedAt) {
|
|
10
11
|
return {
|
|
11
12
|
id: task.id,
|
|
12
13
|
title: task.title,
|
|
@@ -16,17 +17,18 @@ function settlementDocument(task, contractId) {
|
|
|
16
17
|
parent: task.parent,
|
|
17
18
|
from: [...task.from],
|
|
18
19
|
createdAt: task.createdAt,
|
|
20
|
+
updatedAt: advancedTaskUpdatedAt(task, updatedAt),
|
|
19
21
|
creator: task.creator,
|
|
20
22
|
...(task.startedAt === undefined ? {} : { startedAt: task.startedAt }),
|
|
21
23
|
...(contractId === undefined ? {} : { contractId }),
|
|
22
24
|
body: task.body,
|
|
23
25
|
};
|
|
24
26
|
}
|
|
25
|
-
export function contractPointerDocument(task, contractId) {
|
|
26
|
-
return settlementDocument(task, contractId);
|
|
27
|
+
export function contractPointerDocument(task, contractId, updatedAt) {
|
|
28
|
+
return settlementDocument(task, contractId, updatedAt);
|
|
27
29
|
}
|
|
28
|
-
export function reopenedTaskDocument(task) {
|
|
29
|
-
const document = settlementDocument(task, undefined);
|
|
30
|
+
export function reopenedTaskDocument(task, updatedAt) {
|
|
31
|
+
const document = settlementDocument(task, undefined, updatedAt);
|
|
30
32
|
document.state = "open";
|
|
31
33
|
delete document.startedAt;
|
|
32
34
|
return document;
|
|
@@ -96,12 +98,18 @@ export function tasksForContract(board, contractId) {
|
|
|
96
98
|
.filter((task) => task.contractId === contractId)
|
|
97
99
|
.sort((left, right) => left.id.localeCompare(right.id));
|
|
98
100
|
}
|
|
99
|
-
export function doneTaskReplacements(board, contractId) {
|
|
100
|
-
return
|
|
101
|
-
const
|
|
101
|
+
export function doneTaskReplacements(board, contractId, taskIds, updatedAt) {
|
|
102
|
+
return taskIds.flatMap((taskId) => {
|
|
103
|
+
const task = requireSettledTask(board, taskId);
|
|
104
|
+
if (task.state === "done" && task.contractId === undefined)
|
|
105
|
+
return [];
|
|
106
|
+
if (task.contractId !== contractId) {
|
|
107
|
+
throw new FlowError("INTERNAL_STATE", `task ${task.id} no longer points to contract ${contractId} while settling its immutable bind-base ownership`);
|
|
108
|
+
}
|
|
109
|
+
const settled = settlementDocument(task, undefined, updatedAt);
|
|
102
110
|
settled.state = "done";
|
|
103
111
|
delete settled.startedAt;
|
|
104
|
-
return settled;
|
|
112
|
+
return [settled];
|
|
105
113
|
});
|
|
106
114
|
}
|
|
107
115
|
export function requireSettledTask(board, id) {
|
|
@@ -23,6 +23,7 @@ function taskDocument(parsed) {
|
|
|
23
23
|
parent: parsed.parent,
|
|
24
24
|
from: [...parsed.from],
|
|
25
25
|
createdAt: parsed.createdAt,
|
|
26
|
+
...(parsed.updatedAt === undefined ? {} : { updatedAt: parsed.updatedAt }),
|
|
26
27
|
creator: parsed.creator,
|
|
27
28
|
...(parsed.startedAt === undefined ? {} : { startedAt: parsed.startedAt }),
|
|
28
29
|
...(parsed.contractId === undefined ? {} : { contractId: parsed.contractId }),
|
|
@@ -4,7 +4,7 @@ import { withLockHeldTaskBoard } from "./task-git-store.js";
|
|
|
4
4
|
import { prepareTaskDocumentReplacements, taskBoardFromSourceBoard, taskRepositoryFromLockedBoard } from "./task-store-repository.js";
|
|
5
5
|
import { serializeTaskDocument } from "./document.js";
|
|
6
6
|
import { buildTaskSourceBoard } from "./source-board.js";
|
|
7
|
-
import { containsCommit, createLandingCommit, createTaskBoardCommit, publishTaskMutationCommit, inspectCurrentBranch, publishForwardTaskBoardCommit,
|
|
7
|
+
import { composeLandingTree, containsCommit, createLandingCommit, createTaskBoardCommit, findAcceptedLandingCommit, publishTaskMutationCommit, inspectCurrentBranch, publishForwardTaskBoardCommit, reconcileTaskPathsetIndex, refHead, taskContractAtCommit, taskSettlementOwnershipAtCommit, taskSourcesAtCommit, taskWritesFromSources, trackedTaskPaths, updateBranch, updateTargetRef, } from "./settlement-git.js";
|
|
8
8
|
import { contractPointerDocument, doneTaskReplacements, reopenedTaskDocument, requireHealthySettlementBoard, requireSettledTask, selectReadyTasksForBind, tasksForContract, } from "./settlement-policy.js";
|
|
9
9
|
import { stableRepoRoot } from "../worktree-path.js";
|
|
10
10
|
function sourcePath(root, source) {
|
|
@@ -66,7 +66,9 @@ async function clearAttemptedPointers(input) {
|
|
|
66
66
|
return input.repository.mutate((board) => {
|
|
67
67
|
const replacements = input.tasks.flatMap((task) => {
|
|
68
68
|
const current = requireSettledTask(board, task.id);
|
|
69
|
-
return current.contractId === input.contractId
|
|
69
|
+
return current.contractId === input.contractId
|
|
70
|
+
? [contractPointerDocument(current, undefined, input.updatedAt)]
|
|
71
|
+
: [];
|
|
70
72
|
});
|
|
71
73
|
return { replacements, select: () => replacements.map((replacement) => replacement.id) };
|
|
72
74
|
});
|
|
@@ -86,6 +88,7 @@ async function rollForwardClearAttemptedPointer(input) {
|
|
|
86
88
|
repository: input.repository,
|
|
87
89
|
tasks: input.selected,
|
|
88
90
|
contractId: input.contractId,
|
|
91
|
+
updatedAt: input.updatedAt,
|
|
89
92
|
});
|
|
90
93
|
if (clearedIds.length === 0)
|
|
91
94
|
return;
|
|
@@ -107,6 +110,7 @@ async function rollForwardClearAttemptedPointer(input) {
|
|
|
107
110
|
}, input.deps);
|
|
108
111
|
}
|
|
109
112
|
export async function settleTaskBoardForBind(input, deps = {}) {
|
|
113
|
+
const updatedAt = new Date().toISOString();
|
|
110
114
|
await requireGitRepository(input.cwd, "task bind");
|
|
111
115
|
return withLockHeldTaskBoard(input.cwd, async (transaction) => {
|
|
112
116
|
if (transaction.location.mode !== "git") {
|
|
@@ -125,7 +129,7 @@ export async function settleTaskBoardForBind(input, deps = {}) {
|
|
|
125
129
|
const prepared = taskBound ? await input.prepare(selectedTasks) : input;
|
|
126
130
|
const contractId = prepared.contractId;
|
|
127
131
|
if (taskBound && selectedTasks.length > 0) {
|
|
128
|
-
await replaceTaskDocuments(repository, selectedTasks.map((task) => contractPointerDocument(task, contractId)));
|
|
132
|
+
await replaceTaskDocuments(repository, selectedTasks.map((task) => contractPointerDocument(task, contractId, updatedAt)));
|
|
129
133
|
}
|
|
130
134
|
const taskIds = taskBound ? selectedTasks.map((t) => t.id) : undefined;
|
|
131
135
|
const selectedPaths = ownedTaskPaths(await repository.read(), taskIds);
|
|
@@ -176,7 +180,7 @@ export async function settleTaskBoardForBind(input, deps = {}) {
|
|
|
176
180
|
repository, selected: selectedTasks, contractId, hubCwd,
|
|
177
181
|
branchRef: branch.ref, trackedHead: branch.head,
|
|
178
182
|
gitDir: transaction.location.gitDir, root: transaction.location.root,
|
|
179
|
-
selectedPaths, deps,
|
|
183
|
+
selectedPaths, deps, updatedAt,
|
|
180
184
|
});
|
|
181
185
|
}
|
|
182
186
|
else {
|
|
@@ -194,7 +198,7 @@ export async function settleTaskBoardForBind(input, deps = {}) {
|
|
|
194
198
|
repository, selected: selectedTasks, contractId, hubCwd,
|
|
195
199
|
branchRef: branch.ref, trackedHead: branch.head,
|
|
196
200
|
gitDir: transaction.location.gitDir, root: transaction.location.root,
|
|
197
|
-
selectedPaths, deps,
|
|
201
|
+
selectedPaths, deps, updatedAt,
|
|
198
202
|
});
|
|
199
203
|
}
|
|
200
204
|
throw indexError;
|
|
@@ -217,13 +221,14 @@ export async function settleTaskBoardForBind(input, deps = {}) {
|
|
|
217
221
|
repository, selected: selectedTasks, contractId, hubCwd,
|
|
218
222
|
branchRef: branch.ref, trackedHead: branch.head,
|
|
219
223
|
gitDir: transaction.location.gitDir, root: transaction.location.root,
|
|
220
|
-
selectedPaths, deps,
|
|
224
|
+
selectedPaths, deps, updatedAt,
|
|
221
225
|
});
|
|
222
226
|
throw publishError;
|
|
223
227
|
}
|
|
224
228
|
}, { signal: input.signal });
|
|
225
229
|
}
|
|
226
230
|
export async function reconcileTaskForfeit(input) {
|
|
231
|
+
const updatedAt = new Date().toISOString();
|
|
227
232
|
await requireGitRepository(input.cwd, "task forfeit");
|
|
228
233
|
const hubRoot = await stableRepoRoot(input.cwd);
|
|
229
234
|
return withLockHeldTaskBoard(hubRoot, async (transaction) => {
|
|
@@ -238,7 +243,7 @@ export async function reconcileTaskForfeit(input) {
|
|
|
238
243
|
const ledger = await input.publishLedger();
|
|
239
244
|
if (matched.length === 0)
|
|
240
245
|
return { ledger };
|
|
241
|
-
const reopenedDocuments = matched.map(reopenedTaskDocument);
|
|
246
|
+
const reopenedDocuments = matched.map((task) => reopenedTaskDocument(task, updatedAt));
|
|
242
247
|
const reopened = await replaceTaskDocuments(repository, reopenedDocuments);
|
|
243
248
|
const taskLabel = matched.length === 1 ? `task ${matched[0].id}` : `${matched.length} tasks`;
|
|
244
249
|
await publishTaskMutationCommit({
|
|
@@ -255,11 +260,14 @@ export async function reconcileTaskForfeit(input) {
|
|
|
255
260
|
* task trees are fossils: the candidate is the code-only three-way merge plus
|
|
256
261
|
* one complete snapshot of the common hub board held under its repository lock.
|
|
257
262
|
*/
|
|
258
|
-
export async function settleTaskBoardForLanding(input) {
|
|
263
|
+
export async function settleTaskBoardForLanding(input, deps = {}) {
|
|
264
|
+
const updatedAt = new Date().toISOString();
|
|
259
265
|
await requireGitRepository(input.cwd, "task landing");
|
|
260
266
|
const hubRoot = await stableRepoRoot(input.cwd);
|
|
267
|
+
const ownership = await taskSettlementOwnershipAtCommit(input.cwd, input.baseCommit, input.contractId);
|
|
268
|
+
const reconcileIndex = deps.reconcileTaskPathsetIndex ?? reconcileTaskPathsetIndex;
|
|
261
269
|
let lastTargetHead = input.targetHead;
|
|
262
|
-
let
|
|
270
|
+
let lastPostCasError;
|
|
263
271
|
let acceptedMovement;
|
|
264
272
|
for (let attempt = 0; attempt < 4; attempt += 1) {
|
|
265
273
|
const result = await withLockHeldTaskBoard(hubRoot, async (transaction) => {
|
|
@@ -273,77 +281,143 @@ export async function settleTaskBoardForLanding(input) {
|
|
|
273
281
|
// common task lock. A losing CAS exits before the next attempt.
|
|
274
282
|
const targetHead = await refHead(input.cwd, input.targetRef);
|
|
275
283
|
lastTargetHead = targetHead;
|
|
284
|
+
const selectedPaths = ownership.paths;
|
|
285
|
+
const acceptedLanding = acceptedMovement?.mergeCommit === targetHead
|
|
286
|
+
? targetHead
|
|
287
|
+
: await findAcceptedLandingCommit({
|
|
288
|
+
cwd: input.cwd,
|
|
289
|
+
gitDir: transaction.location.gitDir,
|
|
290
|
+
root: transaction.location.root,
|
|
291
|
+
base: input.baseCommit,
|
|
292
|
+
target: targetHead,
|
|
293
|
+
delivery: input.deliveryHead,
|
|
294
|
+
contractId: input.contractId,
|
|
295
|
+
targetBranch: input.targetBranch,
|
|
296
|
+
ownedPaths: selectedPaths,
|
|
297
|
+
settledPaths: ownership.taskIds.map(taskPath),
|
|
298
|
+
});
|
|
299
|
+
const deliveryAlreadyAccepted = ownership.taskIds.length === 0
|
|
300
|
+
&& await containsCommit(input.cwd, input.deliveryHead, targetHead);
|
|
301
|
+
if (acceptedLanding !== undefined || deliveryAlreadyAccepted) {
|
|
302
|
+
const mergeCommit = acceptedLanding ?? targetHead;
|
|
303
|
+
const movement = acceptedMovement?.mergeCommit === targetHead ? acceptedMovement : undefined;
|
|
304
|
+
const authoritativeSources = selectedPaths.length > 0
|
|
305
|
+
? await taskSourcesAtCommit(input.cwd, transaction.location.root, targetHead, selectedPaths)
|
|
306
|
+
: [];
|
|
307
|
+
if (!authoritativeSources) {
|
|
308
|
+
throw new FlowError("INTERNAL_STATE", `accepted landing ${mergeCommit} lost an owned task path`);
|
|
309
|
+
}
|
|
310
|
+
const selection = selectSettlementSources({
|
|
311
|
+
root: transaction.location.root,
|
|
312
|
+
sources: authoritativeSources,
|
|
313
|
+
trackedPaths: await trackedTaskPaths(input.cwd, targetHead),
|
|
314
|
+
ownedPaths: selectedPaths,
|
|
315
|
+
mutationPaths: selectedPaths,
|
|
316
|
+
});
|
|
317
|
+
const currentText = new Map(transaction.sources.map((source) => [sourcePath(transaction.location.root, source), source.text]));
|
|
318
|
+
const writes = authoritativeSources.filter((source) => currentText.get(sourcePath(transaction.location.root, source)) !== source.text);
|
|
319
|
+
if (writes.length > 0)
|
|
320
|
+
await transaction.replace(taskWritesFromSources(writes));
|
|
321
|
+
try {
|
|
322
|
+
await reconcileIndex(transaction.location.root, targetHead, selectedPaths);
|
|
323
|
+
}
|
|
324
|
+
catch (error) {
|
|
325
|
+
lastPostCasError = error;
|
|
326
|
+
return { kind: "retry" };
|
|
327
|
+
}
|
|
328
|
+
try {
|
|
329
|
+
await input.publishLedger?.(mergeCommit);
|
|
330
|
+
}
|
|
331
|
+
catch (error) {
|
|
332
|
+
lastPostCasError = error;
|
|
333
|
+
return { kind: "retry" };
|
|
334
|
+
}
|
|
335
|
+
const reconcileWarnings = movement
|
|
336
|
+
? await input.afterCas?.(mergeCommit, movement.oldHead)
|
|
337
|
+
: undefined;
|
|
338
|
+
return {
|
|
339
|
+
kind: movement ? "accepted" : "idempotent",
|
|
340
|
+
mergeCommit,
|
|
341
|
+
...(movement ? { oldHead: movement.oldHead } : {}),
|
|
342
|
+
...(reconcileWarnings && reconcileWarnings.length > 0 ? { warnings: reconcileWarnings } : {}),
|
|
343
|
+
evidence: selection.evidence,
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
// No durable receipt exists, so this remains a first-publication attempt:
|
|
347
|
+
// validate mutable hub authority before composing or publishing anything.
|
|
276
348
|
const repository = repositoryForSettlement(transaction, transaction.sources);
|
|
277
349
|
const board = requireHealthySettlementBoard(await repository.read());
|
|
278
|
-
const replacements = doneTaskReplacements(board, input.contractId);
|
|
350
|
+
const replacements = doneTaskReplacements(board, input.contractId, ownership.taskIds, updatedAt);
|
|
279
351
|
const prepared = replacements.length > 0
|
|
280
352
|
? prepareTaskDocumentReplacements(transaction.sources, replacements)
|
|
281
353
|
: { sources: transaction.sources, board };
|
|
282
354
|
const finalSources = prepared.sources;
|
|
283
355
|
requireHealthySettlementBoard(prepared.board);
|
|
284
|
-
const replacementIds = replacements.map((r) => r.id);
|
|
285
|
-
const selectedPaths = ownedTaskPaths(prepared.board, replacementIds.length > 0 ? replacementIds : undefined);
|
|
286
356
|
const selection = selectSettlementSources({
|
|
287
357
|
root: transaction.location.root,
|
|
288
358
|
sources: finalSources,
|
|
289
359
|
trackedPaths: await trackedTaskPaths(input.cwd, targetHead),
|
|
290
360
|
ownedPaths: selectedPaths,
|
|
291
|
-
|
|
361
|
+
// Bind-base ownership, unlike incidental hub bytes, authorizes a path
|
|
362
|
+
// even when a concurrent target commit removed it.
|
|
363
|
+
mutationPaths: selectedPaths,
|
|
292
364
|
});
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
if (
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
365
|
+
const selected = new Set(selection.evidence.selectedPaths);
|
|
366
|
+
const missingOwnedPaths = selectedPaths.filter((ownedPath) => !selected.has(ownedPath));
|
|
367
|
+
if (missingOwnedPaths.length > 0) {
|
|
368
|
+
throw new FlowError("INTERNAL_STATE", `hub board lost bind-base-owned task paths: ${missingOwnedPaths.join(", ")}`);
|
|
369
|
+
}
|
|
370
|
+
const candidateTree = await composeLandingTree({
|
|
371
|
+
cwd: input.cwd,
|
|
372
|
+
gitDir: transaction.location.gitDir,
|
|
373
|
+
root: transaction.location.root,
|
|
374
|
+
base: input.baseCommit,
|
|
375
|
+
target: targetHead,
|
|
376
|
+
delivery: input.deliveryHead,
|
|
377
|
+
sources: selection.sources,
|
|
378
|
+
});
|
|
379
|
+
const mergeCommit = await createLandingCommit({
|
|
380
|
+
cwd: input.cwd,
|
|
381
|
+
target: targetHead,
|
|
382
|
+
delivery: input.deliveryHead,
|
|
383
|
+
tree: candidateTree,
|
|
384
|
+
contractId: input.contractId,
|
|
385
|
+
targetBranch: input.targetBranch,
|
|
386
|
+
});
|
|
387
|
+
await input.beforeCas?.(mergeCommit, targetHead);
|
|
388
|
+
let movement;
|
|
389
|
+
try {
|
|
390
|
+
await updateTargetRef(input.cwd, input.targetRef, mergeCommit, targetHead);
|
|
391
|
+
movement = { mergeCommit, oldHead: targetHead };
|
|
392
|
+
acceptedMovement = movement;
|
|
393
|
+
}
|
|
394
|
+
catch {
|
|
395
|
+
return { kind: "retry" };
|
|
321
396
|
}
|
|
322
397
|
const warnings = [];
|
|
323
398
|
if (replacements.length > 0) {
|
|
324
399
|
await transaction.replace(taskWritesFromSources(selection.sources));
|
|
325
400
|
}
|
|
326
401
|
try {
|
|
327
|
-
await
|
|
402
|
+
await reconcileIndex(transaction.location.root, mergeCommit, selectedPaths);
|
|
328
403
|
}
|
|
329
404
|
catch (error) {
|
|
330
|
-
|
|
405
|
+
lastPostCasError = error;
|
|
406
|
+
return { kind: "retry" };
|
|
331
407
|
}
|
|
332
408
|
try {
|
|
333
409
|
await input.publishLedger?.(mergeCommit);
|
|
334
410
|
}
|
|
335
411
|
catch (error) {
|
|
336
|
-
|
|
412
|
+
lastPostCasError = error;
|
|
337
413
|
return { kind: "retry" };
|
|
338
414
|
}
|
|
339
|
-
const reconcileWarnings = movement
|
|
340
|
-
? await input.afterCas?.(mergeCommit, movement.oldHead)
|
|
341
|
-
: undefined;
|
|
415
|
+
const reconcileWarnings = await input.afterCas?.(mergeCommit, movement.oldHead);
|
|
342
416
|
warnings.push(...(reconcileWarnings ?? []));
|
|
343
417
|
return {
|
|
344
|
-
kind:
|
|
418
|
+
kind: "accepted",
|
|
345
419
|
mergeCommit,
|
|
346
|
-
|
|
420
|
+
oldHead: movement.oldHead,
|
|
347
421
|
...(warnings.length > 0 ? { warnings } : {}),
|
|
348
422
|
evidence: selection.evidence,
|
|
349
423
|
};
|
|
@@ -364,7 +438,7 @@ export async function settleTaskBoardForLanding(input) {
|
|
|
364
438
|
...(result.evidence ? { evidence: result.evidence } : {}),
|
|
365
439
|
};
|
|
366
440
|
}
|
|
367
|
-
if (
|
|
368
|
-
throw
|
|
441
|
+
if (lastPostCasError !== undefined)
|
|
442
|
+
throw lastPostCasError;
|
|
369
443
|
throw new FlowError("CONCURRENT_MODIFICATION", `target branch kept moving while claiming ${input.contractId}; last observed ${lastTargetHead}`);
|
|
370
444
|
}
|
|
@@ -11,6 +11,7 @@ export function taskBoardFromSourceBoard(sourceBoard) {
|
|
|
11
11
|
parent: task.parent,
|
|
12
12
|
from: [...task.from],
|
|
13
13
|
createdAt: task.createdAt,
|
|
14
|
+
...(task.updatedAt === undefined ? {} : { updatedAt: task.updatedAt }),
|
|
14
15
|
creator: task.creator,
|
|
15
16
|
...(task.startedAt === undefined ? {} : { startedAt: task.startedAt }),
|
|
16
17
|
...(task.contractId === undefined ? {} : { contractId: task.contractId }),
|
package/build/core/task/task.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
export { deriveTaskDisplay, parseTaskFilter, parseTaskSort, selectTaskList } from "./query.js";
|
|
2
|
+
export { advancedTaskUpdatedAt, taskUpdatedAt } from "./model.js";
|
|
1
3
|
export { parseTaskDocument, serializeTaskDocument, } from "./document.js";
|
|
2
4
|
export { resolveTaskReadId, } from "./board.js";
|
|
3
5
|
export { buildTaskBoard } from "./source-board.js";
|
package/build/flow-error.js
CHANGED
|
@@ -53,9 +53,7 @@ export function resolveFlowErrorHints(code, context, facts) {
|
|
|
53
53
|
return ["keiyaku status — watch for the akuma to land, then renew"];
|
|
54
54
|
}
|
|
55
55
|
if (facts?.kind === "projection_wake_failure") {
|
|
56
|
-
return [
|
|
57
|
-
`keiyaku tell ${facts.projectionId} [--wait DURATION] "retry the saved tell" — retries the same-projection wake; the saved tell remains queued`,
|
|
58
|
-
];
|
|
56
|
+
return [];
|
|
59
57
|
}
|
|
60
58
|
switch (code) {
|
|
61
59
|
case "NOT_GIT_REPO":
|
|
@@ -119,7 +117,7 @@ export function resolveFlowErrorHints(code, context, facts) {
|
|
|
119
117
|
case "INTERNAL_STATE":
|
|
120
118
|
return ["An internal state error occurred. Please verify your repository status."];
|
|
121
119
|
case "SCOPE_VIOLATION":
|
|
122
|
-
return ["Files outside the contract's declared scope were modified.
|
|
120
|
+
return ["Files outside the contract's declared scope were modified. Append ordered scope terms with `--append-scope`, amend the contract, or move the changes back."];
|
|
123
121
|
case "NO_OPEN_ARC":
|
|
124
122
|
return ["Open an arc to receipt delivery commits after the fence, then retry."];
|
|
125
123
|
case "REVIEW_REJECTED":
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
// Generated into build output by scripts/build.mjs
|
|
2
|
-
export const VERSION = "2.9.
|
|
3
|
-
export const GIT_HASH = "
|
|
2
|
+
export const VERSION = "2.9.6";
|
|
3
|
+
export const GIT_HASH = "cb7f9ef";
|
|
@@ -32,8 +32,10 @@ export function parseUnifiedHunks(section) {
|
|
|
32
32
|
hunks.push({
|
|
33
33
|
header: current[0] ?? "",
|
|
34
34
|
lines: current.slice(1),
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
oldStart: parsed.oldStart,
|
|
36
|
+
oldEnd: parsed.oldEnd,
|
|
37
|
+
newStart: parsed.newStart,
|
|
38
|
+
newEnd: parsed.newEnd,
|
|
37
39
|
});
|
|
38
40
|
}
|
|
39
41
|
}
|
|
@@ -49,25 +51,33 @@ export function parseUnifiedHunks(section) {
|
|
|
49
51
|
hunks.push({
|
|
50
52
|
header: current[0] ?? "",
|
|
51
53
|
lines: current.slice(1),
|
|
52
|
-
|
|
53
|
-
|
|
54
|
+
oldStart: parsed.oldStart,
|
|
55
|
+
oldEnd: parsed.oldEnd,
|
|
56
|
+
newStart: parsed.newStart,
|
|
57
|
+
newEnd: parsed.newEnd,
|
|
54
58
|
});
|
|
55
59
|
}
|
|
56
60
|
}
|
|
57
61
|
return { prelude, hunks };
|
|
58
62
|
}
|
|
59
|
-
function parseHunkRange(hunkHeader) {
|
|
60
|
-
const match = hunkHeader.match(/^@@
|
|
63
|
+
export function parseHunkRange(hunkHeader) {
|
|
64
|
+
const match = hunkHeader.match(/^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/);
|
|
61
65
|
if (!match)
|
|
62
66
|
return null;
|
|
63
|
-
const
|
|
64
|
-
const
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
+
const oldStart = Number.parseInt(match[1] ?? "", 10);
|
|
68
|
+
const oldLines = match[2] === undefined ? 1 : Number.parseInt(match[2], 10);
|
|
69
|
+
const newStart = Number.parseInt(match[3] ?? "", 10);
|
|
70
|
+
const newLines = match[4] === undefined ? 1 : Number.parseInt(match[4], 10);
|
|
71
|
+
if (![oldStart, oldLines, newStart, newLines].every(Number.isFinite))
|
|
67
72
|
return null;
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
73
|
+
return {
|
|
74
|
+
oldStart,
|
|
75
|
+
oldLines,
|
|
76
|
+
oldEnd: oldLines <= 0 ? oldStart - 1 : oldStart + oldLines - 1,
|
|
77
|
+
newStart,
|
|
78
|
+
newLines,
|
|
79
|
+
newEnd: newLines <= 0 ? newStart - 1 : newStart + newLines - 1,
|
|
80
|
+
};
|
|
71
81
|
}
|
|
72
82
|
export function isDeletedDiffSection(section) {
|
|
73
83
|
return section.some((line) => line.startsWith("deleted file mode ") || line.startsWith("+++ /dev/null"));
|