@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
package/build/core/renew.js
CHANGED
|
@@ -3,7 +3,7 @@ import { createGit, errorContainsAnyPattern, MISSING_HEAD_PATTERNS, parseRevList
|
|
|
3
3
|
import { readLedger } from "./ledger.js";
|
|
4
4
|
import { hasStrandableProjectionGeneration } from "./projection/index.js";
|
|
5
5
|
import { refreshContractView } from "./contract-view.js";
|
|
6
|
-
import { appendRenewSettlement, captureRenewSeal, captureSeal, findOpenArc, getFence, getMaxArc, receiptsAfterFence } from "./seal.js";
|
|
6
|
+
import { appendRenewSettlement, appendScopeTerms, captureRenewSeal, captureSeal, findOpenArc, getFence, getMaxArc, receiptsAfterFence } from "./seal.js";
|
|
7
7
|
import { deriveContractState, isTerminalState } from "./status/lifecycle.js";
|
|
8
8
|
import { commissionTarget, latestBase } from "./status/drift.js";
|
|
9
9
|
import { findContractWorktreePath, findRegisteredContractWorktreePath, stableRepoRoot } from "./worktree-path.js";
|
|
@@ -13,7 +13,7 @@ import { createRecoveryRef, deleteRecoveryRef } from "../git/refs.js";
|
|
|
13
13
|
import { resolveContractAddress, withResolvedAddress } from "./addressing.js";
|
|
14
14
|
import { ACTOR_IDENTITY } from "../keiyaku.js";
|
|
15
15
|
import { persistResponseHistory } from "./transcripts.js";
|
|
16
|
-
import { convergeRenewRecoverySession,
|
|
16
|
+
import { convergeRenewRecoverySession, assertNoRenewRecoverySession, removeRenewRecoverySession, writeRenewRecoverySession, } from "./renew-session.js";
|
|
17
17
|
function assertOperable(contractId, entries) {
|
|
18
18
|
const state = deriveContractState(entries);
|
|
19
19
|
if (state.state === "corrupt") {
|
|
@@ -47,7 +47,7 @@ const RENEW_RECOVERY_REF_PREFIX = "refs/keiyaku/recovery/renew";
|
|
|
47
47
|
function renewRecoveryRef(contractId, oldHead) {
|
|
48
48
|
return `${RENEW_RECOVERY_REF_PREFIX}/${contractId}/${oldHead}`;
|
|
49
49
|
}
|
|
50
|
-
async function
|
|
50
|
+
export async function rewriteRenewBranch(input) {
|
|
51
51
|
const { cwd, contractId, oldHead, recoveryRef, newBase, oldBase, branch, preserveConflict } = input;
|
|
52
52
|
const git = createGit(cwd);
|
|
53
53
|
const originalBranch = branch ? await getCurrentBranch(cwd) : undefined;
|
|
@@ -160,7 +160,7 @@ async function assertRenewCoordinates(input) {
|
|
|
160
160
|
}
|
|
161
161
|
async function finishRenew(input) {
|
|
162
162
|
const capture = input.capture === undefined
|
|
163
|
-
? await captureSeal(input.repositoryRoot, input.entries, input.oldHead
|
|
163
|
+
? await captureSeal(input.repositoryRoot, input.entries, input.oldHead)
|
|
164
164
|
: input.capture;
|
|
165
165
|
const settlement = await appendRenewSettlement({
|
|
166
166
|
cwd: input.repositoryRoot,
|
|
@@ -198,11 +198,7 @@ export async function renewContract(input) {
|
|
|
198
198
|
const repositoryRoot = await stableRepoRoot(input.cwd);
|
|
199
199
|
const worktreePath = await findContractWorktreePath(repositoryRoot, input.contractId);
|
|
200
200
|
const rewriteCwd = worktreePath ?? input.cwd;
|
|
201
|
-
|
|
202
|
-
throw new FlowError("RENEW_SESSION_OPEN", `renew recovery session is open for ${input.contractId}`, {
|
|
203
|
-
facts: { kind: "renew_session", commission: input.contractId, nextAction: "continue-or-abort" },
|
|
204
|
-
});
|
|
205
|
-
}
|
|
201
|
+
await assertNoRenewRecoverySession(repositoryRoot, input.contractId);
|
|
206
202
|
if (input.resolveConflicts && !worktreePath) {
|
|
207
203
|
throw new FlowError("INTERNAL_STATE", `renew --resolve requires ${input.contractId}'s registered contract worktree`);
|
|
208
204
|
}
|
|
@@ -233,12 +229,27 @@ export async function renewContract(input) {
|
|
|
233
229
|
targetHead,
|
|
234
230
|
entries: ledger.entries,
|
|
235
231
|
});
|
|
236
|
-
const capture = await captureSeal(input.cwd, ledger.entries, oldHead, input.waivePaths);
|
|
237
232
|
if (getMaxArc(ledger.entries) > 0 && !findOpenArc(ledger.entries)) {
|
|
238
233
|
const loose = await receiptsAfterFence(input.cwd, ledger.entries, oldHead);
|
|
239
234
|
if (loose.length > 0)
|
|
240
235
|
throw looseCommitError(input.contractId, loose);
|
|
241
236
|
}
|
|
237
|
+
if (oldBase !== targetHead) {
|
|
238
|
+
const deliveryWorktree = worktreePath ?? repositoryRoot;
|
|
239
|
+
await assertNoInFlightProjection(repositoryRoot, input.contractId, deliveryWorktree, input.nowMs);
|
|
240
|
+
}
|
|
241
|
+
await appendScopeTerms({
|
|
242
|
+
cwd: repositoryRoot,
|
|
243
|
+
renderCwd: rewriteCwd,
|
|
244
|
+
contractId: input.contractId,
|
|
245
|
+
patterns: input.appendScope,
|
|
246
|
+
actor: input.actor,
|
|
247
|
+
nowMs: input.nowMs,
|
|
248
|
+
});
|
|
249
|
+
const currentLedger = input.appendScope?.length ? await readLedger(repositoryRoot, input.contractId) : ledger;
|
|
250
|
+
if (!currentLedger)
|
|
251
|
+
throw new FlowError("INTERNAL_STATE", `contract ${input.contractId} disappeared while appending scope`);
|
|
252
|
+
const capture = await captureSeal(input.cwd, currentLedger.entries, oldHead);
|
|
242
253
|
if (oldBase === targetHead) {
|
|
243
254
|
return {
|
|
244
255
|
contractId: input.contractId,
|
|
@@ -246,14 +257,12 @@ export async function renewContract(input) {
|
|
|
246
257
|
newBase: targetHead,
|
|
247
258
|
oldHead,
|
|
248
259
|
newHead: oldHead,
|
|
249
|
-
ledgerHead:
|
|
260
|
+
ledgerHead: currentLedger.head,
|
|
250
261
|
alreadyFresh: true,
|
|
251
262
|
sealed: null,
|
|
252
263
|
recoveryCleanup: null,
|
|
253
264
|
};
|
|
254
265
|
}
|
|
255
|
-
const deliveryWorktree = worktreePath ?? repositoryRoot;
|
|
256
|
-
await assertNoInFlightProjection(repositoryRoot, input.contractId, deliveryWorktree, input.nowMs);
|
|
257
266
|
const recoveryRef = renewRecoveryRef(input.contractId, oldHead);
|
|
258
267
|
await createRecoveryRef(input.cwd, recoveryRef, oldHead);
|
|
259
268
|
if (input.resolveConflicts) {
|
|
@@ -266,7 +275,7 @@ export async function renewContract(input) {
|
|
|
266
275
|
});
|
|
267
276
|
}
|
|
268
277
|
try {
|
|
269
|
-
await
|
|
278
|
+
await rewriteRenewBranch({
|
|
270
279
|
cwd: rewriteCwd,
|
|
271
280
|
contractId: input.contractId,
|
|
272
281
|
oldHead,
|
|
@@ -287,8 +296,8 @@ export async function renewContract(input) {
|
|
|
287
296
|
return await finishRenew({
|
|
288
297
|
repositoryRoot,
|
|
289
298
|
rewriteCwd,
|
|
290
|
-
ledgerHead:
|
|
291
|
-
entries:
|
|
299
|
+
ledgerHead: currentLedger.head,
|
|
300
|
+
entries: currentLedger.entries,
|
|
292
301
|
contractId: input.contractId,
|
|
293
302
|
oldBase,
|
|
294
303
|
oldHead,
|
|
@@ -390,19 +399,29 @@ export async function continueRenewContract(input) {
|
|
|
390
399
|
facts: { kind: "renew_session", commission: input.contractId, nextAction: "abort" },
|
|
391
400
|
});
|
|
392
401
|
}
|
|
402
|
+
await appendScopeTerms({
|
|
403
|
+
cwd: repositoryRoot,
|
|
404
|
+
renderCwd: rewriteCwd,
|
|
405
|
+
contractId: input.contractId,
|
|
406
|
+
patterns: input.appendScope,
|
|
407
|
+
actor: input.actor,
|
|
408
|
+
nowMs: input.nowMs,
|
|
409
|
+
});
|
|
410
|
+
const currentLedger = input.appendScope?.length ? await readLedger(repositoryRoot, input.contractId) : ledger;
|
|
411
|
+
if (!currentLedger)
|
|
412
|
+
throw new FlowError("INTERNAL_STATE", `contract ${input.contractId} disappeared while appending scope`);
|
|
393
413
|
const capture = await captureRenewSeal({
|
|
394
414
|
cwd: repositoryRoot,
|
|
395
|
-
entries:
|
|
415
|
+
entries: currentLedger.entries,
|
|
396
416
|
receiptHead: session.oldHead,
|
|
397
417
|
contentBase: session.newBase,
|
|
398
418
|
contentHead: newHead,
|
|
399
|
-
waivePaths: input.waivePaths,
|
|
400
419
|
});
|
|
401
420
|
return await finishRenew({
|
|
402
421
|
repositoryRoot,
|
|
403
422
|
rewriteCwd,
|
|
404
|
-
ledgerHead:
|
|
405
|
-
entries:
|
|
423
|
+
ledgerHead: currentLedger.head,
|
|
424
|
+
entries: currentLedger.entries,
|
|
406
425
|
contractId: input.contractId,
|
|
407
426
|
oldBase: session.oldBase,
|
|
408
427
|
oldHead: session.oldHead,
|
|
@@ -469,7 +488,7 @@ export async function renewKeiyaku(input) {
|
|
|
469
488
|
contractId,
|
|
470
489
|
actor: ACTOR_IDENTITY,
|
|
471
490
|
nowMs: Date.now(),
|
|
472
|
-
|
|
491
|
+
appendScope: input.appendScope,
|
|
473
492
|
resolveConflicts: input.resolveConflicts,
|
|
474
493
|
});
|
|
475
494
|
return await buildRenewOutcome(input.cwd, address, renewed);
|
|
@@ -530,7 +549,7 @@ export async function continueRenewKeiyaku(input) {
|
|
|
530
549
|
contractId: input.address.binding.commissionId,
|
|
531
550
|
actor: ACTOR_IDENTITY,
|
|
532
551
|
nowMs: Date.now(),
|
|
533
|
-
|
|
552
|
+
appendScope: input.appendScope,
|
|
534
553
|
})));
|
|
535
554
|
}
|
|
536
555
|
export async function abortRenewKeiyaku(input) {
|
package/build/core/scope.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { FlowError } from "../flow-error.js";
|
|
1
2
|
export class ScopePatternError extends Error {
|
|
2
3
|
code = "INVALID_SCOPE_PATTERN";
|
|
3
4
|
constructor(message) {
|
|
@@ -31,10 +32,136 @@ function stripUnescapedTrailingSpaces(value) {
|
|
|
31
32
|
function canonicalizeScopePatternInput(pattern) {
|
|
32
33
|
return stripUnescapedTrailingSpaces(pattern.replace(/^\s+/, ""));
|
|
33
34
|
}
|
|
34
|
-
/** True when a raw
|
|
35
|
+
/** True when a raw scope line has no pattern after gitignore blank rules. */
|
|
35
36
|
export function isBlankScopePatternLine(line) {
|
|
36
37
|
return canonicalizeScopePatternInput(line).length === 0;
|
|
37
38
|
}
|
|
39
|
+
/** Parse Keiyaku's source-line Scope opener, not a normalized Markdown node. */
|
|
40
|
+
export function parseScopeFenceOpening(line) {
|
|
41
|
+
const match = line.match(/^(`{3,})$/);
|
|
42
|
+
return match ? { length: match[1].length } : null;
|
|
43
|
+
}
|
|
44
|
+
/** Keiyaku closer: column-zero backticks, at least opener length, then whitespace. */
|
|
45
|
+
export function isScopeFenceClosing(line, fence) {
|
|
46
|
+
const match = line.match(/^(`+)[ \t]*$/);
|
|
47
|
+
return Boolean(match && match[1].length >= fence.length);
|
|
48
|
+
}
|
|
49
|
+
function fencedScopeError(heading, detail) {
|
|
50
|
+
return new FlowError("INVALID_KEIYAKU_DRAFT", `${heading} must contain exactly one fenced gitignore-subset block; ${detail}. ` +
|
|
51
|
+
`Paste-ready canonical example:\n\n${heading}\n\`\`\`\nfoo\nbar\n\`\`\``);
|
|
52
|
+
}
|
|
53
|
+
/** Validate and normalize the one fenced block that constitutes a Scope body. */
|
|
54
|
+
export function validateScopeFragmentLines(lines, options) {
|
|
55
|
+
const first = lines.findIndex((line) => line.trim().length > 0);
|
|
56
|
+
if (first === -1)
|
|
57
|
+
throw fencedScopeError(options.heading, "found no fenced block");
|
|
58
|
+
const fence = parseScopeFenceOpening(lines[first]);
|
|
59
|
+
if (!fence)
|
|
60
|
+
throw fencedScopeError(options.heading, `offending line '${lines[first]}'`);
|
|
61
|
+
let close = -1;
|
|
62
|
+
for (let index = first + 1; index < lines.length; index += 1) {
|
|
63
|
+
if (isScopeFenceClosing(lines[index], fence)) {
|
|
64
|
+
close = index;
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (close === -1)
|
|
69
|
+
throw fencedScopeError(options.heading, "the fenced block is not closed");
|
|
70
|
+
const outside = lines.slice(close + 1).find((line) => line.trim().length > 0);
|
|
71
|
+
if (outside !== undefined) {
|
|
72
|
+
throw fencedScopeError(options.heading, `offending line '${outside}' appears outside the fenced block`);
|
|
73
|
+
}
|
|
74
|
+
const patterns = [];
|
|
75
|
+
for (const line of lines.slice(first + 1, close)) {
|
|
76
|
+
if (isBlankScopePatternLine(line))
|
|
77
|
+
continue;
|
|
78
|
+
try {
|
|
79
|
+
patterns.push(normalizeScopePattern(line));
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
if (error instanceof ScopePatternError) {
|
|
83
|
+
throw new FlowError("INVALID_KEIYAKU_DRAFT", error.message);
|
|
84
|
+
}
|
|
85
|
+
throw error;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if (patterns.length === 0)
|
|
89
|
+
throw new FlowError("INVALID_KEIYAKU_DRAFT", options.emptyMessage);
|
|
90
|
+
return patterns;
|
|
91
|
+
}
|
|
92
|
+
function sourceFenceOpening(line) {
|
|
93
|
+
const match = line.match(/^ {0,3}(`{3,}|~{3,})(.*)$/);
|
|
94
|
+
if (!match)
|
|
95
|
+
return null;
|
|
96
|
+
const marker = match[1];
|
|
97
|
+
if (marker[0] === "`" && match[2].includes("`"))
|
|
98
|
+
return null;
|
|
99
|
+
return { delimiter: marker[0], length: marker.length };
|
|
100
|
+
}
|
|
101
|
+
function sourceFenceClosing(line, fence) {
|
|
102
|
+
const match = line.match(/^ {0,3}(`+|~+)[ \t]*$/);
|
|
103
|
+
return Boolean(match && match[1][0] === fence.delimiter && match[1].length >= fence.length);
|
|
104
|
+
}
|
|
105
|
+
/** Locate structural H1/H2 source ranges while leaving fenced headings inert. */
|
|
106
|
+
export function markdownSourceSections(markdown) {
|
|
107
|
+
const lines = [];
|
|
108
|
+
let start = 0;
|
|
109
|
+
while (start < markdown.length) {
|
|
110
|
+
const newline = markdown.indexOf("\n", start);
|
|
111
|
+
const end = newline === -1 ? markdown.length : newline;
|
|
112
|
+
const next = newline === -1 ? markdown.length : newline + 1;
|
|
113
|
+
const raw = markdown.slice(start, end);
|
|
114
|
+
lines.push({ text: raw.endsWith("\r") ? raw.slice(0, -1) : raw, start, next });
|
|
115
|
+
start = next;
|
|
116
|
+
}
|
|
117
|
+
let frontMatterEnd = -1;
|
|
118
|
+
if (lines[0]?.text.trim() === "---") {
|
|
119
|
+
frontMatterEnd = lines.findIndex((line, index) => index > 0 && line.text.trim() === "---");
|
|
120
|
+
}
|
|
121
|
+
const headings = [];
|
|
122
|
+
let fence = null;
|
|
123
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
124
|
+
const line = lines[index];
|
|
125
|
+
if (index <= frontMatterEnd)
|
|
126
|
+
continue;
|
|
127
|
+
if (fence) {
|
|
128
|
+
if (sourceFenceClosing(line.text, fence))
|
|
129
|
+
fence = null;
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
const opening = sourceFenceOpening(line.text);
|
|
133
|
+
if (opening) {
|
|
134
|
+
fence = opening;
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
// Column-0 only: indented ATX-looking lines inside list items remain content.
|
|
138
|
+
const heading = line.text.match(/^(#{1,2})(?!#)[ \t]+(.+)$/);
|
|
139
|
+
if (!heading)
|
|
140
|
+
continue;
|
|
141
|
+
headings.push({
|
|
142
|
+
level: heading[1].length,
|
|
143
|
+
title: heading[2],
|
|
144
|
+
start: line.start,
|
|
145
|
+
contentStart: line.next,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
return headings.map((heading, index) => {
|
|
149
|
+
const next = heading.level === 2
|
|
150
|
+
? headings.slice(index + 1).find((candidate) => candidate.level === 2)
|
|
151
|
+
: headings[index + 1];
|
|
152
|
+
return { ...heading, end: next?.start ?? markdown.length };
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
/** Render canonical Scope bytes with a fence that content cannot close. */
|
|
156
|
+
export function renderScopeFragment(patterns) {
|
|
157
|
+
let longestRun = 0;
|
|
158
|
+
for (const pattern of patterns) {
|
|
159
|
+
for (const run of pattern.match(/`+/g) ?? [])
|
|
160
|
+
longestRun = Math.max(longestRun, run.length);
|
|
161
|
+
}
|
|
162
|
+
const fence = "`".repeat(Math.max(3, longestRun + 1));
|
|
163
|
+
return `${fence}\n${patterns.join("\n")}\n${fence}`;
|
|
164
|
+
}
|
|
38
165
|
/**
|
|
39
166
|
* Canonicalize a scope Gitignore-like pattern for matching and persistence.
|
|
40
167
|
* Strips only a cosmetic leading `./` (including after `!`); preserves meaningful
|
|
@@ -57,7 +184,7 @@ export function normalizeScopePattern(pattern) {
|
|
|
57
184
|
return negative ? `!${body}` : body;
|
|
58
185
|
}
|
|
59
186
|
/**
|
|
60
|
-
* Normalize a path to Git diff form for
|
|
187
|
+
* Normalize a path to Git diff form for matcher comparison:
|
|
61
188
|
* either slash convention, collapse separators, and strip cosmetic `./` or `/`.
|
|
62
189
|
* Leading and trailing spaces are significant path bytes and must remain unchanged.
|
|
63
190
|
*/
|
package/build/core/seal.js
CHANGED
|
@@ -4,7 +4,7 @@ import { KEIYAKU_FILE } from "../keiyaku.js";
|
|
|
4
4
|
import { formatLedgerEntryCommitMessage } from "./entry.js";
|
|
5
5
|
import { activeLedgerRef, appendWithRetry, readLedger } from "./ledger.js";
|
|
6
6
|
import { refreshContractView } from "./contract-view.js";
|
|
7
|
-
import {
|
|
7
|
+
import { normalizeScopePattern, scopePathEvidence, } from "./scope.js";
|
|
8
8
|
import { deriveContractState, isTerminalState } from "./status/lifecycle.js";
|
|
9
9
|
import { casUpdateRefLog, createRefLogCommit } from "./ref-log.js";
|
|
10
10
|
function findBind(entries) {
|
|
@@ -103,20 +103,41 @@ async function scopeViolations(cwd, paths, scope) {
|
|
|
103
103
|
const auditedPaths = paths.filter((changedPath) => changedPath !== KEIYAKU_FILE);
|
|
104
104
|
return (await scopePathEvidence(cwd, scope, auditedPaths)).filter((evidence) => !evidence.matched);
|
|
105
105
|
}
|
|
106
|
-
async function auditScope(cwd, entries, paths
|
|
106
|
+
async function auditScope(cwd, entries, paths) {
|
|
107
107
|
const scope = effectiveScope(entries);
|
|
108
108
|
const scopeViolationEvidence = await scopeViolations(cwd, paths, scope);
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
if (unwaived.length > 0) {
|
|
112
|
-
throw new ScopeAuditError(findBind(entries).contract, scope, unwaived);
|
|
109
|
+
if (scopeViolationEvidence.length > 0) {
|
|
110
|
+
throw new ScopeAuditError(findBind(entries).contract, scope, scopeViolationEvidence);
|
|
113
111
|
}
|
|
114
112
|
const violations = scopeViolationEvidence.map((violation) => violation.path);
|
|
115
113
|
return {
|
|
116
|
-
scopeAudit: { violations
|
|
114
|
+
scopeAudit: { violations },
|
|
117
115
|
scopeViolationEvidence,
|
|
118
116
|
};
|
|
119
117
|
}
|
|
118
|
+
const APPEND_SCOPE_AMENDMENT = "Scope extended by lifecycle command.";
|
|
119
|
+
export async function appendScopeTerms(input) {
|
|
120
|
+
if (!input.patterns || input.patterns.length === 0)
|
|
121
|
+
return null;
|
|
122
|
+
const patterns = input.patterns.map(normalizeScopePattern);
|
|
123
|
+
const at = new Date(input.nowMs).toISOString();
|
|
124
|
+
const result = await appendWithRetry(input.cwd, activeLedgerRef(input.contractId), (_head, entries) => {
|
|
125
|
+
assertOperable(input.contractId, entries);
|
|
126
|
+
return {
|
|
127
|
+
v: 1,
|
|
128
|
+
kind: "amend",
|
|
129
|
+
contract: input.contractId,
|
|
130
|
+
at,
|
|
131
|
+
actor: input.actor,
|
|
132
|
+
data: { amendment: APPEND_SCOPE_AMENDMENT, scopeDelta: { add: patterns } },
|
|
133
|
+
};
|
|
134
|
+
});
|
|
135
|
+
if (!result.ok) {
|
|
136
|
+
throw new FlowError("INTERNAL_STATE", `contract ${input.contractId} active ledger disappeared while appending scope`);
|
|
137
|
+
}
|
|
138
|
+
await refreshContractView({ ledgerCwd: input.cwd, renderCwd: input.renderCwd, contractId: input.contractId });
|
|
139
|
+
return result.head;
|
|
140
|
+
}
|
|
120
141
|
export class ScopeAuditError extends FlowError {
|
|
121
142
|
scopeViolationEvidence;
|
|
122
143
|
constructor(_contractId, scope, evidence) {
|
|
@@ -159,7 +180,7 @@ export function petitionSealSource(entries) {
|
|
|
159
180
|
};
|
|
160
181
|
}
|
|
161
182
|
/** Shared read-only owner for the seal capture and its compact changed-path evidence. */
|
|
162
|
-
export async function previewSeal(cwd, entries, head = "HEAD"
|
|
183
|
+
export async function previewSeal(cwd, entries, head = "HEAD") {
|
|
163
184
|
const arc = currentSealArc(entries);
|
|
164
185
|
if (arc === null)
|
|
165
186
|
return { capture: null, paths: [] };
|
|
@@ -170,7 +191,7 @@ export async function previewSeal(cwd, entries, head = "HEAD", waivePaths = [])
|
|
|
170
191
|
changedPaths(cwd, oldFence, newFence),
|
|
171
192
|
diffstat(cwd, oldFence, newFence),
|
|
172
193
|
]);
|
|
173
|
-
const scopeAudit = await auditScope(cwd, entries, paths
|
|
194
|
+
const scopeAudit = await auditScope(cwd, entries, paths);
|
|
174
195
|
return {
|
|
175
196
|
capture: {
|
|
176
197
|
arc,
|
|
@@ -183,8 +204,8 @@ export async function previewSeal(cwd, entries, head = "HEAD", waivePaths = [])
|
|
|
183
204
|
paths,
|
|
184
205
|
};
|
|
185
206
|
}
|
|
186
|
-
export async function captureSeal(cwd, entries, head = "HEAD"
|
|
187
|
-
return (await previewSeal(cwd, entries, head
|
|
207
|
+
export async function captureSeal(cwd, entries, head = "HEAD") {
|
|
208
|
+
return (await previewSeal(cwd, entries, head)).capture;
|
|
188
209
|
}
|
|
189
210
|
/** Preserve original receipt hashes while auditing the rebased delivery content. */
|
|
190
211
|
export async function captureRenewSeal(input) {
|
|
@@ -208,7 +229,7 @@ export async function captureRenewSeal(input) {
|
|
|
208
229
|
newFence: receiptHead,
|
|
209
230
|
receipts,
|
|
210
231
|
diffstat: stat,
|
|
211
|
-
...await auditScope(input.cwd, input.entries, paths
|
|
232
|
+
...await auditScope(input.cwd, input.entries, paths),
|
|
212
233
|
};
|
|
213
234
|
}
|
|
214
235
|
export async function sealCurrentArc(input) {
|
|
@@ -217,7 +238,7 @@ export async function sealCurrentArc(input) {
|
|
|
217
238
|
throw new FlowError("INTERNAL_STATE", `contract ${input.contractId} not found or not active`);
|
|
218
239
|
}
|
|
219
240
|
assertOperable(input.contractId, ledger.entries);
|
|
220
|
-
const capture = input.capture ?? await captureSeal(input.cwd, ledger.entries, "HEAD"
|
|
241
|
+
const capture = input.capture ?? await captureSeal(input.cwd, ledger.entries, "HEAD");
|
|
221
242
|
if (!capture)
|
|
222
243
|
return null;
|
|
223
244
|
const at = new Date(input.nowMs).toISOString();
|
|
@@ -235,7 +256,7 @@ export async function sealCurrentArc(input) {
|
|
|
235
256
|
}
|
|
236
257
|
const { scopeViolationEvidence: _scopeViolationEvidence, ...durableCapture } = capture;
|
|
237
258
|
return {
|
|
238
|
-
v:
|
|
259
|
+
v: 2,
|
|
239
260
|
kind: "seal",
|
|
240
261
|
contract: input.contractId,
|
|
241
262
|
at,
|
|
@@ -277,7 +298,7 @@ export async function appendRenewSettlement(input) {
|
|
|
277
298
|
if (input.capture) {
|
|
278
299
|
const { scopeViolationEvidence: _evidence, ...durableCapture } = input.capture;
|
|
279
300
|
const sealEntry = {
|
|
280
|
-
v:
|
|
301
|
+
v: 2,
|
|
281
302
|
kind: "seal",
|
|
282
303
|
contract: input.contractId,
|
|
283
304
|
at,
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { spawn } from "node:child_process";
|
|
2
1
|
import { getConfig } from "../../config/env.js";
|
|
3
2
|
import { assertSettingsKnobUsable } from "../../config/settings/disease.js";
|
|
4
3
|
import { resolveReviewerAgentName } from "../../config/settings/knobs.js";
|
|
@@ -15,6 +14,7 @@ import { persistResponseHistory } from "../transcripts.js";
|
|
|
15
14
|
import { logInfo } from "../../telemetry/logger.js";
|
|
16
15
|
import { execSubagent } from "../call/call.js";
|
|
17
16
|
import { readContractLog } from "../log.js";
|
|
17
|
+
import { runVerificationCommand } from "./verification.js";
|
|
18
18
|
const CLAIM_VERIFICATION_OUTPUT_MAX_CHARS = 1200;
|
|
19
19
|
const REVIEW_PASS_TOKEN = "PASS";
|
|
20
20
|
const REVIEW_REJECTION_MESSAGE = "CLAIM rejected by independent reviewer.";
|
|
@@ -48,31 +48,6 @@ function buildReviewAttemptBlock(reviewNumber) {
|
|
|
48
48
|
"Focus on whether the previous concerns have been addressed.",
|
|
49
49
|
];
|
|
50
50
|
}
|
|
51
|
-
async function runCommand(options) {
|
|
52
|
-
return await new Promise((resolve, reject) => {
|
|
53
|
-
const child = spawn(options.command, options.args, {
|
|
54
|
-
cwd: options.cwd,
|
|
55
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
56
|
-
env: getConfig().runtimeEnv,
|
|
57
|
-
});
|
|
58
|
-
let stdout = "";
|
|
59
|
-
let stderr = "";
|
|
60
|
-
child.stdout.on("data", (chunk) => {
|
|
61
|
-
stdout += String(chunk);
|
|
62
|
-
});
|
|
63
|
-
child.stderr.on("data", (chunk) => {
|
|
64
|
-
stderr += String(chunk);
|
|
65
|
-
});
|
|
66
|
-
child.on("error", reject);
|
|
67
|
-
child.on("close", (code) => {
|
|
68
|
-
resolve({
|
|
69
|
-
code: typeof code === "number" ? code : -1,
|
|
70
|
-
stdout,
|
|
71
|
-
stderr,
|
|
72
|
-
});
|
|
73
|
-
});
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
51
|
function clipCommandOutput(value, maxChars = CLAIM_VERIFICATION_OUTPUT_MAX_CHARS) {
|
|
77
52
|
const trimmed = value.trim();
|
|
78
53
|
if (!trimmed)
|
|
@@ -128,11 +103,7 @@ export function createCommandVerificationGate() {
|
|
|
128
103
|
async evaluate({ cwd, verificationCommands }) {
|
|
129
104
|
for (const verification of verificationCommands) {
|
|
130
105
|
logInfo(`[CLAIM] Running verification: ${verification.label}`, { cwd, section: "script", progressOnly: true });
|
|
131
|
-
const result = await
|
|
132
|
-
cwd,
|
|
133
|
-
command: verification.command,
|
|
134
|
-
args: verification.args,
|
|
135
|
-
});
|
|
106
|
+
const result = await runVerificationCommand(cwd, verification);
|
|
136
107
|
if (result.code !== 0) {
|
|
137
108
|
return {
|
|
138
109
|
pass: false,
|
|
@@ -141,8 +112,8 @@ export function createCommandVerificationGate() {
|
|
|
141
112
|
facts: {
|
|
142
113
|
kind: "verification_failure",
|
|
143
114
|
label: verification.label,
|
|
144
|
-
stdout: clipCommandOutput(result.stdout),
|
|
145
|
-
stderr: clipCommandOutput(result.stderr),
|
|
115
|
+
stdout: clipCommandOutput(result.stdout.tail),
|
|
116
|
+
stderr: clipCommandOutput(result.stderr.tail),
|
|
146
117
|
},
|
|
147
118
|
};
|
|
148
119
|
}
|
|
@@ -55,7 +55,7 @@ export async function previewPetition(input) {
|
|
|
55
55
|
let seal;
|
|
56
56
|
if (freshness.result === "pass" && arc.result === "pass" && state.result === "pass") {
|
|
57
57
|
try {
|
|
58
|
-
const sealPreview = await previewSeal(context.deliveryCwd, context.ledger.entries, "HEAD"
|
|
58
|
+
const sealPreview = await previewSeal(context.deliveryCwd, context.ledger.entries, "HEAD");
|
|
59
59
|
seal = { name: "seal", result: "pass", detail: sealPathDetail(sealPreview.paths) };
|
|
60
60
|
}
|
|
61
61
|
catch (error) {
|
|
@@ -3,7 +3,7 @@ import { createGit, errorContainsAnyPattern, MISSING_HEAD_PATTERNS, wrapGitError
|
|
|
3
3
|
import { parseCommandMarkdown } from "../command-markdown.js";
|
|
4
4
|
import { activeLedgerRef, appendWithRetry, readLedger } from "../ledger.js";
|
|
5
5
|
import { allocateSettlementSeat, queueMembership, readQueueReadModel } from "./queue.js";
|
|
6
|
-
import { findOpenArc, getMaxArc, receiptsAfterFence, sealCurrentArc } from "../seal.js";
|
|
6
|
+
import { appendScopeTerms, findOpenArc, getMaxArc, receiptsAfterFence, sealCurrentArc } from "../seal.js";
|
|
7
7
|
import { deriveContractState } from "../status/lifecycle.js";
|
|
8
8
|
import { commissionTarget, latestBase } from "../status/drift.js";
|
|
9
9
|
import { findContractWorktreePath } from "../worktree-path.js";
|
|
@@ -73,13 +73,20 @@ export async function petitionContract(input) {
|
|
|
73
73
|
await assertPetitionFresh(input.cwd, input.contractId, ledger.entries, base);
|
|
74
74
|
await assertPetitionArcReady(deliveryCwd, input.contractId, ledger.entries);
|
|
75
75
|
assertPetitionable(input.contractId, ledger.entries);
|
|
76
|
+
await appendScopeTerms({
|
|
77
|
+
cwd: input.cwd,
|
|
78
|
+
renderCwd: deliveryCwd,
|
|
79
|
+
contractId: input.contractId,
|
|
80
|
+
patterns: input.intent === "claim" ? input.appendScope : undefined,
|
|
81
|
+
actor: input.actor,
|
|
82
|
+
nowMs: input.nowMs,
|
|
83
|
+
});
|
|
76
84
|
const sealed = await sealCurrentArc({
|
|
77
85
|
cwd: deliveryCwd,
|
|
78
86
|
renderCwd: deliveryCwd,
|
|
79
87
|
contractId: input.contractId,
|
|
80
88
|
sealedBy: "petition",
|
|
81
89
|
nowMs: input.nowMs,
|
|
82
|
-
waivePaths: input.intent === "claim" ? input.waivePaths : undefined,
|
|
83
90
|
});
|
|
84
91
|
const allocated = await allocateSettlementSeat(input.cwd, {
|
|
85
92
|
target: target.ref,
|
|
@@ -148,6 +155,6 @@ export function buildPetitionClaimInput(input) {
|
|
|
148
155
|
plea: `Settle contract ${input.contractId} through the petition pipeline.`,
|
|
149
156
|
oath,
|
|
150
157
|
signal: input.signal,
|
|
151
|
-
|
|
158
|
+
appendScope: input.appendScope,
|
|
152
159
|
};
|
|
153
160
|
}
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { assertSettingsKnobUsable } from "../../config/settings/disease.js";
|
|
2
|
-
import { loadKeiyakuSettings } from "../../config/settings/loader.js";
|
|
3
1
|
import { FlowError } from "../../flow-error.js";
|
|
4
2
|
import { readCurrentContractContext } from "../context.js";
|
|
5
3
|
import { readLedger } from "../ledger.js";
|
|
@@ -13,6 +11,7 @@ import { scopePathEvidence } from "../scope.js";
|
|
|
13
11
|
import { nextReviewResponseNumber } from "../transcripts.js";
|
|
14
12
|
import { claimContract } from "./claim.js";
|
|
15
13
|
import { buildClaimGatePipeline, } from "./petition-claim-gates.js";
|
|
14
|
+
import { resolveVerificationCommands } from "./verification.js";
|
|
16
15
|
import { forfeitKeiyaku } from "./petition-forfeit.js";
|
|
17
16
|
import { assertHeadUnchanged } from "./petition-head-guard.js";
|
|
18
17
|
import { petitionContract } from "./petition.js";
|
|
@@ -23,13 +22,7 @@ async function resolveClaimGateConfig(cwd) {
|
|
|
23
22
|
verificationCommands: [],
|
|
24
23
|
};
|
|
25
24
|
try {
|
|
26
|
-
|
|
27
|
-
assertSettingsKnobUsable(loaded, "gate");
|
|
28
|
-
const gate = loaded.knobs?.gate;
|
|
29
|
-
if (!gate) {
|
|
30
|
-
return merged;
|
|
31
|
-
}
|
|
32
|
-
merged.verificationCommands = gate.commands ?? merged.verificationCommands;
|
|
25
|
+
merged.verificationCommands = await resolveVerificationCommands(cwd);
|
|
33
26
|
return merged;
|
|
34
27
|
}
|
|
35
28
|
catch (error) {
|
|
@@ -255,7 +248,7 @@ export async function claimKeiyaku(input, context, dependencies) {
|
|
|
255
248
|
nowMs: Date.now(),
|
|
256
249
|
intent: "claim",
|
|
257
250
|
oath,
|
|
258
|
-
|
|
251
|
+
appendScope: input.appendScope,
|
|
259
252
|
});
|
|
260
253
|
// 2. Pure verdict: judgment facts only; no claim execution.
|
|
261
254
|
// Capture one clock for verdict + claim so ledger timestamps match the prior
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { getConfig } from "../../config/env.js";
|
|
3
|
+
import { assertSettingsKnobUsable } from "../../config/settings/disease.js";
|
|
4
|
+
import { loadKeiyakuSettings } from "../../config/settings/loader.js";
|
|
5
|
+
import { FlowError } from "../../flow-error.js";
|
|
6
|
+
const AUDIT_OUTPUT_TAIL_BYTES = 8_192;
|
|
7
|
+
function tail(value, limit = AUDIT_OUTPUT_TAIL_BYTES) {
|
|
8
|
+
if (value.byteLength <= limit) {
|
|
9
|
+
return { tail: value.toString("utf8"), truncated: false, omittedBytes: 0 };
|
|
10
|
+
}
|
|
11
|
+
return {
|
|
12
|
+
tail: value.subarray(value.byteLength - limit).toString("utf8"),
|
|
13
|
+
truncated: true,
|
|
14
|
+
omittedBytes: value.byteLength - limit,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export function verificationExecutor(command) {
|
|
18
|
+
const basename = command.command.split(/[\\/]/).pop() ?? "";
|
|
19
|
+
const executor = basename.toLowerCase().replace(/\.exe$/, "");
|
|
20
|
+
if (executor !== "bash" && executor !== "zsh" && executor !== "pwsh") {
|
|
21
|
+
return { cause: "unsupported_executor" };
|
|
22
|
+
}
|
|
23
|
+
const bodyIndex = command.args.findIndex((arg) => arg === "-c" || arg === "-lc" || arg === "-Command");
|
|
24
|
+
const body = bodyIndex < 0 ? undefined : command.args[bodyIndex + 1];
|
|
25
|
+
if (body === undefined)
|
|
26
|
+
return { cause: "missing_command_body" };
|
|
27
|
+
return { executor, body };
|
|
28
|
+
}
|
|
29
|
+
export async function resolveVerificationCommands(cwd) {
|
|
30
|
+
const loaded = await loadKeiyakuSettings(cwd);
|
|
31
|
+
assertSettingsKnobUsable(loaded, "gate");
|
|
32
|
+
return loaded.knobs?.gate?.commands ?? [];
|
|
33
|
+
}
|
|
34
|
+
export async function runVerificationCommand(cwd, verification) {
|
|
35
|
+
const started = process.hrtime.bigint();
|
|
36
|
+
return await new Promise((resolve, reject) => {
|
|
37
|
+
const child = spawn(verification.command, verification.args, {
|
|
38
|
+
cwd,
|
|
39
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
40
|
+
env: getConfig().runtimeEnv,
|
|
41
|
+
});
|
|
42
|
+
const stdout = [];
|
|
43
|
+
const stderr = [];
|
|
44
|
+
child.stdout.on("data", (chunk) => stdout.push(Buffer.from(chunk)));
|
|
45
|
+
child.stderr.on("data", (chunk) => stderr.push(Buffer.from(chunk)));
|
|
46
|
+
child.on("error", reject);
|
|
47
|
+
child.on("close", (code) => {
|
|
48
|
+
const durationMs = Number((process.hrtime.bigint() - started) / 1000000n);
|
|
49
|
+
resolve({
|
|
50
|
+
code: typeof code === "number" ? code : -1,
|
|
51
|
+
durationMs,
|
|
52
|
+
stdout: tail(Buffer.concat(stdout)),
|
|
53
|
+
stderr: tail(Buffer.concat(stderr)),
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
}).catch((error) => {
|
|
57
|
+
throw new FlowError("CLOSE_QUALITY_GATE_FAILED", `verification '${verification.label}' could not start`, { cause: error });
|
|
58
|
+
});
|
|
59
|
+
}
|