@astrosheep/keiyaku 2.9.12 → 2.9.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -18
- package/build/.tsbuildinfo +1 -1
- package/build/agents/call-terms.js +1 -0
- package/build/agents/providers/claude-agent-sdk/adapter.js +2 -2
- package/build/cli/commands/akuma/akuma/handler.js +2 -1
- package/build/cli/commands/akuma/akuma/meta.js +4 -3
- package/build/cli/commands/akuma/catalog.js +2 -0
- package/build/cli/commands/akuma/list/handler.js +1 -1
- package/build/cli/commands/akuma/list/meta-list.js +12 -0
- package/build/cli/commands/akuma/list/meta-ls.js +2 -2
- package/build/cli/commands/akuma.js +5 -0
- package/build/cli/commands/contract/amend/meta.js +4 -3
- package/build/cli/commands/contract/arc/meta.js +2 -0
- package/build/cli/commands/contract/audit/handler.js +28 -2
- package/build/cli/commands/contract/audit/meta.js +4 -3
- package/build/cli/commands/contract/petition/handler.js +2 -1
- package/build/cli/commands/contract/petition/meta.js +2 -2
- package/build/cli/commands/contract/renew/handler.js +44 -3
- package/build/cli/commands/contract/renew/meta.js +5 -4
- package/build/cli/commands/metadata.js +12 -9
- package/build/cli/commands/projection/call/handler.js +1 -1
- package/build/cli/commands/projection/call/meta.js +1 -1
- package/build/cli/commands/projection/catalog.js +2 -2
- package/build/cli/commands/projection/{revive → fork}/handler.js +10 -5
- package/build/cli/commands/projection/fork/meta.js +12 -0
- package/build/cli/commands/projection/history/handler.js +46 -2
- package/build/cli/commands/projection/history/meta.js +3 -3
- package/build/cli/commands/projection/kill/meta.js +2 -2
- package/build/cli/commands/projection/status/handler.js +29 -13
- package/build/cli/commands/projection/status/meta.js +14 -3
- package/build/cli/commands/projection/tell/handler.js +8 -4
- package/build/cli/commands/projection/tell/meta.js +1 -1
- package/build/cli/commands/projection/wait/handler.js +45 -20
- package/build/cli/commands/projection/wait/meta.js +1 -1
- package/build/cli/commands/shared.js +1 -1
- package/build/cli/commands/system/catalog.js +2 -0
- package/build/cli/commands/task/add/handler.js +28 -12
- package/build/cli/commands/task/add/meta.js +18 -6
- package/build/cli/commands/task/catalog.js +2 -0
- package/build/cli/commands/task/compose/handler.js +2 -2
- package/build/cli/commands/task/compose/meta.js +2 -2
- package/build/cli/commands/task/doctor/handler.js +2 -3
- package/build/cli/commands/task/doctor/meta.js +2 -2
- package/build/cli/commands/task/done/meta.js +2 -2
- package/build/cli/commands/task/drop/handler.js +7 -2
- package/build/cli/commands/task/drop/meta.js +6 -3
- package/build/cli/commands/task/hold/meta.js +2 -2
- package/build/cli/commands/task/log/meta.js +2 -2
- package/build/cli/commands/task/ls/meta.js +2 -2
- package/build/cli/commands/task/note/handler.js +6 -0
- package/build/cli/commands/task/note/meta.js +11 -0
- package/build/cli/commands/task/resume/meta.js +2 -2
- package/build/cli/commands/task/shared.js +29 -15
- package/build/cli/commands/task/show/meta.js +2 -2
- package/build/cli/commands/task/start/meta.js +2 -2
- package/build/cli/commands/task/stop/meta.js +2 -2
- package/build/cli/commands/task/update/meta.js +3 -2
- package/build/cli/commands/verification/handler.js +43 -0
- package/build/cli/commands/verification/meta.js +13 -0
- package/build/cli/completion.js +16 -13
- package/build/cli/flags.js +43 -11
- package/build/cli/help.js +29 -30
- package/build/cli/index.js +6 -5
- package/build/cli/parse-flags.js +39 -0
- package/build/cli/parse-metadata.js +5 -2
- package/build/cli/parse-selectors.js +18 -0
- package/build/cli/parse.js +21 -8
- package/build/cli/render/arc.js +12 -3
- package/build/cli/render/audit.js +2 -2
- package/build/cli/render/call.js +4 -4
- package/build/cli/render/kanshi.js +122 -85
- package/build/cli/render/misc.js +2 -2
- package/build/cli/render/petition.js +16 -1
- package/build/cli/render/projection-history.js +17 -4
- package/build/cli/render/shared.js +5 -5
- package/build/cli/render/status-indicator.js +47 -0
- package/build/cli/render/status.js +106 -76
- package/build/cli/render/success-response.js +20 -5
- package/build/cli/render/task.js +6 -5
- package/build/cli/render/tell.js +10 -4
- package/build/cli/render/tool-command-normalization.js +137 -0
- package/build/cli/render/tool-presentation.js +2 -1
- package/build/cli/render/verification.js +23 -0
- package/build/cli/render/wait.js +101 -33
- package/build/cli/types.js +2 -1
- package/build/config/akuma-loader.js +21 -1
- package/build/config/settings/knobs.js +7 -1
- package/build/config/settings/schema.js +10 -5
- package/build/core/amend.js +102 -17
- package/build/core/arc.js +4 -4
- package/build/core/audit/candidate.js +10 -18
- package/build/core/audit/coordinates.js +12 -17
- package/build/core/audit/evidence.js +6 -4
- package/build/core/audit/facade.js +3 -3
- package/build/core/audit/report.js +1 -1
- package/build/core/bind-reconciliation.js +42 -23
- package/build/core/bind.js +182 -81
- package/build/core/call/call.js +6 -7
- package/build/core/call/context.js +43 -30
- package/build/core/call/execution.js +21 -6
- package/build/core/call/prompt.js +11 -42
- package/build/core/contract-carrier-runtime.js +209 -105
- package/build/core/contract-carrier.js +156 -73
- package/build/core/contract-view.js +42 -5
- package/build/core/contract.js +2 -1
- package/build/core/derived-replay.js +168 -35
- package/build/core/draft.js +9 -5
- package/build/core/forfeit.js +1 -1
- package/build/core/lifecycle-history.js +13 -0
- package/build/core/lifecycle-recovery.js +32 -27
- package/build/core/lifecycle-runner.js +14 -14
- package/build/core/log.js +1 -1
- package/build/core/outcome-base.js +2 -2
- package/build/core/projection/akuma-name.js +43 -0
- package/build/core/projection/generation/database.js +141 -79
- package/build/core/projection/generation/ledger.js +91 -0
- package/build/core/projection/generation/model.js +18 -13
- package/build/core/projection/generation/projection-generation-continuation.js +1 -1
- package/build/core/projection/generation/projection-generation-doorbell.js +88 -0
- package/build/core/projection/generation/projection-generation-execution.js +24 -0
- package/build/core/projection/generation/projection-generation-identity.js +4 -0
- package/build/core/projection/generation/projection-generation-launcher.js +72 -138
- package/build/core/projection/generation/projection-generation-process.js +16 -15
- package/build/core/projection/generation/projection-generation-runner.js +125 -79
- package/build/core/projection/generation/projection-generation-runtime.js +10 -61
- package/build/core/projection/generation/protocol.js +68 -0
- package/build/core/projection/generation/store.js +17 -7
- package/build/core/projection/generation/transitions.js +14 -14
- package/build/core/projection/index.js +5 -5
- package/build/core/projection/projection-core.js +6 -2
- package/build/core/projection/projection-execution-observer.js +1 -1
- package/build/core/projection/projection-history.js +7 -7
- package/build/core/projection/projection-kill.js +15 -6
- package/build/core/projection/projection-life-observer.js +8 -8
- package/build/core/projection/projection-mint.js +6 -8
- package/build/core/projection/projection-status-observation.js +77 -8
- package/build/core/projection/projection-status.js +151 -4
- package/build/core/projection/projection-wait.js +46 -27
- package/build/core/projection/projection-wake.js +27 -89
- package/build/core/projection/tell/launch-store.js +1 -1
- package/build/core/projection/tell/store.js +1 -1
- package/build/core/renew-build.js +204 -74
- package/build/core/renew-plan.js +34 -9
- package/build/core/renew-rewrite.js +56 -15
- package/build/core/renew.js +39 -35
- package/build/core/repository-ledger/accepted-fold-read.js +6 -3
- package/build/core/repository-ledger/codec.js +11 -5
- package/build/core/repository-ledger/current-state-store.js +13 -16
- package/build/core/repository-ledger/fold-repository.js +5 -6
- package/build/core/repository-ledger/identity.js +60 -0
- package/build/core/repository-ledger/inventory.js +3 -3
- package/build/core/repository-ledger/read-model.js +54 -6
- package/build/core/repository-ledger/write-transaction.js +9 -4
- package/build/core/run-control/connection-bound-close.js +41 -0
- package/build/core/run-control/detached-lease-bootstrap.js +208 -0
- package/build/core/run-control/detached-process.js +14 -0
- package/build/core/run-control/index.js +6 -0
- package/build/core/{process-group.js → run-control/process-tree.js} +1 -1
- package/build/core/run-control/runner-lease.js +172 -0
- package/build/core/{sqlite-process-lifetime-lock.js → run-control/sqlite-process-lifetime-lock.js} +4 -24
- package/build/core/seal.js +44 -32
- package/build/core/settlement/claim-delivery.js +22 -9
- package/build/core/settlement/claim.js +8 -6
- package/build/core/settlement/petition-claim-gates.js +9 -8
- package/build/core/settlement/petition-head-guard.js +5 -6
- package/build/core/settlement/petition-preview.js +21 -5
- package/build/core/settlement/petition.js +51 -17
- package/build/core/settlement/settlement.js +20 -15
- package/build/core/settlement/verdict.js +2 -2
- package/build/core/settlement/verification.js +492 -364
- package/build/core/status/board.js +184 -75
- package/build/core/status/drift.js +8 -10
- package/build/core/task/board.js +1 -0
- package/build/core/task/commands.js +25 -0
- package/build/core/task/compose.js +3 -2
- package/build/core/task/document.js +102 -39
- package/build/core/task/index.js +3 -3
- package/build/core/task/settlement-git.js +1 -4
- package/build/core/task/settlement-policy.js +27 -14
- package/build/core/task/source-board.js +1 -0
- package/build/core/task/task-bind-preparation.js +59 -9
- package/build/core/task/task-contract.js +2 -2
- package/build/core/task/task-store-repository.js +1 -0
- package/build/core/task/task.js +2 -2
- package/build/core/transcripts.js +4 -4
- package/build/core/verification-declaration.js +95 -0
- package/build/core/worktree-bootstrap.js +1 -1
- package/build/core/worktree-path.js +7 -11
- package/build/flow-error.js +9 -1
- package/build/generated/version.js +2 -2
- package/build/git/branches.js +41 -70
- package/build/git/commits.js +92 -21
- package/build/git/core.js +9 -161
- package/build/git/diff/preview.js +4 -4
- package/build/git/diff/read.js +4 -4
- package/build/git/diff/structured.js +5 -5
- package/build/git/process.js +229 -0
- package/build/git/refs.js +30 -5
- package/build/git/staging.js +39 -115
- package/build/git/streaming-batch.js +9 -16
- package/build/git/worktree.js +104 -91
- package/build/index.js +7 -0
- package/package.json +1 -1
- package/skills/keiyaku-akuma/SKILL.md +21 -20
- package/skills/keiyaku-task/SKILL.md +38 -4
- package/skills/keiyaku-workflow/SKILL.md +51 -11
- package/build/cli/commands/projection/revive/meta.js +0 -12
- package/build/core/projection/projection-life-protocol.js +0 -115
- package/build/core/projection/projection-runner-lock.js +0 -211
- package/build/core/projection/tell/database.js +0 -127
- package/build/core/settlement/verification-coordination.js +0 -305
- package/build/core/settlement/verification-supervisor.js +0 -275
|
@@ -10,6 +10,7 @@ const FIELD_ORDER = [
|
|
|
10
10
|
"from",
|
|
11
11
|
"supersedes",
|
|
12
12
|
"relatesTo",
|
|
13
|
+
"notes",
|
|
13
14
|
"createdAt",
|
|
14
15
|
"updatedAt",
|
|
15
16
|
"creator",
|
|
@@ -17,6 +18,9 @@ const FIELD_ORDER = [
|
|
|
17
18
|
"contractId",
|
|
18
19
|
];
|
|
19
20
|
const FIELD_SET = new Set(FIELD_ORDER);
|
|
21
|
+
const CREATION_INPUT_FIELDS = new Set([
|
|
22
|
+
"title", "pri", "needs", "parent", "from", "supersedes", "relatesTo",
|
|
23
|
+
]);
|
|
20
24
|
function validateNode(node) {
|
|
21
25
|
if (node == null)
|
|
22
26
|
return;
|
|
@@ -60,7 +64,7 @@ function frontMatter(source) {
|
|
|
60
64
|
body: lines.slice(closing + 1).join("\n"),
|
|
61
65
|
};
|
|
62
66
|
}
|
|
63
|
-
|
|
67
|
+
function parseTaskSource(source) {
|
|
64
68
|
const split = frontMatter(source);
|
|
65
69
|
const parsed = parseDocument(split.yaml, {
|
|
66
70
|
merge: false,
|
|
@@ -83,6 +87,10 @@ export function parseTaskDocument(source) {
|
|
|
83
87
|
if (!FIELD_SET.has(key))
|
|
84
88
|
invalid(`unknown task front matter key '${key}'`);
|
|
85
89
|
const value = item.value;
|
|
90
|
+
if (key === "notes") {
|
|
91
|
+
values.set(key, noteArray(value));
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
86
94
|
if (isSeq(value)) {
|
|
87
95
|
const items = value.items.map((entry) => {
|
|
88
96
|
if (!isScalar(entry) || typeof entry.value !== "string") {
|
|
@@ -102,6 +110,68 @@ export function parseTaskDocument(source) {
|
|
|
102
110
|
invalid(`task field '${key}' must be a scalar or string array`);
|
|
103
111
|
}
|
|
104
112
|
}
|
|
113
|
+
return { values, body: split.body };
|
|
114
|
+
}
|
|
115
|
+
function noteArray(value) {
|
|
116
|
+
if (!isSeq(value))
|
|
117
|
+
invalid("task field 'notes' must be an array of note objects");
|
|
118
|
+
return value.items.map((item) => {
|
|
119
|
+
if (!isMap(item))
|
|
120
|
+
invalid("task field 'notes' must contain only note objects");
|
|
121
|
+
const values = new Map();
|
|
122
|
+
for (const pair of item.items) {
|
|
123
|
+
if (!isScalar(pair.key) || typeof pair.key.value !== "string") {
|
|
124
|
+
invalid("task note fields must be strings");
|
|
125
|
+
}
|
|
126
|
+
const key = pair.key.value;
|
|
127
|
+
if (key !== "actor" && key !== "timestamp" && key !== "text") {
|
|
128
|
+
invalid(`unknown task note field '${key}'`);
|
|
129
|
+
}
|
|
130
|
+
if (!isScalar(pair.value))
|
|
131
|
+
invalid(`task note field '${key}' must be a string`);
|
|
132
|
+
values.set(key, pair.value.value);
|
|
133
|
+
}
|
|
134
|
+
return {
|
|
135
|
+
actor: nonblank("task note actor", values.get("actor")),
|
|
136
|
+
timestamp: canonicalTimestamp("task note timestamp", values.get("timestamp")),
|
|
137
|
+
text: nonblank("task note text", values.get("text")),
|
|
138
|
+
};
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
function taskNotes(values) {
|
|
142
|
+
const value = values.get("notes");
|
|
143
|
+
return value === undefined ? [] : value;
|
|
144
|
+
}
|
|
145
|
+
function stringArray(values, field, required) {
|
|
146
|
+
const value = values.get(field);
|
|
147
|
+
if (value === undefined && !required)
|
|
148
|
+
return [];
|
|
149
|
+
if (!Array.isArray(value))
|
|
150
|
+
invalid(`task field '${field}' must be a string array`);
|
|
151
|
+
const related = value.map((item) => fullId(field, item));
|
|
152
|
+
if (new Set(related).size !== related.length)
|
|
153
|
+
invalid(`task field '${field}' must be unique`);
|
|
154
|
+
return related;
|
|
155
|
+
}
|
|
156
|
+
function priority(values, required) {
|
|
157
|
+
const value = values.get("pri");
|
|
158
|
+
if (value === undefined && !required)
|
|
159
|
+
return undefined;
|
|
160
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < 0 || value > 3) {
|
|
161
|
+
invalid("task field 'pri' must be an integer from 0 through 3");
|
|
162
|
+
}
|
|
163
|
+
return value;
|
|
164
|
+
}
|
|
165
|
+
function parent(values, required) {
|
|
166
|
+
const value = values.get("parent");
|
|
167
|
+
if (value === undefined && !required)
|
|
168
|
+
return null;
|
|
169
|
+
if (value === null)
|
|
170
|
+
return null;
|
|
171
|
+
return fullId("parent", value);
|
|
172
|
+
}
|
|
173
|
+
export function parseTaskDocument(source) {
|
|
174
|
+
const { values, body } = parseTaskSource(source);
|
|
105
175
|
const id = nonblank("id", values.get("id"));
|
|
106
176
|
if (!isTaskId(id))
|
|
107
177
|
invalid("task field 'id' must be a canonical local task id");
|
|
@@ -110,42 +180,13 @@ export function parseTaskDocument(source) {
|
|
|
110
180
|
if (typeof state !== "string" || !TASK_STATES.has(state)) {
|
|
111
181
|
invalid("task field 'state' must be open, in_progress, on_hold, done, or drop");
|
|
112
182
|
}
|
|
113
|
-
const pri = values
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
const needs = needsValue.map((need) => fullId("needs", need));
|
|
121
|
-
if (new Set(needs).size !== needs.length)
|
|
122
|
-
invalid("task field 'needs' must be unique");
|
|
123
|
-
const relationship = (field) => {
|
|
124
|
-
const value = values.get(field);
|
|
125
|
-
if (value === null)
|
|
126
|
-
return null;
|
|
127
|
-
return fullId(field, value);
|
|
128
|
-
};
|
|
129
|
-
const parent = relationship("parent");
|
|
130
|
-
const fromValue = values.get("from");
|
|
131
|
-
if (!Array.isArray(fromValue))
|
|
132
|
-
invalid("task field 'from' must be a string array");
|
|
133
|
-
const from = fromValue.map((related) => fullId("from", related));
|
|
134
|
-
if (new Set(from).size !== from.length)
|
|
135
|
-
invalid("task field 'from' must be unique");
|
|
136
|
-
const relationships = (field) => {
|
|
137
|
-
const value = values.get(field);
|
|
138
|
-
if (value === undefined)
|
|
139
|
-
return [];
|
|
140
|
-
if (!Array.isArray(value))
|
|
141
|
-
invalid(`task field '${field}' must be a string array`);
|
|
142
|
-
const related = value.map((item) => fullId(field, item));
|
|
143
|
-
if (new Set(related).size !== related.length)
|
|
144
|
-
invalid(`task field '${field}' must be unique`);
|
|
145
|
-
return related;
|
|
146
|
-
};
|
|
147
|
-
const supersedes = relationships("supersedes");
|
|
148
|
-
const relatesTo = relationships("relatesTo");
|
|
183
|
+
const pri = priority(values, true);
|
|
184
|
+
const needs = stringArray(values, "needs", true);
|
|
185
|
+
const taskParent = parent(values, true);
|
|
186
|
+
const from = stringArray(values, "from", true);
|
|
187
|
+
const supersedes = stringArray(values, "supersedes", false);
|
|
188
|
+
const relatesTo = stringArray(values, "relatesTo", false);
|
|
189
|
+
const notes = taskNotes(values);
|
|
149
190
|
const createdAt = canonicalTimestamp("createdAt", values.get("createdAt"));
|
|
150
191
|
const updatedValue = values.get("updatedAt");
|
|
151
192
|
const updatedAt = updatedValue === undefined ? undefined : canonicalTimestamp("updatedAt", updatedValue);
|
|
@@ -166,19 +207,40 @@ export function parseTaskDocument(source) {
|
|
|
166
207
|
state: state,
|
|
167
208
|
pri,
|
|
168
209
|
needs,
|
|
169
|
-
parent,
|
|
210
|
+
parent: taskParent,
|
|
170
211
|
from,
|
|
171
212
|
supersedes,
|
|
172
213
|
relatesTo,
|
|
214
|
+
notes,
|
|
173
215
|
createdAt,
|
|
174
216
|
...(updatedAt === undefined ? {} : { updatedAt }),
|
|
175
217
|
creator,
|
|
176
218
|
...(startedAt === undefined ? {} : { startedAt }),
|
|
177
219
|
...(contractId === undefined ? {} : { contractId }),
|
|
178
|
-
body
|
|
220
|
+
body,
|
|
179
221
|
source,
|
|
180
222
|
};
|
|
181
223
|
}
|
|
224
|
+
/** Parse the caller-owned subset of the canonical task document grammar. */
|
|
225
|
+
export function parseTaskCreationDocument(source) {
|
|
226
|
+
const { values, body } = parseTaskSource(source);
|
|
227
|
+
for (const field of values.keys()) {
|
|
228
|
+
if (!CREATION_INPUT_FIELDS.has(field)) {
|
|
229
|
+
invalid(`task creation document field '${field}' is creation-owned and cannot be supplied`);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
const pri = priority(values, false);
|
|
233
|
+
return {
|
|
234
|
+
title: nonblank("title", values.get("title")),
|
|
235
|
+
body,
|
|
236
|
+
...(pri === undefined ? {} : { pri }),
|
|
237
|
+
needs: stringArray(values, "needs", false),
|
|
238
|
+
parent: parent(values, false),
|
|
239
|
+
from: stringArray(values, "from", false),
|
|
240
|
+
supersedes: stringArray(values, "supersedes", false),
|
|
241
|
+
relatesTo: stringArray(values, "relatesTo", false),
|
|
242
|
+
};
|
|
243
|
+
}
|
|
182
244
|
function validateDocument(document) {
|
|
183
245
|
return parseTaskDocument({ path: `${document.id}.md`, text: serializeUnchecked(document) });
|
|
184
246
|
}
|
|
@@ -193,6 +255,7 @@ function serializeUnchecked(document) {
|
|
|
193
255
|
from: document.from,
|
|
194
256
|
...(document.supersedes.length === 0 ? {} : { supersedes: document.supersedes }),
|
|
195
257
|
...(document.relatesTo.length === 0 ? {} : { relatesTo: document.relatesTo }),
|
|
258
|
+
...(document.notes.length === 0 ? {} : { notes: document.notes.map((note) => ({ ...note })) }),
|
|
196
259
|
createdAt: document.createdAt,
|
|
197
260
|
...(document.updatedAt === undefined ? {} : { updatedAt: document.updatedAt }),
|
|
198
261
|
creator: document.creator,
|
package/build/core/task/index.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Package-internal modules must import each other directly, not through this file.
|
|
5
5
|
*/
|
|
6
6
|
export { previewTaskBoardForBind, reconcileTaskForfeit, replayClaimTasks, } from "./task-contract.js";
|
|
7
|
-
export { replayPreparedTaskBind, replayPreparedTaskBindLocked, replayCommittedTaskBind, replayCommittedTaskBindForfeit, replayPreparedTaskBindForfeit, withPreparedTaskBind, } from "./task-bind-preparation.js";
|
|
7
|
+
export { replayPreparedTaskBind, replayPreparedTaskBindLocked, replayCommittedTaskBind, replayCommittedTaskBindForfeit, replayPreparedTaskBindForfeit, withPreparedTaskBind, withPreparedTaskBindSnapshot, } from "./task-bind-preparation.js";
|
|
8
8
|
export { readTaskBoardGeneration } from "./task-git-store.js";
|
|
9
9
|
export { resolveTaskCreator } from "./task-git-actor.js";
|
|
10
10
|
export { openTaskCommandRepository, openTaskMutationContext, readTaskDoctor, taskCommandContractId, taskCommandNamespace, } from "./task-git-runtime.js";
|
|
@@ -12,5 +12,5 @@ export { openTaskStore, readTaskHistory, readTaskNamespaces } from "./task-git-s
|
|
|
12
12
|
export { taskRepositoryFromStore } from "./task-store-repository.js";
|
|
13
13
|
export { composeTasks, planTaskComposition, parseTaskCompositionSketch, } from "./compose.js";
|
|
14
14
|
export { isTaskId } from "./validation.js";
|
|
15
|
-
export { isTaskCoordinate, parseTaskCoordinate, taskCoordinateFromSourcePath } from "./coordinate.js";
|
|
16
|
-
export { buildTaskTree, createTask, doneTask, dropTask, parseTaskDocument, readTaskBoard, resolveTaskReadId, serializeTaskDocument, startTask, stopTask, holdTask, resumeTask, updateTask, updateTaskWithReceipt, deriveTaskDisplay, selectTaskList, taskUpdatedAt, advancedTaskUpdatedAt, } from "./task.js";
|
|
15
|
+
export { isTaskCoordinate, parseTaskCoordinate, taskCoordinateFromSourcePath, taskNamespaceFromScope } from "./coordinate.js";
|
|
16
|
+
export { buildTaskTree, createTask, doneTask, dropTask, dropTaskWithNote, appendTaskNote, parseTaskCreationDocument, parseTaskDocument, readTaskBoard, resolveTaskReadId, serializeTaskDocument, startTask, stopTask, holdTask, resumeTask, updateTask, updateTaskWithReceipt, deriveTaskDisplay, selectTaskList, taskUpdatedAt, advancedTaskUpdatedAt, } from "./task.js";
|
|
@@ -278,10 +278,7 @@ async function readTaskAdmissionEvidence(input, readDocument = taskDocumentAtCom
|
|
|
278
278
|
const needs = [];
|
|
279
279
|
for (const need of document.needs) {
|
|
280
280
|
const prerequisite = await read(need);
|
|
281
|
-
|
|
282
|
-
throw new FlowError("INTERNAL_STATE", `bind base ${input.base} task ${taskId} has unsatisfied need ${need}`);
|
|
283
|
-
}
|
|
284
|
-
needs.push({ taskId: need, state: prerequisite.state });
|
|
281
|
+
needs.push({ taskId: need, state: prerequisite.state, ...(prerequisite.contractId === undefined ? {} : { contractId: prerequisite.contractId }) });
|
|
285
282
|
}
|
|
286
283
|
selected.push({ taskId, needs });
|
|
287
284
|
}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { FlowError } from "../../flow-error.js";
|
|
2
2
|
import { resolveTaskReadId } from "./board.js";
|
|
3
|
+
import { openTaskStore } from "./task-git-store.js";
|
|
4
|
+
import { taskRepositoryFromStore } from "./task-store-repository.js";
|
|
5
|
+
import { readTaskBoard } from "./commands.js";
|
|
3
6
|
import { advancedTaskUpdatedAt } from "./model.js";
|
|
4
7
|
export function requireHealthySettlementBoard(board) {
|
|
5
8
|
if (board.diseases.length > 0) {
|
|
@@ -20,6 +23,7 @@ function settlementDocument(task, contractId, updatedAt) {
|
|
|
20
23
|
from: [...task.from],
|
|
21
24
|
supersedes: [...task.supersedes],
|
|
22
25
|
relatesTo: [...task.relatesTo],
|
|
26
|
+
notes: task.notes.map((note) => ({ ...note })),
|
|
23
27
|
createdAt: task.createdAt,
|
|
24
28
|
updatedAt: advancedTaskUpdatedAt(task, updatedAt),
|
|
25
29
|
creator: task.creator,
|
|
@@ -31,6 +35,12 @@ function settlementDocument(task, contractId, updatedAt) {
|
|
|
31
35
|
export function contractPointerDocument(task, contractId, updatedAt) {
|
|
32
36
|
return settlementDocument(task, contractId, updatedAt);
|
|
33
37
|
}
|
|
38
|
+
export function boundTaskDocument(task, contractId, updatedAt) {
|
|
39
|
+
const document = settlementDocument(task, contractId, updatedAt);
|
|
40
|
+
document.state = "open";
|
|
41
|
+
delete document.startedAt;
|
|
42
|
+
return document;
|
|
43
|
+
}
|
|
34
44
|
export function reopenedTaskDocument(task, updatedAt) {
|
|
35
45
|
const document = settlementDocument(task, undefined, updatedAt);
|
|
36
46
|
document.state = "open";
|
|
@@ -38,25 +48,28 @@ export function reopenedTaskDocument(task, updatedAt) {
|
|
|
38
48
|
return document;
|
|
39
49
|
}
|
|
40
50
|
function validateReadyForBind(board, task) {
|
|
41
|
-
if (task.state
|
|
42
|
-
throw new FlowError("INVALID_TARGET", `task ${task.coordinate} is
|
|
43
|
-
}
|
|
44
|
-
if (task.state !== "open") {
|
|
45
|
-
throw new FlowError("INVALID_TARGET", `task ${task.coordinate} is ${task.state}, not ready for bind`);
|
|
46
|
-
}
|
|
47
|
-
const unsatisfied = task.needs
|
|
48
|
-
.filter((need) => {
|
|
49
|
-
const dependency = board.byId.get(need);
|
|
50
|
-
return dependency.state === "open" || dependency.state === "in_progress" || dependency.state === "on_hold";
|
|
51
|
-
})
|
|
52
|
-
.sort();
|
|
53
|
-
if (unsatisfied.length > 0) {
|
|
54
|
-
throw new FlowError("INVALID_TARGET", `task ${task.coordinate} is blocked by: ${unsatisfied.join(", ")}`);
|
|
51
|
+
if (task.state !== "open" && task.state !== "in_progress") {
|
|
52
|
+
throw new FlowError("INVALID_TARGET", `task ${task.coordinate} is ${task.state}, not open or in_progress for bind`);
|
|
55
53
|
}
|
|
56
54
|
if (task.contractId !== undefined) {
|
|
57
55
|
throw new FlowError("INVALID_TARGET", `task ${task.coordinate} is already bound to contract ${task.contractId}`);
|
|
58
56
|
}
|
|
59
57
|
}
|
|
58
|
+
/** Read the sole live task prerequisite gate for a task-selected contract. */
|
|
59
|
+
export async function readRemainingTaskNeedsForSettlement(cwd, evidence) {
|
|
60
|
+
const needIds = [...new Set(evidence?.selected.flatMap((task) => task.needs
|
|
61
|
+
.filter((need) => need.contractId === undefined)
|
|
62
|
+
.map((need) => need.taskId)) ?? [])].sort();
|
|
63
|
+
if (needIds.length === 0)
|
|
64
|
+
return [];
|
|
65
|
+
const board = requireHealthySettlementBoard(await readTaskBoard(taskRepositoryFromStore(await openTaskStore(cwd))));
|
|
66
|
+
return needIds.map((id) => {
|
|
67
|
+
const task = board.byId.get(id);
|
|
68
|
+
if (!task)
|
|
69
|
+
throw new FlowError("INTERNAL_STATE", `task prerequisite ${id} disappeared from the authoritative board`);
|
|
70
|
+
return { id, satisfied: task.state === "done" || task.state === "drop", state: task.state };
|
|
71
|
+
});
|
|
72
|
+
}
|
|
60
73
|
export function selectReadyTasksForBind(board, taskIds) {
|
|
61
74
|
requireHealthySettlementBoard(board);
|
|
62
75
|
const rootIds = taskIds.map((taskId) => resolveTaskReadId(board, taskId));
|
|
@@ -24,6 +24,7 @@ function taskDocument(parsed) {
|
|
|
24
24
|
from: [...parsed.from],
|
|
25
25
|
supersedes: [...parsed.supersedes],
|
|
26
26
|
relatesTo: [...parsed.relatesTo],
|
|
27
|
+
notes: parsed.notes.map((note) => ({ ...note })),
|
|
27
28
|
createdAt: parsed.createdAt,
|
|
28
29
|
...(parsed.updatedAt === undefined ? {} : { updatedAt: parsed.updatedAt }),
|
|
29
30
|
creator: parsed.creator,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { FlowError } from "../../flow-error.js";
|
|
2
|
+
import { readCommitCoordinate } from "../../git/commits.js";
|
|
2
3
|
import { runGitProcess } from "../../git/core.js";
|
|
3
4
|
import { assertGitObservation, requireGitRepository } from "../../git/branches.js";
|
|
4
5
|
import { inspectCurrentBranch, createTaskBoardCommit, publishForwardTaskBoardCommit, reconcileTaskPathsetIndex, refHead, taskSourcesAtCommit, taskWritesFromSources, } from "./settlement-git.js";
|
|
@@ -21,6 +22,7 @@ function linkedTaskDocument(task, contractId, at) {
|
|
|
21
22
|
from: [...task.from],
|
|
22
23
|
supersedes: [...task.supersedes],
|
|
23
24
|
relatesTo: [...task.relatesTo],
|
|
25
|
+
notes: task.notes.map((note) => ({ ...note })),
|
|
24
26
|
createdAt: task.createdAt,
|
|
25
27
|
updatedAt: advancedTaskUpdatedAt(task, at),
|
|
26
28
|
creator: task.creator,
|
|
@@ -32,11 +34,10 @@ function selectedEvidence(board, selected) {
|
|
|
32
34
|
return selected.map((task) => ({
|
|
33
35
|
taskId: task.coordinate,
|
|
34
36
|
needs: task.needs.map((taskId) => {
|
|
35
|
-
const
|
|
36
|
-
if (
|
|
37
|
-
throw new FlowError("INTERNAL_STATE", `
|
|
38
|
-
}
|
|
39
|
-
return { taskId, state };
|
|
37
|
+
const need = board.byId.get(taskId);
|
|
38
|
+
if (!need)
|
|
39
|
+
throw new FlowError("INTERNAL_STATE", `selected task ${task.coordinate} has missing need ${taskId}`);
|
|
40
|
+
return { taskId, state: need.state, ...(need.contractId === undefined ? {} : { contractId: need.contractId }) };
|
|
40
41
|
}),
|
|
41
42
|
}));
|
|
42
43
|
}
|
|
@@ -100,6 +101,52 @@ export async function withPreparedTaskBind(input, deps = {}) {
|
|
|
100
101
|
return { preparation, result };
|
|
101
102
|
}, { signal: input.signal });
|
|
102
103
|
}
|
|
104
|
+
/** Task-lock admission from bind's already-observed repository and board coordinates. */
|
|
105
|
+
export async function withPreparedTaskBindSnapshot(input, deps = {}) {
|
|
106
|
+
return await withLockHeldTaskBoard(input.location.root, async (transaction) => {
|
|
107
|
+
const prepared = await prepareLockedTaskBindFromSnapshot(transaction, input, deps);
|
|
108
|
+
return { preparation: prepared, result: await input.transact(prepared, transaction) };
|
|
109
|
+
}, { signal: input.signal }, input.location);
|
|
110
|
+
}
|
|
111
|
+
async function prepareLockedTaskBindFromSnapshot(transaction, input, deps) {
|
|
112
|
+
if (transaction.location.mode !== "git")
|
|
113
|
+
throw new FlowError("NOT_GIT_REPO", "task bind requires Git task storage");
|
|
114
|
+
const observedBoard = await input.observeBoard();
|
|
115
|
+
const board = requireHealthySettlementBoard(taskBoardFromSourceBoard(buildTaskSourceBoard(transaction.sources)));
|
|
116
|
+
const selected = selectReadyTasksForBind(board, input.taskIds);
|
|
117
|
+
if (selected.length === 0)
|
|
118
|
+
throw new FlowError("INVALID_TARGET", "task bind requires at least one selected task");
|
|
119
|
+
const attempt = await input.prepare(selected);
|
|
120
|
+
const evidence = selectedEvidence(board, selected);
|
|
121
|
+
const linked = prepareTaskDocumentReplacements(transaction.sources, selected.map((task) => linkedTaskDocument(task, attempt.contractId, attempt.at)));
|
|
122
|
+
const linkedIds = new Set(selected.map((task) => task.coordinate));
|
|
123
|
+
const linkedSources = linked.sources.filter((source) => linkedIds.has(taskCoordinateFromSourcePath(source.path).coordinate));
|
|
124
|
+
const originalSources = transaction.sources.filter((source) => linkedIds.has(taskCoordinateFromSourcePath(source.path).coordinate));
|
|
125
|
+
const newHead = await (deps.createTaskBoardCommit ?? createTaskBoardCommit)({
|
|
126
|
+
cwd: transaction.location.root,
|
|
127
|
+
gitDir: transaction.location.gitDir,
|
|
128
|
+
root: transaction.location.root,
|
|
129
|
+
parent: observedBoard.head,
|
|
130
|
+
sources: linkedSources,
|
|
131
|
+
subject: selected.length === 1
|
|
132
|
+
? `Bind task ${selected[0].coordinate} to contract ${attempt.contractId}`
|
|
133
|
+
: `Bind ${selected.length} tasks to contract ${attempt.contractId}`,
|
|
134
|
+
});
|
|
135
|
+
return {
|
|
136
|
+
boardRef: observedBoard.ref,
|
|
137
|
+
expectedHead: observedBoard.head,
|
|
138
|
+
newHead,
|
|
139
|
+
entryBase: newHead,
|
|
140
|
+
taskIds: selected.map((task) => task.coordinate),
|
|
141
|
+
selected: evidence,
|
|
142
|
+
taskBoard: { ref: observedBoard.ref, expectedHead: observedBoard.head, head: newHead },
|
|
143
|
+
replay: {
|
|
144
|
+
paths: selected.map((task) => `.keiyaku/tasks/${task.coordinate}.md`).sort(),
|
|
145
|
+
linkedSources,
|
|
146
|
+
originalSources,
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
}
|
|
103
150
|
/** Complete the prepared task projection while its authority transaction still owns the task lock. */
|
|
104
151
|
export async function replayPreparedTaskBindLocked(transaction, prepared) {
|
|
105
152
|
if (transaction.location.mode !== "git")
|
|
@@ -149,11 +196,14 @@ export async function replayCommittedTaskBindForfeit(cwd, commit, boardRef, task
|
|
|
149
196
|
return await withLockHeldTaskBoard(cwd, async (transaction) => {
|
|
150
197
|
if (transaction.location.mode !== "git")
|
|
151
198
|
throw new FlowError("NOT_GIT_REPO", "task forfeit replay requires Git task storage");
|
|
152
|
-
const
|
|
153
|
-
if (
|
|
154
|
-
throw new FlowError("INTERNAL_STATE", `committed task bind base ${commit} has no readable parent`, { cause:
|
|
199
|
+
const coordinate = await readCommitCoordinate(transaction.location.root, commit);
|
|
200
|
+
if (coordinate.status !== "ok") {
|
|
201
|
+
throw new FlowError("INTERNAL_STATE", `committed task bind base ${commit} has no readable parent`, { cause: coordinate.status === "failed" ? coordinate.error : undefined });
|
|
202
|
+
}
|
|
203
|
+
const parent = coordinate.coordinate.parents[0];
|
|
204
|
+
if (!parent) {
|
|
205
|
+
throw new FlowError("INTERNAL_STATE", `committed task bind base ${commit} has no readable parent`);
|
|
155
206
|
}
|
|
156
|
-
const parent = parentResult.stdout.trim();
|
|
157
207
|
const paths = taskIds.map((taskId) => `.keiyaku/tasks/${taskId}.md`).sort();
|
|
158
208
|
const originalSources = await taskSourcesAtCommit(transaction.location.root, transaction.location.root, parent, paths);
|
|
159
209
|
if (!originalSources)
|
|
@@ -5,7 +5,7 @@ import { prepareTaskDocumentReplacements, taskBoardFromSourceBoard, taskReposito
|
|
|
5
5
|
import { buildTaskSourceBoard } from "./source-board.js";
|
|
6
6
|
import { formatTaskCoordinate } from "./coordinate.js";
|
|
7
7
|
import { createTaskBoardCommit, publishTaskMutationCommit, inspectCurrentBranch, publishForwardTaskBoardCommit, reconcileTaskPathsetIndex, refHead, taskContractAtCommit, taskSourcesAtCommit, taskWritesFromSources, trackedTaskPaths, updateBranch, } from "./settlement-git.js";
|
|
8
|
-
import { contractPointerDocument, doneTaskReplacements, reopenedTaskDocument, requireHealthySettlementBoard, requireSettledTask, selectReadyTasksForBind, tasksForContract, } from "./settlement-policy.js";
|
|
8
|
+
import { boundTaskDocument, contractPointerDocument, doneTaskReplacements, reopenedTaskDocument, requireHealthySettlementBoard, requireSettledTask, selectReadyTasksForBind, tasksForContract, } from "./settlement-policy.js";
|
|
9
9
|
import { stableRepoRoot } from "../worktree-path.js";
|
|
10
10
|
/**
|
|
11
11
|
* Read the same healthy task board and run the same ready-task selector as bind
|
|
@@ -147,7 +147,7 @@ export async function settleTaskBoardForBind(input, deps = {}) {
|
|
|
147
147
|
const prepared = taskBound ? await input.prepare(selectedTasks) : input;
|
|
148
148
|
const contractId = prepared.contractId;
|
|
149
149
|
if (taskBound && selectedTasks.length > 0) {
|
|
150
|
-
await replaceTaskDocuments(repository, selectedTasks.map((task) =>
|
|
150
|
+
await replaceTaskDocuments(repository, selectedTasks.map((task) => boundTaskDocument(task, contractId, updatedAt)));
|
|
151
151
|
}
|
|
152
152
|
const taskIds = taskBound ? selectedTasks.map((t) => t.coordinate) : undefined;
|
|
153
153
|
const selectedPaths = ownedTaskPaths(await repository.read(), taskIds);
|
|
@@ -15,6 +15,7 @@ export function taskBoardFromSourceBoard(sourceBoard) {
|
|
|
15
15
|
from: [...task.from],
|
|
16
16
|
supersedes: [...task.supersedes],
|
|
17
17
|
relatesTo: [...task.relatesTo],
|
|
18
|
+
notes: task.notes.map((note) => ({ ...note })),
|
|
18
19
|
createdAt: task.createdAt,
|
|
19
20
|
...(task.updatedAt === undefined ? {} : { updatedAt: task.updatedAt }),
|
|
20
21
|
creator: task.creator,
|
package/build/core/task/task.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { deriveTaskDisplay, parseTaskFilter, parseTaskSort, selectTaskList } from "./query.js";
|
|
2
2
|
export { advancedTaskUpdatedAt, taskUpdatedAt } from "./model.js";
|
|
3
|
-
export { parseTaskDocument, serializeTaskDocument, } from "./document.js";
|
|
3
|
+
export { parseTaskCreationDocument, parseTaskDocument, serializeTaskDocument, } from "./document.js";
|
|
4
4
|
export { resolveTaskReadId, } from "./board.js";
|
|
5
5
|
export { buildTaskBoard } from "./source-board.js";
|
|
6
6
|
export { buildTaskTree } from "./tree.js";
|
|
7
|
-
export { readTaskBoard, createTask, updateTask, updateTaskWithReceipt, startTask, stopTask, holdTask, resumeTask, doneTask, dropTask, selectSoleInProgress, rollbackOrphanTaskPointers, } from "./commands.js";
|
|
7
|
+
export { readTaskBoard, createTask, updateTask, updateTaskWithReceipt, startTask, stopTask, holdTask, resumeTask, doneTask, dropTask, dropTaskWithNote, appendTaskNote, selectSoleInProgress, rollbackOrphanTaskPointers, } from "./commands.js";
|
|
@@ -410,7 +410,7 @@ export async function locateResponseArtifact(input) {
|
|
|
410
410
|
return carry(error);
|
|
411
411
|
}
|
|
412
412
|
if (!isResponseArtifactId(identity)) {
|
|
413
|
-
return carry(new FlowError("INVALID_RESPONSE_PATH", `${identity} is not a response artifact handle;
|
|
413
|
+
return carry(new FlowError("INVALID_RESPONSE_PATH", `${identity} is not a response artifact handle; fork expects an artifact id`));
|
|
414
414
|
}
|
|
415
415
|
let physicalLocalRoot;
|
|
416
416
|
try {
|
|
@@ -463,11 +463,11 @@ export async function locateResponseArtifact(input) {
|
|
|
463
463
|
}
|
|
464
464
|
export function requireResumableArtifact(artifact) {
|
|
465
465
|
if (!artifact.session)
|
|
466
|
-
throw artifactError(artifact.artifactId, RESPONSE_HISTORY_SESSION_KEY, "is missing; this conversation cannot be
|
|
466
|
+
throw artifactError(artifact.artifactId, RESPONSE_HISTORY_SESSION_KEY, "is missing; this conversation cannot be forked");
|
|
467
467
|
if (!artifact.subagentName)
|
|
468
|
-
throw artifactError(artifact.artifactId, "subagent_name", "is missing; this conversation cannot be
|
|
468
|
+
throw artifactError(artifact.artifactId, "subagent_name", "is missing; this conversation cannot be forked");
|
|
469
469
|
if (!artifact.launchSnapshot)
|
|
470
|
-
throw artifactError(artifact.artifactId, RESPONSE_HISTORY_LAUNCH_SNAPSHOT_KEY, "is missing; this conversation cannot be
|
|
470
|
+
throw artifactError(artifact.artifactId, RESPONSE_HISTORY_LAUNCH_SNAPSHOT_KEY, "is missing; this conversation cannot be forked");
|
|
471
471
|
return artifact;
|
|
472
472
|
}
|
|
473
473
|
export async function nextReviewResponseNumber(cwd) {
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { FlowError } from "../flow-error.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
export const VERIFICATION_EXECUTORS = ["bash", "zsh", "pwsh"];
|
|
4
|
+
export const verificationDeclarationSchema = z.object({
|
|
5
|
+
executor: z.enum(VERIFICATION_EXECUTORS),
|
|
6
|
+
script: z.string().refine((value) => value.trim().length > 0, "expected a nonblank script"),
|
|
7
|
+
}).strict();
|
|
8
|
+
function example(heading) {
|
|
9
|
+
return `${heading}\n\`\`\`bash\n<verification script>\n\`\`\``;
|
|
10
|
+
}
|
|
11
|
+
function invalid(options, detail) {
|
|
12
|
+
throw new FlowError("INVALID_KEIYAKU_DRAFT", `${options.heading} ${detail}. Use a fenced executor script block:\n${example(options.heading)}`);
|
|
13
|
+
}
|
|
14
|
+
function isExecutor(value) {
|
|
15
|
+
return VERIFICATION_EXECUTORS.includes(value);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Parse the sole persisted contract Verification declaration grammar. Bodies
|
|
19
|
+
* are deliberately source-level so shell backticks and blank lines stay exact.
|
|
20
|
+
*/
|
|
21
|
+
export function parseVerificationDeclarations(source, options) {
|
|
22
|
+
const lines = source.split(/\r?\n/);
|
|
23
|
+
const declarations = [];
|
|
24
|
+
let sawNone = false;
|
|
25
|
+
for (let index = 0; index < lines.length;) {
|
|
26
|
+
const line = lines[index];
|
|
27
|
+
if (line.trim().length === 0) {
|
|
28
|
+
index += 1;
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
if (options.allowNone && line.trim() === "- (none)") {
|
|
32
|
+
if (declarations.length > 0 || sawNone) {
|
|
33
|
+
invalid(options, "the explicit '- (none)' replacement cannot be combined with Verification declarations");
|
|
34
|
+
}
|
|
35
|
+
sawNone = true;
|
|
36
|
+
index += 1;
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
const opening = line.match(/^ {0,3}(`{3,}|~{3,})(.*)$/);
|
|
40
|
+
if (!opening)
|
|
41
|
+
invalid(options, "must contain only direct fenced script blocks");
|
|
42
|
+
const fence = opening[1];
|
|
43
|
+
const delimiter = fence[0];
|
|
44
|
+
const executor = opening[2].trim();
|
|
45
|
+
if (!executor)
|
|
46
|
+
invalid(options, "fenced script blocks require an executor header (bash, zsh, or pwsh)");
|
|
47
|
+
if (!isExecutor(executor))
|
|
48
|
+
invalid(options, `does not support the '${executor}' executor`);
|
|
49
|
+
const body = [];
|
|
50
|
+
index += 1;
|
|
51
|
+
let closed = false;
|
|
52
|
+
while (index < lines.length) {
|
|
53
|
+
const candidate = lines[index];
|
|
54
|
+
const close = candidate.match(/^ {0,3}(`+|~+)[ \t]*$/);
|
|
55
|
+
if (close && close[1][0] === delimiter && close[1].length >= fence.length) {
|
|
56
|
+
closed = true;
|
|
57
|
+
index += 1;
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
body.push(candidate);
|
|
61
|
+
index += 1;
|
|
62
|
+
}
|
|
63
|
+
if (!closed)
|
|
64
|
+
invalid(options, "contains an unclosed fenced script block");
|
|
65
|
+
const script = body.join("\n");
|
|
66
|
+
if (script.trim().length === 0)
|
|
67
|
+
invalid(options, "contains an empty fenced script block");
|
|
68
|
+
if (sawNone) {
|
|
69
|
+
invalid(options, "the explicit '- (none)' replacement cannot be combined with Verification declarations");
|
|
70
|
+
}
|
|
71
|
+
declarations.push({ executor, script });
|
|
72
|
+
}
|
|
73
|
+
if (sawNone)
|
|
74
|
+
return [];
|
|
75
|
+
if (declarations.length === 0)
|
|
76
|
+
invalid(options, "must contain at least one fenced script block");
|
|
77
|
+
return declarations;
|
|
78
|
+
}
|
|
79
|
+
function fenceFor(executor, script) {
|
|
80
|
+
const candidateLengths = ["`", "~"].map((delimiter) => {
|
|
81
|
+
const largestRun = script.split("\n").reduce((largest, line) => {
|
|
82
|
+
const match = line.match(new RegExp(`^ {0,3}\\${delimiter}+`));
|
|
83
|
+
return Math.max(largest, match?.[0].trim().length ?? 0);
|
|
84
|
+
}, 0);
|
|
85
|
+
return { delimiter, length: Math.max(3, largestRun + 1) };
|
|
86
|
+
});
|
|
87
|
+
const selected = candidateLengths.sort((left, right) => left.length - right.length)[0];
|
|
88
|
+
return selected.delimiter.repeat(selected.length);
|
|
89
|
+
}
|
|
90
|
+
export function renderVerificationDeclarations(declarations) {
|
|
91
|
+
return declarations.map((declaration) => {
|
|
92
|
+
const fence = fenceFor(declaration.executor, declaration.script);
|
|
93
|
+
return `${fence}${declaration.executor}\n${declaration.script}\n${fence}`;
|
|
94
|
+
}).join("\n\n");
|
|
95
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { spawn as nodeSpawn } from "node:child_process";
|
|
2
|
-
import { terminateProcessGroup } from "./process-
|
|
2
|
+
import { terminateProcessGroup } from "./run-control/process-tree.js";
|
|
3
3
|
export const WORKTREE_BOOTSTRAP_OUTPUT_TAIL_BYTES = 2048;
|
|
4
4
|
export const WORKTREE_BOOTSTRAP_KILL_GRACE_MS = 10_000;
|
|
5
5
|
function defaultWait(ms) {
|
|
@@ -101,23 +101,19 @@ export function worktreeContainsCoordinate(root, candidate) {
|
|
|
101
101
|
}
|
|
102
102
|
export async function stableRepoRoot(cwd) {
|
|
103
103
|
const git = createGit(cwd);
|
|
104
|
-
let
|
|
104
|
+
let facts;
|
|
105
105
|
try {
|
|
106
|
-
|
|
106
|
+
facts = (await git.raw(["rev-parse", "--is-bare-repository", "--path-format=absolute", "--git-common-dir"])).trim().split("\n");
|
|
107
107
|
}
|
|
108
108
|
catch (error) {
|
|
109
|
-
throw wrapGitError("rev-parse
|
|
109
|
+
throw wrapGitError("rev-parse repository coordinate", error, cwd);
|
|
110
110
|
}
|
|
111
|
+
const [bare, commonDir] = facts;
|
|
111
112
|
if (bare === "true") {
|
|
112
113
|
throw new FlowError("INTERNAL_STATE", "Keiyaku worktree operations require a non-bare repository");
|
|
113
114
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
commonDir = (await git.raw(["rev-parse", "--path-format=absolute", "--git-common-dir"])).trim();
|
|
117
|
-
}
|
|
118
|
-
catch (error) {
|
|
119
|
-
throw wrapGitError("rev-parse --path-format=absolute --git-common-dir", error, cwd);
|
|
120
|
-
}
|
|
115
|
+
if (!commonDir)
|
|
116
|
+
throw new FlowError("INTERNAL_STATE", "Git did not report its common directory");
|
|
121
117
|
const stableRoot = gitCommonRootPath(commonDir);
|
|
122
118
|
if (!path.isAbsolute(stableRoot)) {
|
|
123
119
|
throw new FlowError("INTERNAL_STATE", `git common directory is not absolute: ${commonDir}`);
|
|
@@ -132,7 +128,7 @@ export async function stableRepoRoot(cwd) {
|
|
|
132
128
|
export async function contractWorktreePathFromBind(cwd, contractId, bind) {
|
|
133
129
|
return contractWorktreePathFromStableRoot(await stableRepoRoot(cwd), contractId, bind);
|
|
134
130
|
}
|
|
135
|
-
function contractWorktreePathFromStableRoot(stableRoot, contractId, bind) {
|
|
131
|
+
export function contractWorktreePathFromStableRoot(stableRoot, contractId, bind) {
|
|
136
132
|
return path.join(stableRoot, ".keiyaku", "wt", bind?.data.place ?? contractId);
|
|
137
133
|
}
|
|
138
134
|
export async function contractWorktreePath(cwd, contractId) {
|