@aviaratech/ai-delivery 0.3.19 → 0.3.20
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 +18 -1
- package/dist/cli.js +14 -0
- package/dist/cli.js.map +1 -1
- package/dist/config/deliveryConfig.d.ts +1 -0
- package/dist/config/deliveryConfig.js +3 -0
- package/dist/config/deliveryConfig.js.map +1 -1
- package/dist/config/deliveryConfig.test.js +106 -0
- package/dist/config/deliveryConfig.test.js.map +1 -1
- package/dist/github/client.d.ts +3 -2
- package/dist/github/client.js +35 -15
- package/dist/github/client.js.map +1 -1
- package/dist/issue.d.ts +1 -0
- package/dist/issue.js +5 -1
- package/dist/issue.js.map +1 -1
- package/dist/mcp/tools.d.ts +1 -0
- package/dist/mcp/tools.js +7 -0
- package/dist/mcp/tools.js.map +1 -1
- package/dist/pr.d.ts +4 -1
- package/dist/pr.js +20 -4
- package/dist/pr.js.map +1 -1
- package/dist/setup.d.ts +1 -0
- package/dist/setup.js +71 -25
- package/dist/setup.js.map +1 -1
- package/dist/setup.test.js +342 -4
- package/dist/setup.test.js.map +1 -1
- package/dist/verification.d.ts +8 -2
- package/dist/verification.js +205 -64
- package/dist/verification.js.map +1 -1
- package/package.json +1 -1
- package/plugins/ai-delivery/.claude-plugin/plugin.json +1 -1
package/dist/verification.js
CHANGED
|
@@ -792,9 +792,13 @@ function observeOwnedProcesses(state, snapshot) {
|
|
|
792
792
|
state.sampled = true;
|
|
793
793
|
return [...selected.values()];
|
|
794
794
|
}
|
|
795
|
-
function sampleOwnedTree(state, repoRoot, bounds, baseline, newOutputBytes, controllerRssBytes = 0) {
|
|
796
|
-
const
|
|
797
|
-
const
|
|
795
|
+
function sampleOwnedTree(state, repoRoot, bounds, baseline, newOutputBytes, controllerRssBytes = 0, includeControllerTree = false) {
|
|
796
|
+
const snapshot = processSnapshot();
|
|
797
|
+
const owned = observeOwnedProcesses(state, snapshot);
|
|
798
|
+
const controller = includeControllerTree
|
|
799
|
+
? controllerTreeRssBytes(snapshot, new Set(owned.map((member) => member.pid)))
|
|
800
|
+
: controllerRssBytes;
|
|
801
|
+
const rssBytes = owned.reduce((total, member) => total + member.rssBytes, controller);
|
|
798
802
|
if (!Number.isSafeInteger(rssBytes))
|
|
799
803
|
throw new DeliveryError('Owned aggregate RSS is outside the safe integer range.');
|
|
800
804
|
if (rssBytes > bounds.maxAggregateRssBytes) {
|
|
@@ -818,6 +822,24 @@ function sampleOwnedTree(state, repoRoot, bounds, baseline, newOutputBytes, cont
|
|
|
818
822
|
ownedProcessCount: owned.length,
|
|
819
823
|
};
|
|
820
824
|
}
|
|
825
|
+
/** Accounting includes the caller's tree; containment remains restricted to the recorded command. */
|
|
826
|
+
function controllerTreeRssBytes(snapshot = processSnapshot(), excluded = new Set()) {
|
|
827
|
+
const controller = snapshot.get(process.pid);
|
|
828
|
+
if (controller === undefined || controller.status.startsWith('Z'))
|
|
829
|
+
throw new DeliveryError('Runtime setup controller resource observation is unavailable.');
|
|
830
|
+
const selected = new Set([process.pid]);
|
|
831
|
+
let bytes = process.memoryUsage().rss;
|
|
832
|
+
for (const pid of selected)
|
|
833
|
+
for (const member of snapshot.values())
|
|
834
|
+
if (member.ppid === pid && !member.status.startsWith('Z') && !selected.has(member.pid)) {
|
|
835
|
+
selected.add(member.pid);
|
|
836
|
+
if (!excluded.has(member.pid))
|
|
837
|
+
bytes += member.rssBytes;
|
|
838
|
+
}
|
|
839
|
+
if (!Number.isSafeInteger(bytes))
|
|
840
|
+
throw new DeliveryError('Controller aggregate RSS observation overflowed.');
|
|
841
|
+
return bytes;
|
|
842
|
+
}
|
|
821
843
|
async function confirmOwnedCleanup(state) {
|
|
822
844
|
if (!state.sampled || state.rootIdentity === undefined) {
|
|
823
845
|
throw new DeliveryError('Owned process cleanup cannot be verified without a root identity.');
|
|
@@ -1316,7 +1338,7 @@ function runStageCommand(repoRoot, argv, abortSignal, onRunning, resourceBounds,
|
|
|
1316
1338
|
executable,
|
|
1317
1339
|
...args,
|
|
1318
1340
|
], {
|
|
1319
|
-
cwd: repoRoot,
|
|
1341
|
+
cwd: execution?.cwd ?? repoRoot,
|
|
1320
1342
|
detached: process.platform !== 'win32',
|
|
1321
1343
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
1322
1344
|
...(baseline?.gate === undefined && execution === undefined
|
|
@@ -1334,6 +1356,8 @@ function runStageCommand(repoRoot, argv, abortSignal, onRunning, resourceBounds,
|
|
|
1334
1356
|
? undefined
|
|
1335
1357
|
: { rootPid: child.pid, sampled: false, tracked: new Map() };
|
|
1336
1358
|
let outputBytes = 0;
|
|
1359
|
+
let retainedOutputBytes = 0;
|
|
1360
|
+
const outputLimit = execution?.maxCapturedOutputBytes ?? 8 * 1024 * 1024;
|
|
1337
1361
|
let sampledOutputBytes = 0;
|
|
1338
1362
|
let failure;
|
|
1339
1363
|
let cleanupFailure;
|
|
@@ -1438,11 +1462,19 @@ function runStageCommand(repoRoot, argv, abortSignal, onRunning, resourceBounds,
|
|
|
1438
1462
|
stop('source-phase captured output exceeded remaining cumulative allowance');
|
|
1439
1463
|
return;
|
|
1440
1464
|
}
|
|
1441
|
-
if (outputBytes >
|
|
1442
|
-
|
|
1465
|
+
if (outputBytes > outputLimit) {
|
|
1466
|
+
const remaining = outputLimit - retainedOutputBytes;
|
|
1467
|
+
if (remaining > 0) {
|
|
1468
|
+
chunks.push(Buffer.from(chunk.subarray(0, remaining)));
|
|
1469
|
+
retainedOutputBytes += Math.min(remaining, chunk.length);
|
|
1470
|
+
}
|
|
1471
|
+
stop(execution?.maxCapturedOutputBytes === undefined
|
|
1472
|
+
? 'captured output exceeded 8 MiB'
|
|
1473
|
+
: `captured output exceeded ${outputLimit} bytes`);
|
|
1443
1474
|
return;
|
|
1444
1475
|
}
|
|
1445
1476
|
chunks.push(chunk);
|
|
1477
|
+
retainedOutputBytes += chunk.length;
|
|
1446
1478
|
};
|
|
1447
1479
|
child.stdout.on('data', (chunk) => capture(stdout, chunk));
|
|
1448
1480
|
child.stderr.on('data', (chunk) => capture(stderr, chunk));
|
|
@@ -1474,7 +1506,7 @@ function runStageCommand(repoRoot, argv, abortSignal, onRunning, resourceBounds,
|
|
|
1474
1506
|
observeOwnedProcesses(owned, processSnapshot());
|
|
1475
1507
|
writer?.observed(owned);
|
|
1476
1508
|
if (resourceBounds !== undefined) {
|
|
1477
|
-
const result = sampleOwnedTree(owned, repoRoot, resourceBounds, baseline, undefined, execution === undefined ? 0 : process.memoryUsage().rss);
|
|
1509
|
+
const result = sampleOwnedTree(owned, repoRoot, resourceBounds, baseline, undefined, execution === undefined ? 0 : process.memoryUsage().rss, execution?.includeControllerTree);
|
|
1478
1510
|
onResourceSample?.(result);
|
|
1479
1511
|
onRunning?.(outputBytes, result);
|
|
1480
1512
|
}
|
|
@@ -1490,7 +1522,7 @@ function runStageCommand(repoRoot, argv, abortSignal, onRunning, resourceBounds,
|
|
|
1490
1522
|
if (failure !== undefined)
|
|
1491
1523
|
return;
|
|
1492
1524
|
sampledOutputBytes = bytes;
|
|
1493
|
-
const result = sampleOwnedTree(owned, repoRoot, resourceBounds, baseline, bytes + (execution === undefined ? 0 : outputBytes), execution === undefined ? 0 : process.memoryUsage().rss);
|
|
1525
|
+
const result = sampleOwnedTree(owned, repoRoot, resourceBounds, baseline, bytes + (execution === undefined ? 0 : outputBytes), execution === undefined ? 0 : process.memoryUsage().rss, execution?.includeControllerTree);
|
|
1494
1526
|
onResourceSample?.(result);
|
|
1495
1527
|
onRunning?.(outputBytes, result);
|
|
1496
1528
|
}
|
|
@@ -1533,7 +1565,7 @@ function runStageCommand(repoRoot, argv, abortSignal, onRunning, resourceBounds,
|
|
|
1533
1565
|
const sample = sampleOwnedTree(owned, repoRoot, resourceBounds, baseline, baseline === undefined
|
|
1534
1566
|
? undefined
|
|
1535
1567
|
: (await positiveNewOutputBytes(baseline, scanCancellation.signal)) +
|
|
1536
|
-
(execution === undefined ? 0 : outputBytes), execution === undefined ? 0 : process.memoryUsage().rss);
|
|
1568
|
+
(execution === undefined ? 0 : outputBytes), execution === undefined ? 0 : process.memoryUsage().rss, execution?.includeControllerTree);
|
|
1537
1569
|
onResourceSample?.(sample);
|
|
1538
1570
|
onRunning?.(outputBytes, sample);
|
|
1539
1571
|
}
|
|
@@ -2894,63 +2926,172 @@ export async function withIssueSourcePhase(supplied, operation) {
|
|
|
2894
2926
|
return sourceReceipt(record);
|
|
2895
2927
|
}
|
|
2896
2928
|
/** @internal Runtime setup shares the existing writer, ownership handshake and bounded command runner. */
|
|
2897
|
-
export async function withRuntimeSetupWriter(root, operation) {
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2929
|
+
export async function withRuntimeSetupWriter(root, operation, scope) {
|
|
2930
|
+
const cancellation = new AbortController();
|
|
2931
|
+
const signal = scope?.signal === undefined ? cancellation.signal : AbortSignal.any([scope.signal, cancellation.signal]);
|
|
2932
|
+
let failure;
|
|
2933
|
+
let baseline;
|
|
2934
|
+
let captured = 0;
|
|
2935
|
+
const started = Date.now();
|
|
2936
|
+
const guard = () => {
|
|
2937
|
+
if (failure !== undefined)
|
|
2938
|
+
throw failure;
|
|
2939
|
+
if (signal.aborted)
|
|
2940
|
+
throw new DeliveryError('Runtime setup cancelled.');
|
|
2941
|
+
const rss = scope === undefined ? 0 : controllerTreeRssBytes();
|
|
2942
|
+
if (scope !== undefined) {
|
|
2943
|
+
if (rss > scope.bounds.maxAggregateRssBytes)
|
|
2944
|
+
throw new DeliveryError(`Controller aggregate RSS ${rss} exceeded limit ${scope.bounds.maxAggregateRssBytes}.`);
|
|
2945
|
+
assertDiskHeadroom(root, scope.bounds, baseline);
|
|
2946
|
+
}
|
|
2947
|
+
return rss;
|
|
2948
|
+
};
|
|
2949
|
+
if (scope !== undefined) {
|
|
2950
|
+
assertResourceBounds(scope.bounds);
|
|
2951
|
+
if (!Number.isSafeInteger(scope.maxCapturedOutputBytes) ||
|
|
2952
|
+
scope.maxCapturedOutputBytes <= 0 ||
|
|
2953
|
+
scope.maxCapturedOutputBytes > 8 * 1024 ** 2)
|
|
2954
|
+
throw new DeliveryError('Invalid runtime setup captured-output bound; maximum is 8 MiB.');
|
|
2955
|
+
guard();
|
|
2956
|
+
}
|
|
2957
|
+
root = gitRoot(root);
|
|
2958
|
+
return withVerificationWriter(root, async (writer) => withOutputObservationGate(root, scope !== undefined, async (scopeGate) => {
|
|
2959
|
+
if (scope !== undefined) {
|
|
2960
|
+
const paths = [...(scope.bounds.outputRoots ?? []), join(gitCommonDir(root), 'ai-delivery')]
|
|
2961
|
+
.map((path) => resolve(root, path))
|
|
2962
|
+
.sort();
|
|
2963
|
+
const roots = paths.filter((path, index) => !paths.slice(0, index).some((prior) => path === prior || path.startsWith(`${prior}${sep}`)));
|
|
2964
|
+
const links = new Map();
|
|
2965
|
+
const files = await withOutputGateLock(scopeGate, 'scan', () => scanOutputRoots(roots, links), signal);
|
|
2966
|
+
baseline = {
|
|
2967
|
+
roots,
|
|
2968
|
+
links,
|
|
2969
|
+
files,
|
|
2970
|
+
baselineId: digestValue([...files]),
|
|
2971
|
+
...(scopeGate === undefined ? {} : { gate: scopeGate }),
|
|
2972
|
+
};
|
|
2973
|
+
}
|
|
2974
|
+
const checkResources = async () => {
|
|
2975
|
+
const rss = guard();
|
|
2976
|
+
if (scope === undefined || baseline === undefined)
|
|
2977
|
+
return;
|
|
2978
|
+
const newOutputBytes = captured + (await positiveNewOutputBytes(baseline, signal));
|
|
2979
|
+
guard();
|
|
2980
|
+
if (newOutputBytes > scope.bounds.maxNewOutputBytes)
|
|
2981
|
+
throw new DeliveryError(`Runtime setup output ${newOutputBytes} exceeded limit ${scope.bounds.maxNewOutputBytes}.`);
|
|
2982
|
+
reportVerificationProgress({
|
|
2983
|
+
state: 'running',
|
|
2984
|
+
stageId: 'runtime-setup',
|
|
2985
|
+
completedStages: 0,
|
|
2986
|
+
reusedStages: 0,
|
|
2987
|
+
remainingStages: 1,
|
|
2988
|
+
elapsedMs: Date.now() - started,
|
|
2989
|
+
capturedOutputBytes: captured,
|
|
2990
|
+
sampledAggregateRssBytes: rss,
|
|
2991
|
+
sampledFreeDiskBytes: freeDiskBytes(root, baseline),
|
|
2992
|
+
sampledNewOutputBytes: newOutputBytes,
|
|
2993
|
+
});
|
|
2994
|
+
};
|
|
2995
|
+
await checkResources();
|
|
2996
|
+
const timer = scope === undefined
|
|
2997
|
+
? undefined
|
|
2998
|
+
: setInterval(() => {
|
|
2999
|
+
try {
|
|
3000
|
+
guard();
|
|
3001
|
+
}
|
|
3002
|
+
catch (error) {
|
|
3003
|
+
failure ??= error instanceof Error ? error : new DeliveryError(String(error));
|
|
3004
|
+
cancellation.abort();
|
|
3005
|
+
}
|
|
3006
|
+
}, 1_000);
|
|
3007
|
+
timer?.unref();
|
|
3008
|
+
try {
|
|
3009
|
+
const result = await operation({
|
|
3010
|
+
assertQuiescent: () => writer.assertQuiescent(),
|
|
3011
|
+
checkResources,
|
|
3012
|
+
signal,
|
|
3013
|
+
run: async (argv, bounds, commandSignal, environment, cwd) => {
|
|
3014
|
+
await checkResources();
|
|
3015
|
+
assertResourceBounds(bounds);
|
|
3016
|
+
const roots = bounds.outputRoots?.map((path) => resolve(root, path)).sort();
|
|
3017
|
+
if (roots !== undefined) {
|
|
3018
|
+
for (let i = 1; i < roots.length; i++)
|
|
3019
|
+
if (roots[i] === roots[i - 1] || roots[i].startsWith(`${roots[i - 1]}${sep}`))
|
|
3020
|
+
throw new DeliveryError('Filesystem output roots must not overlap.');
|
|
3021
|
+
}
|
|
3022
|
+
const run = async (gate) => {
|
|
3023
|
+
const links = new Map();
|
|
3024
|
+
const files = roots === undefined
|
|
3025
|
+
? undefined
|
|
3026
|
+
: await withOutputGateLock(gate, 'scan', () => scanOutputRoots(roots, links), signal);
|
|
3027
|
+
const commandBaseline = roots === undefined || files === undefined
|
|
3028
|
+
? undefined
|
|
3029
|
+
: {
|
|
3030
|
+
roots,
|
|
3031
|
+
files,
|
|
3032
|
+
links,
|
|
3033
|
+
baselineId: digestValue([...files]),
|
|
3034
|
+
...(gate === undefined ? {} : { gate }),
|
|
3035
|
+
};
|
|
3036
|
+
assertDiskHeadroom(root, bounds, commandBaseline);
|
|
3037
|
+
let commandCaptured = 0;
|
|
3038
|
+
const output = await runStageCommand(root, argv, commandSignal === undefined ? signal : AbortSignal.any([signal, commandSignal]), (capturedOutputBytes, sample, reason) => reportVerificationProgress({
|
|
3039
|
+
state: 'running',
|
|
3040
|
+
stageId: 'runtime-setup:install',
|
|
3041
|
+
completedStages: 0,
|
|
3042
|
+
reusedStages: 0,
|
|
3043
|
+
remainingStages: 1,
|
|
3044
|
+
elapsedMs: Date.now() - started,
|
|
3045
|
+
capturedOutputBytes,
|
|
3046
|
+
...(reason === undefined ? {} : { reason }),
|
|
3047
|
+
...(sample === undefined
|
|
3048
|
+
? {}
|
|
3049
|
+
: {
|
|
3050
|
+
sampledAggregateRssBytes: sample.aggregateRssBytes,
|
|
3051
|
+
sampledFreeDiskBytes: sample.freeDiskBytes,
|
|
3052
|
+
sampledNewOutputBytes: sample.newOutputBytes,
|
|
3053
|
+
}),
|
|
3054
|
+
}), bounds, commandBaseline, undefined, writer, scope === undefined && environment === undefined && cwd === undefined
|
|
3055
|
+
? undefined
|
|
3056
|
+
: {
|
|
3057
|
+
environment: environment ?? process.env,
|
|
3058
|
+
...(cwd === undefined ? {} : { cwd }),
|
|
3059
|
+
...(scope === undefined
|
|
3060
|
+
? {}
|
|
3061
|
+
: { maxCapturedOutputBytes: scope.maxCapturedOutputBytes, includeControllerTree: true }),
|
|
3062
|
+
onCaptured: (bytes) => {
|
|
3063
|
+
captured += bytes - commandCaptured;
|
|
3064
|
+
commandCaptured = bytes;
|
|
3065
|
+
},
|
|
3066
|
+
});
|
|
3067
|
+
await checkResources();
|
|
3068
|
+
return output;
|
|
2921
3069
|
};
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
const output = await runStageCommand(root, argv, signal, (capturedOutputBytes, sample, reason) => reportVerificationProgress({
|
|
2925
|
-
state: 'running',
|
|
2926
|
-
stageId: 'runtime-setup',
|
|
2927
|
-
completedStages: 0,
|
|
2928
|
-
reusedStages: 0,
|
|
2929
|
-
remainingStages: 1,
|
|
2930
|
-
elapsedMs: Date.now() - started,
|
|
2931
|
-
capturedOutputBytes,
|
|
2932
|
-
...(reason === undefined ? {} : { reason }),
|
|
2933
|
-
...(sample === undefined
|
|
2934
|
-
? {}
|
|
2935
|
-
: {
|
|
2936
|
-
sampledAggregateRssBytes: sample.aggregateRssBytes,
|
|
2937
|
-
sampledFreeDiskBytes: sample.freeDiskBytes,
|
|
2938
|
-
sampledNewOutputBytes: sample.newOutputBytes,
|
|
2939
|
-
}),
|
|
2940
|
-
}), bounds, baseline, undefined, writer);
|
|
2941
|
-
if (signal?.aborted)
|
|
2942
|
-
throw new DeliveryError('Runtime setup cancelled.');
|
|
2943
|
-
reportVerificationProgress({
|
|
2944
|
-
state: 'completed',
|
|
2945
|
-
stageId: 'runtime-setup',
|
|
2946
|
-
completedStages: 1,
|
|
2947
|
-
reusedStages: 0,
|
|
2948
|
-
remainingStages: 0,
|
|
2949
|
-
elapsedMs: Date.now() - started,
|
|
2950
|
-
});
|
|
2951
|
-
return output;
|
|
3070
|
+
return scope === undefined ? withOutputObservationGate(root, roots !== undefined, run) : run(scopeGate);
|
|
3071
|
+
},
|
|
2952
3072
|
});
|
|
2953
|
-
|
|
3073
|
+
await checkResources();
|
|
3074
|
+
reportVerificationProgress({
|
|
3075
|
+
state: 'completed',
|
|
3076
|
+
stageId: 'runtime-setup',
|
|
3077
|
+
completedStages: 1,
|
|
3078
|
+
reusedStages: 0,
|
|
3079
|
+
remainingStages: 0,
|
|
3080
|
+
elapsedMs: Date.now() - started,
|
|
3081
|
+
});
|
|
3082
|
+
return result;
|
|
3083
|
+
}
|
|
3084
|
+
catch (error) {
|
|
3085
|
+
if (failure !== undefined)
|
|
3086
|
+
throw new DeliveryError(`${failure.message} Runtime setup operation failed (${error instanceof Error ? error.message : String(error)}).`);
|
|
3087
|
+
if (signal.aborted)
|
|
3088
|
+
throw new DeliveryError(`Runtime setup cancelled. ${error instanceof Error ? error.message : String(error)}`);
|
|
3089
|
+
throw error;
|
|
3090
|
+
}
|
|
3091
|
+
finally {
|
|
3092
|
+
if (timer !== undefined)
|
|
3093
|
+
clearInterval(timer);
|
|
3094
|
+
}
|
|
2954
3095
|
}));
|
|
2955
3096
|
}
|
|
2956
3097
|
//# sourceMappingURL=verification.js.map
|