@astrosheep/keiyaku 2.9.4 → 2.9.5
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 +21 -1
- package/build/agents/harness/projection.js +147 -75
- package/build/agents/providers/codex-app-server/adapter.js +32 -0
- package/build/agents/providers/codex-app-server/events.js +23 -0
- package/build/agents/providers/pi/events.js +4 -3
- 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/flags.js +18 -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/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.js +650 -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 +172 -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 +42 -13
|
@@ -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.5";
|
|
3
|
+
export const GIT_HASH = "ade1510";
|
|
@@ -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"));
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { createGit, wrapGitError } from "../core.js";
|
|
2
|
+
import { diffPathspecArgs } from "./pathspec.js";
|
|
3
|
+
import { parseHunkRange, parseUnifiedHunks, splitDiffByFile } from "./parsers.js";
|
|
4
|
+
function parseRawDiff(output) {
|
|
5
|
+
const fields = output.split("\0");
|
|
6
|
+
const files = [];
|
|
7
|
+
let index = 0;
|
|
8
|
+
while (index < fields.length) {
|
|
9
|
+
const header = fields[index++];
|
|
10
|
+
if (!header)
|
|
11
|
+
continue;
|
|
12
|
+
const statusToken = header.trim().split(/\s+/).at(-1) ?? "";
|
|
13
|
+
const status = statusToken[0];
|
|
14
|
+
if (status !== "A" && status !== "M" && status !== "D" && status !== "R" && status !== "C" && status !== "T" && status !== "U")
|
|
15
|
+
continue;
|
|
16
|
+
const firstPath = fields[index++] ?? "";
|
|
17
|
+
if (status === "R" || status === "C") {
|
|
18
|
+
const path = fields[index++] ?? "";
|
|
19
|
+
files.push({
|
|
20
|
+
oldPath: firstPath,
|
|
21
|
+
path,
|
|
22
|
+
status,
|
|
23
|
+
similarity: Number.parseInt(statusToken.slice(1), 10) || 0,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
files.push({ path: firstPath, status });
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return files;
|
|
31
|
+
}
|
|
32
|
+
function withoutTerminalPatchDelimiter(patch) {
|
|
33
|
+
return patch.endsWith("\n") ? patch.slice(0, -1) : patch;
|
|
34
|
+
}
|
|
35
|
+
function parseNumstat(output) {
|
|
36
|
+
const fields = output.split("\0");
|
|
37
|
+
const stats = [];
|
|
38
|
+
let index = 0;
|
|
39
|
+
while (index < fields.length) {
|
|
40
|
+
const record = fields[index++];
|
|
41
|
+
if (!record)
|
|
42
|
+
continue;
|
|
43
|
+
const [added = "0", removed = "0", path = ""] = record.split("\t");
|
|
44
|
+
const binary = added === "-" || removed === "-";
|
|
45
|
+
if (path) {
|
|
46
|
+
stats.push({ path, insertions: binary ? 0 : Number.parseInt(added, 10) || 0, deletions: binary ? 0 : Number.parseInt(removed, 10) || 0, binary });
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
const oldPath = fields[index++] ?? "";
|
|
50
|
+
const newPath = fields[index++] ?? "";
|
|
51
|
+
stats.push({ path: newPath, oldPath, insertions: binary ? 0 : Number.parseInt(added, 10) || 0, deletions: binary ? 0 : Number.parseInt(removed, 10) || 0, binary });
|
|
52
|
+
}
|
|
53
|
+
return stats;
|
|
54
|
+
}
|
|
55
|
+
function structuredLines(raw, oldStart, newStart) {
|
|
56
|
+
let oldLine = oldStart;
|
|
57
|
+
let newLine = newStart;
|
|
58
|
+
const lines = [];
|
|
59
|
+
for (const line of raw) {
|
|
60
|
+
if (line.startsWith("\"))
|
|
61
|
+
continue;
|
|
62
|
+
if (line.startsWith("+")) {
|
|
63
|
+
lines.push({ kind: "addition", content: line.slice(1), oldLine: null, newLine: newLine++ });
|
|
64
|
+
}
|
|
65
|
+
else if (line.startsWith("-")) {
|
|
66
|
+
lines.push({ kind: "deletion", content: line.slice(1), oldLine: oldLine++, newLine: null });
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
lines.push({ kind: "context", content: line.startsWith(" ") ? line.slice(1) : line, oldLine: oldLine++, newLine: newLine++ });
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return lines;
|
|
73
|
+
}
|
|
74
|
+
function parsePatchHunks(section) {
|
|
75
|
+
return parseUnifiedHunks([...section]).hunks.flatMap((hunk, index) => {
|
|
76
|
+
const range = parseHunkRange(hunk.header);
|
|
77
|
+
if (!range)
|
|
78
|
+
return [];
|
|
79
|
+
return [{
|
|
80
|
+
index,
|
|
81
|
+
header: hunk.header,
|
|
82
|
+
oldStart: range.oldStart,
|
|
83
|
+
oldLines: range.oldLines,
|
|
84
|
+
newStart: range.newStart,
|
|
85
|
+
newLines: range.newLines,
|
|
86
|
+
lines: structuredLines(hunk.lines, range.oldStart, range.newStart),
|
|
87
|
+
}];
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
export async function readStructuredDiff(cwd, from, to, options = {}) {
|
|
91
|
+
const git = createGit(cwd);
|
|
92
|
+
const range = `${from}..${to}`;
|
|
93
|
+
const pathspec = diffPathspecArgs({ excludePaths: options.excludePaths });
|
|
94
|
+
try {
|
|
95
|
+
const [raw, numstat, patch] = await Promise.all([
|
|
96
|
+
git.raw(["diff", "--raw", "-z", "--find-renames", range, ...pathspec]),
|
|
97
|
+
git.raw(["diff", "--numstat", "-z", "--find-renames", range, ...pathspec]),
|
|
98
|
+
git.raw(["diff", "--no-color", "--no-ext-diff", "--unified=3", "--find-renames", range, ...pathspec]),
|
|
99
|
+
]);
|
|
100
|
+
const rawFiles = parseRawDiff(raw);
|
|
101
|
+
const stats = parseNumstat(numstat);
|
|
102
|
+
const sections = splitDiffByFile(withoutTerminalPatchDelimiter(patch));
|
|
103
|
+
const files = rawFiles.map((file, index) => {
|
|
104
|
+
const stat = stats[index];
|
|
105
|
+
return {
|
|
106
|
+
...file,
|
|
107
|
+
insertions: stat?.insertions ?? 0,
|
|
108
|
+
deletions: stat?.deletions ?? 0,
|
|
109
|
+
binary: stat?.binary ?? false,
|
|
110
|
+
hunks: parsePatchHunks(sections[index] ?? []),
|
|
111
|
+
};
|
|
112
|
+
});
|
|
113
|
+
return {
|
|
114
|
+
from,
|
|
115
|
+
to,
|
|
116
|
+
files,
|
|
117
|
+
stat: {
|
|
118
|
+
filesChanged: files.length,
|
|
119
|
+
insertions: files.reduce((sum, file) => sum + file.insertions, 0),
|
|
120
|
+
deletions: files.reduce((sum, file) => sum + file.deletions, 0),
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
throw wrapGitError(`diff --find-renames ${range}`, error, cwd);
|
|
126
|
+
}
|
|
127
|
+
}
|
package/build/index.js
CHANGED
|
@@ -3,10 +3,10 @@ import { VERSION, GIT_HASH } from "./generated/version.js";
|
|
|
3
3
|
import * as fs from "node:fs/promises";
|
|
4
4
|
import { getConfig, initializeConfig } from "./config/env.js";
|
|
5
5
|
import { ENV_KEYS } from "./config/env-keys.js";
|
|
6
|
-
import {
|
|
6
|
+
import { formatErrorWithCause } from "./telemetry/logger.js";
|
|
7
7
|
import { runProjectionGenerationRuntime } from "./core/projection/index.js";
|
|
8
8
|
import { renderCliHelp, runCliCommand } from "./cli/index.js";
|
|
9
|
-
import { installCliOutputStreamErrorPolicy } from "./cli/output-stream-error-policy.js";
|
|
9
|
+
import { installCliOutputStreamErrorPolicy, writeCliOutput } from "./cli/output-stream-error-policy.js";
|
|
10
10
|
import { assertSubagentCliCommandAllowed } from "./cli/subagent-guard.js";
|
|
11
11
|
installCliOutputStreamErrorPolicy(process.stdout, process.stderr);
|
|
12
12
|
const packageVersion = VERSION;
|
|
@@ -45,16 +45,16 @@ async function main() {
|
|
|
45
45
|
args: cliArgs,
|
|
46
46
|
});
|
|
47
47
|
if (cliArgs.length === 1 && cliArgs[0] === "--version") {
|
|
48
|
-
|
|
48
|
+
await writeCliOutput(process.stdout, `${GIT_HASH ? `${packageVersion} (${GIT_HASH})` : packageVersion}\n`);
|
|
49
49
|
return;
|
|
50
50
|
}
|
|
51
51
|
if (cliArgs.length === 0 || (cliArgs.length === 1 && (cliArgs[0] === "--help" || cliArgs[0] === "-h"))) {
|
|
52
|
-
|
|
52
|
+
await writeCliOutput(process.stdout, `${renderCliHelp(packageVersion)}\n`);
|
|
53
53
|
return;
|
|
54
54
|
}
|
|
55
55
|
process.exitCode = await runCliCommand(cliArgs);
|
|
56
56
|
}
|
|
57
|
-
main().catch((err) => {
|
|
58
|
-
|
|
59
|
-
process.
|
|
57
|
+
await main().catch(async (err) => {
|
|
58
|
+
await writeCliOutput(process.stderr, `${formatErrorWithCause("Fatal error:", err)}\n`);
|
|
59
|
+
process.exitCode = 1;
|
|
60
60
|
});
|
|
@@ -31,6 +31,9 @@ function stringifyUnknown(error) {
|
|
|
31
31
|
return String(error);
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
|
+
export function formatErrorWithCause(message, error) {
|
|
35
|
+
return `${message} ${stringifyUnknown(error)}`;
|
|
36
|
+
}
|
|
34
37
|
export function logInfo(message, options = {}) {
|
|
35
38
|
log("info", message, options);
|
|
36
39
|
}
|
|
@@ -41,6 +44,5 @@ export function logError(message, options = {}) {
|
|
|
41
44
|
log("error", message, options);
|
|
42
45
|
}
|
|
43
46
|
export function logErrorWithCause(message, error, options = {}) {
|
|
44
|
-
|
|
45
|
-
logError(`${message} ${details}`, options);
|
|
47
|
+
logError(formatErrorWithCause(message, error), options);
|
|
46
48
|
}
|