agentic-workflow-manager 9.21.0 → 9.22.1
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/dist/src/commands/change/index.js +16 -5
- package/dist/src/commands/init.js +8 -4
- package/dist/src/commands/preflight/checks.js +22 -0
- package/dist/src/commands/registry/add.js +9 -3
- package/dist/src/commands/sensors/compatibility/contract.js +56 -3
- package/dist/src/commands/sensors/compatibility/manifest.js +16 -2
- package/dist/src/commands/sensors/index.js +10 -1
- package/dist/src/commands/sensors/inputs-digest.js +315 -0
- package/dist/src/commands/sensors/prepare.js +10 -0
- package/dist/src/commands/sensors/result-cache.js +278 -0
- package/dist/src/commands/sensors/result.js +31 -12
- package/dist/src/commands/sensors/run.js +265 -8
- package/dist/src/commands/sensors/status.js +66 -7
- package/dist/src/commands/sync.js +11 -5
- package/dist/src/commands/update.js +23 -12
- package/dist/src/core/changes/derive.js +6 -3
- package/dist/src/core/changes/git.js +1 -2
- package/dist/src/core/changes/receipt.js +219 -9
- package/dist/src/core/changes/worktree.js +15 -3
- package/dist/src/core/init/steps.js +3 -8
- package/dist/src/core/registries.js +20 -15
- package/dist/src/core/registry-git-errors.js +107 -0
- package/dist/src/core/registry-git.js +93 -0
- package/dist/src/core/registry-sync-report.js +74 -0
- package/dist/src/core/versioning.js +3 -6
- package/dist/src/index.js +5 -8
- package/dist/tests/commands/change/new.test.js +104 -58
- package/dist/tests/commands/change/validate-unmeasured.test.js +246 -0
- package/dist/tests/commands/change/validate.test.js +164 -108
- package/dist/tests/commands/changes/list.test.js +41 -22
- package/dist/tests/commands/job/gate-reconcile.test.js +13 -2
- package/dist/tests/commands/preflight/sensors-inputs.test.js +110 -0
- package/dist/tests/commands/registry-sync-honesty.test.js +175 -0
- package/dist/tests/commands/sensors/compatibility/contract.test.js +60 -0
- package/dist/tests/commands/sensors/compatibility/manifest.test.js +94 -0
- package/dist/tests/commands/sensors/exec.test.js +3 -1
- package/dist/tests/commands/sensors/inputs-digest.test.js +373 -0
- package/dist/tests/commands/sensors/result-cache.test.js +352 -0
- package/dist/tests/commands/sensors/run-cache.test.js +434 -0
- package/dist/tests/commands/sensors/run-slice.test.js +171 -0
- package/dist/tests/commands/sensors/status-inputs.test.js +271 -0
- package/dist/tests/commands/update.test.js +8 -8
- package/dist/tests/commands/watch/supervisor-loop.test.js +21 -5
- package/dist/tests/core/changes/helpers.js +15 -3
- package/dist/tests/core/changes/list-paging.test.js +131 -110
- package/dist/tests/core/changes/list.test.js +133 -83
- package/dist/tests/core/changes/receipt.test.js +279 -8
- package/dist/tests/core/diagnostics/provider-checks.test.js +3 -0
- package/dist/tests/core/init/steps-registry-sync.test.js +6 -8
- package/dist/tests/core/registry-git.test.js +153 -0
- package/dist/tests/helpers/commit-seeded-registry.js +18 -0
- package/dist/tests/integration/codex-provider-isolated.test.js +7 -2
- package/dist/tests/integration/copilot-init-isolated.test.js +7 -4
- package/dist/tests/structural/active-documentation.test.js +0 -41
- package/dist/tests/structural/bundle-skill-reference-contract.test.js +0 -12
- package/dist/tests/structural/cli-reference-matches-commander.test.js +94 -0
- package/dist/tests/structural/native-release-artifacts.test.js +116 -107
- package/dist/tests/structural/sensor-documentation-contract.test.js +0 -47
- package/dist/tests/structural/sensor-portability-contract.test.js +0 -7
- package/dist/tests/structural/simple-git-v4-pin.test.js +49 -0
- package/package.json +3 -2
- package/prebuilds/darwin-arm64/secure_fs.node +0 -0
- package/prebuilds/darwin-x64/secure_fs.node +0 -0
- package/prebuilds/win32-arm64/secure_fs.node +0 -0
- package/prebuilds/win32-x64/secure_fs.node +0 -0
- package/dist/tests/structural/cloud-admission-docs-contract.test.js +0 -54
- package/dist/tests/structural/documentation-phase-is-mechanized.test.js +0 -29
- package/dist/tests/structural/operator-environments-docs-contract.test.js +0 -70
- package/dist/tests/structural/process-model-description-contract.test.js +0 -34
- package/dist/tests/structural/project-declared-sensors-docs-contract.test.js +0 -104
- package/dist/tests/structural/r4-plan-currentness-docs.test.js +0 -37
|
@@ -5,6 +5,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.isFullSelection = isFullSelection;
|
|
7
7
|
exports.sensorPairs = sensorPairs;
|
|
8
|
+
exports.allowsUnmeasuredRelaxation = allowsUnmeasuredRelaxation;
|
|
9
|
+
exports.measuredPathSet = measuredPathSet;
|
|
10
|
+
exports.filterLateFilesForReceipt = filterLateFilesForReceipt;
|
|
8
11
|
exports.computeReceiptDigest = computeReceiptDigest;
|
|
9
12
|
exports.buildReceipt = buildReceipt;
|
|
10
13
|
exports.writeReceipt = writeReceipt;
|
|
@@ -22,14 +25,20 @@ const paths_1 = require("../paths");
|
|
|
22
25
|
const types_1 = require("./types");
|
|
23
26
|
const text_safety_1 = require("./text-safety");
|
|
24
27
|
const worktree_1 = require("./worktree");
|
|
25
|
-
const
|
|
28
|
+
const inputs_digest_1 = require("../../commands/sensors/inputs-digest");
|
|
29
|
+
const contract_1 = require("../../commands/sensors/compatibility/contract");
|
|
30
|
+
const RECEIPT_KEYS_V1 = ['schema', 'commit', 'ranAt', 'cliVersion', 'overall', 'sensors', 'digest'];
|
|
31
|
+
const RECEIPT_KEYS_V2 = [...RECEIPT_KEYS_V1, 'measurements', 'alwaysMeasuredPaths'];
|
|
26
32
|
const MAX_RECEIPT_BYTES = 1024 * 1024;
|
|
27
|
-
|
|
33
|
+
const MAX_MEASURED_PATHS = 50_000;
|
|
34
|
+
/** True when the run covers every sensor: `--changed`/`--slice` never do, `--all` always does, `--fast` xor `--slow` is partial. */
|
|
28
35
|
function isFullSelection(opts) {
|
|
29
36
|
if (opts === null || typeof opts !== 'object')
|
|
30
37
|
return false;
|
|
31
38
|
if (opts.changed)
|
|
32
39
|
return false;
|
|
40
|
+
if (opts.slice)
|
|
41
|
+
return false;
|
|
33
42
|
if (opts.all)
|
|
34
43
|
return true;
|
|
35
44
|
return Boolean(opts.fast) === Boolean(opts.slow);
|
|
@@ -46,6 +55,108 @@ function sensorPairs(sensors) {
|
|
|
46
55
|
function sortedSensors(sensors) {
|
|
47
56
|
return Object.fromEntries(sensorPairs(sensors));
|
|
48
57
|
}
|
|
58
|
+
function sortedStrings(values) {
|
|
59
|
+
return [...values].sort(byName);
|
|
60
|
+
}
|
|
61
|
+
function isSha256Digest(v) {
|
|
62
|
+
return typeof v === 'string' && /^sha256:[0-9a-f]{64}$/.test(v);
|
|
63
|
+
}
|
|
64
|
+
function isPackageRootRel(v) {
|
|
65
|
+
return typeof v === 'string' && (v === '' || (0, inputs_digest_1.isSafeRepoRel)(v));
|
|
66
|
+
}
|
|
67
|
+
function measurementProblem(name, value) {
|
|
68
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value))
|
|
69
|
+
return `measurement for ${name} must be an object`;
|
|
70
|
+
const m = value;
|
|
71
|
+
const keys = Object.keys(m);
|
|
72
|
+
if (keys.length !== 5 || !['inputs', 'packageRootRel', 'inputDigest', 'sensorFingerprint', 'measuredPaths'].every((k) => k in m)) {
|
|
73
|
+
return `measurement for ${name} must have exactly inputs, packageRootRel, inputDigest, sensorFingerprint, measuredPaths`;
|
|
74
|
+
}
|
|
75
|
+
if (!Array.isArray(m.inputs) || m.inputs.length === 0) {
|
|
76
|
+
return `measurement inputs for ${name} must be a nonempty array of single-line strings`;
|
|
77
|
+
}
|
|
78
|
+
try {
|
|
79
|
+
// RF-1.5: reuse the same glob grammar as authored sensor inputs (reject .., !, braces, extglobs).
|
|
80
|
+
(0, contract_1.parseSensorInputs)(m.inputs, 'receipt', `measurement[${name}].inputs`);
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return `measurement inputs for ${name} must obey RF-1.5 sensor input glob rules`;
|
|
84
|
+
}
|
|
85
|
+
if (!isPackageRootRel(m.packageRootRel))
|
|
86
|
+
return `measurement packageRootRel for ${name} must be '' or a safe repo-root POSIX path`;
|
|
87
|
+
if (!isSha256Digest(m.inputDigest))
|
|
88
|
+
return `measurement inputDigest for ${name} must be sha256:<hex>`;
|
|
89
|
+
if (!isSha256Digest(m.sensorFingerprint))
|
|
90
|
+
return `measurement sensorFingerprint for ${name} must be sha256:<hex>`;
|
|
91
|
+
if (!Array.isArray(m.measuredPaths) || m.measuredPaths.length > MAX_MEASURED_PATHS) {
|
|
92
|
+
return `measurement measuredPaths for ${name} must be an array within bounds`;
|
|
93
|
+
}
|
|
94
|
+
if (m.measuredPaths.some((p) => typeof p !== 'string' || !(0, inputs_digest_1.isSafeRepoRel)(p))) {
|
|
95
|
+
return `measurement measuredPaths for ${name} must be safe repo-root POSIX paths`;
|
|
96
|
+
}
|
|
97
|
+
const sorted = sortedStrings(m.measuredPaths);
|
|
98
|
+
if (JSON.stringify(m.measuredPaths) !== JSON.stringify(sorted)) {
|
|
99
|
+
return `measurement measuredPaths for ${name} must be sorted`;
|
|
100
|
+
}
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* RF-4.6: schema-2 unmeasured-path relaxation applies only when every sensor on the
|
|
105
|
+
* receipt had effective authored inputs (a nonempty measurement.inputs entry).
|
|
106
|
+
*/
|
|
107
|
+
function allowsUnmeasuredRelaxation(receipt) {
|
|
108
|
+
if (receipt === null || typeof receipt !== 'object')
|
|
109
|
+
return false;
|
|
110
|
+
if (receipt.schema !== 2)
|
|
111
|
+
return false;
|
|
112
|
+
const names = Object.keys(receipt.sensors);
|
|
113
|
+
if (names.length === 0)
|
|
114
|
+
return false;
|
|
115
|
+
for (const name of names) {
|
|
116
|
+
const m = receipt.measurements[name];
|
|
117
|
+
if (!m || !Array.isArray(m.inputs) || m.inputs.length === 0)
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
/** Repo-root POSIX paths that count as measured for RF-4.5 late-path checks. */
|
|
123
|
+
function measuredPathSet(receipt) {
|
|
124
|
+
const out = new Set();
|
|
125
|
+
for (const p of receipt.alwaysMeasuredPaths)
|
|
126
|
+
out.add(p);
|
|
127
|
+
for (const m of Object.values(receipt.measurements)) {
|
|
128
|
+
for (const p of m.measuredPaths)
|
|
129
|
+
out.add(p);
|
|
130
|
+
}
|
|
131
|
+
return out;
|
|
132
|
+
}
|
|
133
|
+
function isRuntimeStatePath(rel) {
|
|
134
|
+
return worktree_1.RUNTIME_STATE_EXCLUDES.some((ex) => rel === ex || rel.startsWith(`${ex}/`));
|
|
135
|
+
}
|
|
136
|
+
/** True when a late path is measured: frozen set OR rematch against any measurement's inputs (RF-4.5). */
|
|
137
|
+
function latePathIsMeasured(repoRel, receipt, frozen) {
|
|
138
|
+
if (frozen.has(repoRel))
|
|
139
|
+
return true;
|
|
140
|
+
for (const m of Object.values(receipt.measurements)) {
|
|
141
|
+
if ((0, inputs_digest_1.repoPathMatchesInputGlobs)(repoRel, m.packageRootRel, m.inputs))
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Filter late files for Change Record validate (RF-4.5/4.6). Schema 1 and incomplete
|
|
148
|
+
* schema 2 keep today's strict rule (only `docs/changes/` carved out). Complete schema 2
|
|
149
|
+
* keeps paths in the frozen measured ∪ always-measured union OR matching authored inputs
|
|
150
|
+
* globs (so new matching sources after verify are not a false green). Runtime-state never counts.
|
|
151
|
+
*/
|
|
152
|
+
function filterLateFilesForReceipt(files, receipt, recordDirPrefix) {
|
|
153
|
+
const outsideRecords = files.filter((f) => !f.startsWith(`${recordDirPrefix}/`));
|
|
154
|
+
if (receipt === null || receipt.schema !== 2 || !allowsUnmeasuredRelaxation(receipt)) {
|
|
155
|
+
return outsideRecords;
|
|
156
|
+
}
|
|
157
|
+
const measured = measuredPathSet(receipt);
|
|
158
|
+
return outsideRecords.filter((f) => !isRuntimeStatePath(f) && latePathIsMeasured(f, receipt, measured));
|
|
159
|
+
}
|
|
49
160
|
/**
|
|
50
161
|
* sha256 over the canonical JSON of the receipt without its digest, `sensors` being the name-sorted pairs of sensorPairs (R2.2c).
|
|
51
162
|
* The digest is local (receipts are not versioned and are rewritten by every full run), so changing its canonical form only
|
|
@@ -56,9 +167,33 @@ function computeReceiptDigest(body) {
|
|
|
56
167
|
throw new Error((0, text_safety_1.esc)('invalid receipt: digest body must be an object'));
|
|
57
168
|
if (body.sensors === null || typeof body.sensors !== 'object' || Array.isArray(body.sensors))
|
|
58
169
|
throw new Error((0, text_safety_1.esc)('invalid receipt: digest body sensors must be an object'));
|
|
170
|
+
if (body.schema === 1) {
|
|
171
|
+
const canonical = JSON.stringify({
|
|
172
|
+
schema: body.schema, commit: body.commit, ranAt: body.ranAt, cliVersion: body.cliVersion,
|
|
173
|
+
overall: body.overall, sensors: sensorPairs(body.sensors),
|
|
174
|
+
});
|
|
175
|
+
return 'sha256:' + crypto_1.default.createHash('sha256').update(canonical).digest('hex');
|
|
176
|
+
}
|
|
177
|
+
if (body.schema !== 2)
|
|
178
|
+
throw new Error((0, text_safety_1.esc)('invalid receipt: digest body schema must be 1 or 2'));
|
|
179
|
+
const v2 = body;
|
|
180
|
+
if (v2.measurements === null || typeof v2.measurements !== 'object' || Array.isArray(v2.measurements)) {
|
|
181
|
+
throw new Error((0, text_safety_1.esc)('invalid receipt: digest body measurements must be an object'));
|
|
182
|
+
}
|
|
183
|
+
if (!Array.isArray(v2.alwaysMeasuredPaths))
|
|
184
|
+
throw new Error((0, text_safety_1.esc)('invalid receipt: digest body alwaysMeasuredPaths must be an array'));
|
|
185
|
+
const measurements = Object.fromEntries(Object.entries(v2.measurements).sort(([a], [b]) => byName(a, b)).map(([name, m]) => [name, {
|
|
186
|
+
inputs: m.inputs,
|
|
187
|
+
packageRootRel: m.packageRootRel,
|
|
188
|
+
inputDigest: m.inputDigest,
|
|
189
|
+
sensorFingerprint: m.sensorFingerprint,
|
|
190
|
+
measuredPaths: m.measuredPaths,
|
|
191
|
+
}]));
|
|
59
192
|
const canonical = JSON.stringify({
|
|
60
|
-
schema:
|
|
61
|
-
overall:
|
|
193
|
+
schema: v2.schema, commit: v2.commit, ranAt: v2.ranAt, cliVersion: v2.cliVersion,
|
|
194
|
+
overall: v2.overall, sensors: sensorPairs(v2.sensors),
|
|
195
|
+
measurements,
|
|
196
|
+
alwaysMeasuredPaths: v2.alwaysMeasuredPaths,
|
|
62
197
|
});
|
|
63
198
|
return 'sha256:' + crypto_1.default.createHash('sha256').update(canonical).digest('hex');
|
|
64
199
|
}
|
|
@@ -70,11 +205,12 @@ function receiptProblem(x) {
|
|
|
70
205
|
if (x === null || typeof x !== 'object' || Array.isArray(x))
|
|
71
206
|
return 'receipt must be an object';
|
|
72
207
|
const r = x;
|
|
208
|
+
if (r.schema !== 1 && r.schema !== 2)
|
|
209
|
+
return 'schema must be 1 or 2';
|
|
210
|
+
const expectedKeys = r.schema === 1 ? RECEIPT_KEYS_V1 : RECEIPT_KEYS_V2;
|
|
73
211
|
const keys = Object.keys(r);
|
|
74
|
-
if (keys.length !==
|
|
212
|
+
if (keys.length !== expectedKeys.length || !expectedKeys.every((k) => k in r))
|
|
75
213
|
return 'receipt must have exactly the schema keys';
|
|
76
|
-
if (r.schema !== 1)
|
|
77
|
-
return 'schema must be 1';
|
|
78
214
|
if (!(0, types_1.isCommitId)(r.commit))
|
|
79
215
|
return 'commit must be 40 or 64 lowercase hex characters';
|
|
80
216
|
if (!(0, text_safety_1.isCanonicalIso)(r.ranAt))
|
|
@@ -92,6 +228,28 @@ function receiptProblem(x) {
|
|
|
92
228
|
if (!(0, types_1.isSensorStatus)(status))
|
|
93
229
|
return 'a sensor status is not a known status';
|
|
94
230
|
}
|
|
231
|
+
if (r.schema === 2) {
|
|
232
|
+
const measurements = r.measurements;
|
|
233
|
+
if (measurements === null || typeof measurements !== 'object' || Array.isArray(measurements))
|
|
234
|
+
return 'measurements must be an object';
|
|
235
|
+
for (const [name, value] of Object.entries(measurements)) {
|
|
236
|
+
if (!(0, types_1.isSensorName)(name))
|
|
237
|
+
return `measurement sensor names must be a single line of 1 to ${types_1.MAX_SENSOR_NAME} characters`;
|
|
238
|
+
const problem = measurementProblem(name, value);
|
|
239
|
+
if (problem !== null)
|
|
240
|
+
return problem;
|
|
241
|
+
}
|
|
242
|
+
if (!Array.isArray(r.alwaysMeasuredPaths) || r.alwaysMeasuredPaths.length > MAX_MEASURED_PATHS) {
|
|
243
|
+
return 'alwaysMeasuredPaths must be an array within bounds';
|
|
244
|
+
}
|
|
245
|
+
if (r.alwaysMeasuredPaths.some((p) => typeof p !== 'string' || !(0, inputs_digest_1.isSafeRepoRel)(p))) {
|
|
246
|
+
return 'alwaysMeasuredPaths must be safe repo-root POSIX paths';
|
|
247
|
+
}
|
|
248
|
+
const sortedAlways = sortedStrings(r.alwaysMeasuredPaths);
|
|
249
|
+
if (JSON.stringify(r.alwaysMeasuredPaths) !== JSON.stringify(sortedAlways)) {
|
|
250
|
+
return 'alwaysMeasuredPaths must be sorted';
|
|
251
|
+
}
|
|
252
|
+
}
|
|
95
253
|
if (typeof r.digest !== 'string')
|
|
96
254
|
return 'digest must be a string';
|
|
97
255
|
const { digest, ...body } = r;
|
|
@@ -99,14 +257,66 @@ function receiptProblem(x) {
|
|
|
99
257
|
return 'digest does not match the content';
|
|
100
258
|
return null;
|
|
101
259
|
}
|
|
102
|
-
|
|
260
|
+
function extractMeasurements(sensors) {
|
|
261
|
+
const out = {};
|
|
262
|
+
for (const sensor of sensors) {
|
|
263
|
+
const m = sensor.measurement;
|
|
264
|
+
if (!m || typeof m !== 'object')
|
|
265
|
+
continue;
|
|
266
|
+
if (!Array.isArray(m.inputs) || m.inputs.length === 0)
|
|
267
|
+
continue;
|
|
268
|
+
if (!isPackageRootRel(m.packageRootRel))
|
|
269
|
+
continue;
|
|
270
|
+
if (!isSha256Digest(m.inputDigest) || !isSha256Digest(m.sensorFingerprint))
|
|
271
|
+
continue;
|
|
272
|
+
if (!Array.isArray(m.measuredPaths) || m.measuredPaths.some((p) => typeof p !== 'string' || !(0, inputs_digest_1.isSafeRepoRel)(p)))
|
|
273
|
+
continue;
|
|
274
|
+
out[sensor.name] = {
|
|
275
|
+
inputs: [...m.inputs],
|
|
276
|
+
packageRootRel: m.packageRootRel,
|
|
277
|
+
inputDigest: m.inputDigest,
|
|
278
|
+
sensorFingerprint: m.sensorFingerprint,
|
|
279
|
+
measuredPaths: sortedStrings(m.measuredPaths),
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
return Object.fromEntries(Object.entries(out).sort(([a], [b]) => byName(a, b)));
|
|
283
|
+
}
|
|
284
|
+
/** Builds a schema-2 receipt (RF-4.3). Schema 1 remains readable via `readReceipt` / `writeReceipt`. */
|
|
103
285
|
function buildReceipt(input) {
|
|
104
286
|
const out = input === null || typeof input !== 'object' ? undefined : input.output;
|
|
105
287
|
if (out === null || typeof out !== 'object' || !Array.isArray(out.sensors) || out.sensors.some((s) => s === null || typeof s !== 'object')) {
|
|
106
288
|
throw new Error('invalid receipt: run output must carry a sensors array');
|
|
107
289
|
}
|
|
108
290
|
const sensors = sortedSensors(Object.fromEntries(out.sensors.map((s) => [s.name, s.status])));
|
|
109
|
-
const
|
|
291
|
+
const measurements = extractMeasurements(out.sensors);
|
|
292
|
+
const alwaysFromSensors = new Set();
|
|
293
|
+
for (const sensor of out.sensors) {
|
|
294
|
+
const extras = sensor.measurement?.alwaysMeasuredPaths;
|
|
295
|
+
if (!Array.isArray(extras))
|
|
296
|
+
continue;
|
|
297
|
+
for (const p of extras) {
|
|
298
|
+
if (typeof p === 'string' && (0, inputs_digest_1.isSafeRepoRel)(p))
|
|
299
|
+
alwaysFromSensors.add(p);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
const provided = Array.isArray(input.alwaysMeasuredPaths) ? input.alwaysMeasuredPaths : [];
|
|
303
|
+
for (const p of provided) {
|
|
304
|
+
if (typeof p === 'string' && (0, inputs_digest_1.isSafeRepoRel)(p))
|
|
305
|
+
alwaysFromSensors.add(p);
|
|
306
|
+
}
|
|
307
|
+
// Validate always-measured always includes baseline (RF-2.12 / RF-4.5), even when empty.
|
|
308
|
+
alwaysFromSensors.add(inputs_digest_1.CACHE_DIGEST_BASELINE_REL);
|
|
309
|
+
const alwaysMeasuredPaths = sortedStrings([...alwaysFromSensors]);
|
|
310
|
+
const body = {
|
|
311
|
+
schema: 2,
|
|
312
|
+
commit: input.commit,
|
|
313
|
+
ranAt: input.ranAt,
|
|
314
|
+
cliVersion: input.cliVersion,
|
|
315
|
+
overall: out.overall,
|
|
316
|
+
sensors,
|
|
317
|
+
measurements,
|
|
318
|
+
alwaysMeasuredPaths,
|
|
319
|
+
};
|
|
110
320
|
const receipt = { ...body, digest: computeReceiptDigest(body) };
|
|
111
321
|
const problem = receiptProblem(receipt);
|
|
112
322
|
if (problem !== null)
|
|
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.REASON_GIT_FAILED = exports.REASON_NOT_REPO = exports.RUNTIME_STATE_EXCLUDES = void 0;
|
|
7
7
|
exports.gitState = gitState;
|
|
8
|
+
exports.treeDirty = treeDirty;
|
|
8
9
|
const path_1 = __importDefault(require("path"));
|
|
9
10
|
const git_env_1 = require("./git-env");
|
|
10
11
|
const text_safety_1 = require("./text-safety");
|
|
@@ -39,15 +40,26 @@ function gitState(cwd, untracked, extraExcludes = []) {
|
|
|
39
40
|
catch (error) {
|
|
40
41
|
return gitFailure(error);
|
|
41
42
|
}
|
|
43
|
+
const dirty = treeDirty(root, untracked, extraExcludes);
|
|
44
|
+
return dirty === null ? { kind: 'failed' } : { kind: 'ok', root, commit, dirty };
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Only the dirty measure of `gitState`, for a caller that already knows the repository root and does not need HEAD: one
|
|
48
|
+
* `status` instead of three git processes. Null when git fails or an exclusion is unsafe (the caller fails closed). Never throws.
|
|
49
|
+
*/
|
|
50
|
+
function treeDirty(root, untracked, extraExcludes = []) {
|
|
51
|
+
if (typeof root !== 'string' || root.length === 0)
|
|
52
|
+
return null;
|
|
53
|
+
if (!Array.isArray(extraExcludes) || extraExcludes.length > MAX_EXTRA_EXCLUDES || !extraExcludes.every(text_safety_1.isSafeRelativePath))
|
|
54
|
+
return null;
|
|
42
55
|
try {
|
|
43
56
|
// Submodules are never hidden by config (`.gitmodules` ignore=all, diff.ignoreSubmodules). The pre-run measure counts everything
|
|
44
57
|
// (`none`); the post-run recheck is about tracked files only, so it also ignores untracked content inside a submodule
|
|
45
58
|
// (sensor build/coverage output) while still seeing a moved checkout or modified tracked content (`untracked`).
|
|
46
59
|
const args = ['status', '--porcelain', untracked ? '--untracked-files=normal' : '--untracked-files=no', untracked ? '--ignore-submodules=none' : '--ignore-submodules=untracked', '--', '.', ...exports.RUNTIME_STATE_EXCLUDES.map((p) => `:(exclude)${p}`), ...extraExcludes.map((p) => `:(exclude,literal)${p}`)];
|
|
47
|
-
|
|
48
|
-
return { kind: 'ok', root, commit, dirty: status.trim().length > 0 };
|
|
60
|
+
return (0, git_env_1.execGit)(root, args, { stderr: 'ignore', maxBuffer: MAX_GIT_OUTPUT }).trim().length > 0;
|
|
49
61
|
}
|
|
50
62
|
catch {
|
|
51
|
-
return
|
|
63
|
+
return null;
|
|
52
64
|
}
|
|
53
65
|
}
|
|
@@ -162,21 +162,16 @@ async function stepCache(d) {
|
|
|
162
162
|
return failed('machine.cache', 'machine', msg);
|
|
163
163
|
}
|
|
164
164
|
// `syncRegistries()` reports per-registry failures as RESULTS, not throws
|
|
165
|
-
// (registries.ts), so the `try` above catches none of them.
|
|
166
|
-
//
|
|
167
|
-
// later step's failure — with its own cause already gone.
|
|
165
|
+
// (registries.ts), so the `try` above catches none of them. SG-7: init must
|
|
166
|
+
// fail closed on ANY sync/clone failure (usable on-disk cache is not enough).
|
|
168
167
|
const errors = (0, registries_1.registrySyncErrors)(results);
|
|
169
168
|
if (errors.length === 0)
|
|
170
169
|
return ok('machine.cache', 'machine', 'applied');
|
|
171
|
-
// A registry that errored and has no content on disk is unusable, and every
|
|
172
|
-
// later step that reads it will fail for a reason that no longer names this
|
|
173
|
-
// cause — so machine.cache owns it here. One that errored but still has
|
|
174
|
-
// content is stale, not broken: record it and carry on.
|
|
175
170
|
const unusable = (0, registries_1.unusableSyncedRegistries)(results);
|
|
176
171
|
if (unusable.length > 0) {
|
|
177
172
|
return failed('machine.cache', 'machine', `registry unavailable — ${(0, registries_1.describeRegistrySyncErrors)(unusable)}`);
|
|
178
173
|
}
|
|
179
|
-
return
|
|
174
|
+
return failed('machine.cache', 'machine', `registry sync failed — ${(0, registries_1.describeRegistrySyncErrors)(errors)}`);
|
|
180
175
|
}
|
|
181
176
|
/** Step 2 – Install the session-start hook for the target agent. */
|
|
182
177
|
function stepHook(d) {
|
|
@@ -30,8 +30,9 @@ exports.assertRegistryGates = assertRegistryGates;
|
|
|
30
30
|
// src/core/registries.ts
|
|
31
31
|
const fs_1 = __importDefault(require("fs"));
|
|
32
32
|
const path_1 = __importDefault(require("path"));
|
|
33
|
-
const simple_git_1 = __importDefault(require("simple-git"));
|
|
34
33
|
const registry_1 = require("./registry");
|
|
34
|
+
const registry_git_1 = require("./registry-git");
|
|
35
|
+
const registry_git_errors_1 = require("./registry-git-errors");
|
|
35
36
|
const versioning_1 = require("./versioning");
|
|
36
37
|
const cli_version_1 = require("./cli-version");
|
|
37
38
|
const paths_1 = require("./paths");
|
|
@@ -229,8 +230,10 @@ function registriesNeedSync() {
|
|
|
229
230
|
return listRegistries().some((registry) => !validateRegistryLayout(registry.contentRoot));
|
|
230
231
|
}
|
|
231
232
|
/** Sincroniza cada registry configurado al ref resuelto (pin > último tag > HEAD);
|
|
232
|
-
* re-clona si falta el dir. Errores por-registry
|
|
233
|
+
* re-clona si falta el dir. Errores por-registry se reportan en el resultado (clasificados SG-6);
|
|
234
|
+
* si el llamador debe abortar lo decide la política SG-7 del comando. */
|
|
233
235
|
async function syncRegistries() {
|
|
236
|
+
(0, registry_git_1.warnIgnoredGitEnvOnce)();
|
|
234
237
|
const results = [];
|
|
235
238
|
for (const reg of listRegistries()) {
|
|
236
239
|
try {
|
|
@@ -241,7 +244,7 @@ async function syncRegistries() {
|
|
|
241
244
|
if (freshClone) {
|
|
242
245
|
fs_1.default.mkdirSync(registriesDir(), { recursive: true });
|
|
243
246
|
try {
|
|
244
|
-
await (0,
|
|
247
|
+
await (0, registry_git_1.createRegistryGit)().clone(reg.remote, reg.contentRoot);
|
|
245
248
|
}
|
|
246
249
|
catch (e) {
|
|
247
250
|
fs_1.default.rmSync(reg.contentRoot, { recursive: true, force: true });
|
|
@@ -249,9 +252,9 @@ async function syncRegistries() {
|
|
|
249
252
|
}
|
|
250
253
|
}
|
|
251
254
|
else {
|
|
252
|
-
await (0,
|
|
255
|
+
await (0, registry_git_1.createRegistryGit)(reg.contentRoot).reset(['--hard']);
|
|
253
256
|
}
|
|
254
|
-
const git = (0,
|
|
257
|
+
const git = (0, registry_git_1.createRegistryGit)(reg.contentRoot);
|
|
255
258
|
const previousVersion = freshClone ? null : await (0, versioning_1.currentVersion)(reg.contentRoot);
|
|
256
259
|
const versionOpts = (0, versioning_1.machineVersionOpts)(reg.name);
|
|
257
260
|
let resolved;
|
|
@@ -290,7 +293,11 @@ async function syncRegistries() {
|
|
|
290
293
|
});
|
|
291
294
|
}
|
|
292
295
|
catch (e) {
|
|
293
|
-
results.push({
|
|
296
|
+
results.push({
|
|
297
|
+
name: reg.name,
|
|
298
|
+
action: 'error',
|
|
299
|
+
error: (0, registry_git_errors_1.formatClassifiedRegistryFailure)({ name: reg.name, remote: reg.remote, err: e }),
|
|
300
|
+
});
|
|
294
301
|
}
|
|
295
302
|
}
|
|
296
303
|
return results;
|
|
@@ -309,15 +316,13 @@ function describeRegistrySyncErrors(errors) {
|
|
|
309
316
|
* Registries that both errored during sync AND no longer have a usable content
|
|
310
317
|
* layout afterwards — i.e. genuinely unusable, as opposed to merely stale.
|
|
311
318
|
*
|
|
312
|
-
* `syncRegistries()`
|
|
313
|
-
*
|
|
314
|
-
*
|
|
315
|
-
*
|
|
316
|
-
*
|
|
317
|
-
*
|
|
318
|
-
*
|
|
319
|
-
* not about being a healthy git clone — a seeded content root with no `.git`
|
|
320
|
-
* fails to sync every time and is still perfectly readable.
|
|
319
|
+
* `syncRegistries()` still reports per-registry failures as results rather than
|
|
320
|
+
* throwing (so callers can classify/format each one). Under SG-7, init / update /
|
|
321
|
+
* sync / registry add exit non-zero on ANY sync error; add / list may continue
|
|
322
|
+
* only when cached content remains usable — and this helper is what those soft
|
|
323
|
+
* paths consult. "Usable" is about content on disk, not a healthy git clone —
|
|
324
|
+
* a seeded content root with no `.git` fails to sync every time and is still
|
|
325
|
+
* readable.
|
|
321
326
|
*/
|
|
322
327
|
function unusableSyncedRegistries(results) {
|
|
323
328
|
const errors = registrySyncErrors(results);
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.redactRemoteForDisplay = redactRemoteForDisplay;
|
|
4
|
+
exports.extractFatalLine = extractFatalLine;
|
|
5
|
+
exports.classifyRegistryGitError = classifyRegistryGitError;
|
|
6
|
+
exports.formatClassifiedRegistryFailure = formatClassifiedRegistryFailure;
|
|
7
|
+
const registry_git_1 = require("./registry-git");
|
|
8
|
+
const text_safety_1 = require("./changes/text-safety");
|
|
9
|
+
const REMEDIES = {
|
|
10
|
+
authentication: 'configure credential.helper (awm ignores ambient GIT_ASKPASS)',
|
|
11
|
+
tls: 'git config --global http.sslCAInfo <path> or http.sslCAPath (awm ignores ambient GIT_SSL_*)',
|
|
12
|
+
network: 'check connectivity / proxy via git config --global http.proxy (awm does not pass ambient GIT_* proxy vars)',
|
|
13
|
+
ssh: 'set core.sshCommand or ~/.ssh/config (awm ignores ambient GIT_SSH_COMMAND)',
|
|
14
|
+
'not-found': 'verify the remote URL and that the repository exists and is reachable',
|
|
15
|
+
// simple-git `timeout.block` resets on stdout/stderr activity — idle bound, not wall-clock.
|
|
16
|
+
// Only the plugin's "block timeout reached" is this class (audit M1); git/curl "timed out" → network.
|
|
17
|
+
timeout: `check network connectivity; registry git children are killed after ${registry_git_1.REGISTRY_GIT_TIMEOUT_MS / 1000}s `
|
|
18
|
+
+ 'without stdout/stderr activity',
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Strip credential-bearing userinfo from a remote URL (or URL-shaped substring) before
|
|
22
|
+
* echoing it to operators. HTTPS `user:token@host` / `user@host` → scheme://host… only.
|
|
23
|
+
* Does not JSON-escape or clip — callers run `cleanText`/`clipMessage`/`esc` for echo safety.
|
|
24
|
+
*/
|
|
25
|
+
function redactRemoteForDisplay(remote) {
|
|
26
|
+
if (typeof remote !== 'string' || !remote)
|
|
27
|
+
return '';
|
|
28
|
+
// https://user:pass@host/... or https://user@host/...
|
|
29
|
+
return remote.replace(/\b([a-z][a-z0-9+.-]*:\/\/)([^/@\s]+)@/gi, '$1');
|
|
30
|
+
}
|
|
31
|
+
function errorText(err) {
|
|
32
|
+
if (err instanceof Error)
|
|
33
|
+
return err.message;
|
|
34
|
+
return String(err);
|
|
35
|
+
}
|
|
36
|
+
/** Extract the last line that looks like git's `fatal: …` diagnostic. */
|
|
37
|
+
function extractFatalLine(text) {
|
|
38
|
+
const lines = text.split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
|
|
39
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
40
|
+
if (/^fatal:/i.test(lines[i]))
|
|
41
|
+
return lines[i];
|
|
42
|
+
}
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Map a registry git failure to a stable class + file-based remedy (SG-6).
|
|
47
|
+
* Heuristics are substring-based on message/stderr; never includes env values.
|
|
48
|
+
*/
|
|
49
|
+
function classifyRegistryGitError(err) {
|
|
50
|
+
const message = errorText(err);
|
|
51
|
+
const fatalLine = extractFatalLine(message);
|
|
52
|
+
let errorClass;
|
|
53
|
+
// Audit M1: only simple-git's idle-block plugin text is `timeout`. Git/curl
|
|
54
|
+
// "Connection timed out" / "timed out" are connectivity → `network`.
|
|
55
|
+
if (/block timeout reached/i.test(message)) {
|
|
56
|
+
errorClass = 'timeout';
|
|
57
|
+
}
|
|
58
|
+
else if (/ssl|tls|certificate|unable to get local issuer|certificate verify failed/i.test(message)) {
|
|
59
|
+
errorClass = 'tls';
|
|
60
|
+
}
|
|
61
|
+
else if (
|
|
62
|
+
// Score repository-shaped not-found BEFORE broad SSH wording — GitHub SSH
|
|
63
|
+
// often pairs "Repository not found" with "Could not read from remote repository".
|
|
64
|
+
/repository not found|remote:\s*.*not found|fatal:\s*repository .+ not found|404\b/i.test(message)) {
|
|
65
|
+
errorClass = 'not-found';
|
|
66
|
+
}
|
|
67
|
+
else if (/host key verification|permission denied \(publickey\)/i.test(message)) {
|
|
68
|
+
errorClass = 'ssh';
|
|
69
|
+
}
|
|
70
|
+
else if (
|
|
71
|
+
// Remaining SSH transport wording — but DNS/timeout under ssh: belongs to network.
|
|
72
|
+
/\bssh\b/i.test(message)
|
|
73
|
+
&& !/could not resolve|name or service not known|timed?\s*out|connection timed out|terminal prompts disabled|authentication failed|could not read username/i.test(message)) {
|
|
74
|
+
errorClass = 'ssh';
|
|
75
|
+
}
|
|
76
|
+
else if (/terminal prompts disabled|authentication failed|could not read username|auth(entication)? (fail|required)|401\b|403\b|invalid credentials/i.test(message)) {
|
|
77
|
+
errorClass = 'authentication';
|
|
78
|
+
}
|
|
79
|
+
else if (/could not resolve host|could not resolve hostname|failed to connect|connection refused|network is unreachable|unable to connect|proxy|name or service not known|timed?\s*out|connection timed out/i.test(message)) {
|
|
80
|
+
errorClass = 'network';
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
// Default: treat unknown transport failures as network.
|
|
84
|
+
errorClass = 'network';
|
|
85
|
+
}
|
|
86
|
+
const rawFatal = fatalLine ?? (message.trim() ? message.trim().split(/\r?\n/).pop().slice(0, 240) : null);
|
|
87
|
+
return {
|
|
88
|
+
class: errorClass,
|
|
89
|
+
fatalLine: rawFatal === null ? null : redactRemoteForDisplay(rawFatal),
|
|
90
|
+
remedy: REMEDIES[errorClass],
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Operator-facing SG-6 line: registry name, remote when known, class, fatal, remedy.
|
|
95
|
+
* Never includes ambient env values or URL userinfo credentials.
|
|
96
|
+
*/
|
|
97
|
+
function formatClassifiedRegistryFailure(opts) {
|
|
98
|
+
const classified = classifyRegistryGitError(opts.err);
|
|
99
|
+
const remotePart = opts.remote
|
|
100
|
+
? `; remote=${(0, text_safety_1.clipMessage)((0, text_safety_1.cleanText)(redactRemoteForDisplay(opts.remote)))}`
|
|
101
|
+
: '';
|
|
102
|
+
const fatalPart = classified.fatalLine
|
|
103
|
+
? `; fatal: ${(0, text_safety_1.clipMessage)((0, text_safety_1.cleanText)(classified.fatalLine.replace(/^fatal:\s*/i, '')))}`
|
|
104
|
+
: '';
|
|
105
|
+
return (`registry ${(0, text_safety_1.esc)(opts.name)} sync failed (class=${classified.class}${remotePart}${fatalPart}); `
|
|
106
|
+
+ `remedy: ${classified.remedy}`);
|
|
107
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.REGISTRY_GIT_TIMEOUT_MS = void 0;
|
|
4
|
+
exports.buildRegistryGitEnv = buildRegistryGitEnv;
|
|
5
|
+
exports.listIgnoredGuardedGitEnvNames = listIgnoredGuardedGitEnvNames;
|
|
6
|
+
exports.formatIgnoredGitEnvNotice = formatIgnoredGitEnvNotice;
|
|
7
|
+
exports.resetIgnoredGitEnvNoticeForTests = resetIgnoredGitEnvNoticeForTests;
|
|
8
|
+
exports.warnIgnoredGitEnvOnce = warnIgnoredGitEnvOnce;
|
|
9
|
+
exports.createRegistryGit = createRegistryGit;
|
|
10
|
+
const simple_git_1 = require("simple-git");
|
|
11
|
+
exports.REGISTRY_GIT_TIMEOUT_MS = 60_000;
|
|
12
|
+
/**
|
|
13
|
+
* Non-`GIT_*` keys that simple-git 4 treats as guarded when supplied via `.env()`.
|
|
14
|
+
* Omit them from the copy so `.env(copy)` does not reject (editor/pager/visual family).
|
|
15
|
+
* Kept in sync with `@simple-git/argv-parser` GitEnvKeys non-prefix entries.
|
|
16
|
+
*/
|
|
17
|
+
const NON_PREFIX_GUARDED_ENV = new Set(['editor', 'pager', 'prefix', 'ssh_askpass', 'visual']);
|
|
18
|
+
/**
|
|
19
|
+
* Ambient names that affect registry transport/auth and are stripped by the factory.
|
|
20
|
+
* SG-5 warns only for these (audit M2) — not GIT_EDITOR / GIT_PAGER / GIT_AUTHOR_*.
|
|
21
|
+
* Includes SSH_ASKPASS (audit B2), which simple-git also guards without a GIT_ prefix.
|
|
22
|
+
*/
|
|
23
|
+
function isTransportRelevantIgnoredEnv(key) {
|
|
24
|
+
const lower = key.toLowerCase();
|
|
25
|
+
if (lower === 'ssh_askpass')
|
|
26
|
+
return true;
|
|
27
|
+
if (!lower.startsWith('git_'))
|
|
28
|
+
return false;
|
|
29
|
+
if (lower === 'git_terminal_prompt')
|
|
30
|
+
return false;
|
|
31
|
+
return (lower.startsWith('git_ssl_')
|
|
32
|
+
|| lower === 'git_askpass'
|
|
33
|
+
|| lower.startsWith('git_ssh')
|
|
34
|
+
|| lower.startsWith('git_proxy')
|
|
35
|
+
|| lower.startsWith('git_http_')
|
|
36
|
+
|| lower.startsWith('git_curl_')
|
|
37
|
+
|| lower.startsWith('git_config'));
|
|
38
|
+
}
|
|
39
|
+
/** Copy parent env without guarded GIT_* / editor keys, then force non-interactive prompts. */
|
|
40
|
+
function buildRegistryGitEnv(parent = process.env) {
|
|
41
|
+
const env = {};
|
|
42
|
+
for (const [key, value] of Object.entries(parent)) {
|
|
43
|
+
if (value === undefined)
|
|
44
|
+
continue;
|
|
45
|
+
const lower = key.toLowerCase();
|
|
46
|
+
if (lower.startsWith('git_'))
|
|
47
|
+
continue;
|
|
48
|
+
if (NON_PREFIX_GUARDED_ENV.has(lower))
|
|
49
|
+
continue;
|
|
50
|
+
env[key] = value;
|
|
51
|
+
}
|
|
52
|
+
env.GIT_TERMINAL_PROMPT = '0';
|
|
53
|
+
return env;
|
|
54
|
+
}
|
|
55
|
+
/** Names of stripped transport/auth vars worth warning about (SG-5) — never values. */
|
|
56
|
+
function listIgnoredGuardedGitEnvNames(parent = process.env) {
|
|
57
|
+
return Object.keys(parent)
|
|
58
|
+
.filter((key) => isTransportRelevantIgnoredEnv(key))
|
|
59
|
+
.sort((a, b) => a.localeCompare(b));
|
|
60
|
+
}
|
|
61
|
+
/** Operator notice for SG-5 — names only, never values. */
|
|
62
|
+
function formatIgnoredGitEnvNotice(names) {
|
|
63
|
+
const listed = names.join(', ');
|
|
64
|
+
return (`⚠ Ignoring ambient transport/auth env vars: ${listed}. `
|
|
65
|
+
+ 'Use file-based git config instead '
|
|
66
|
+
+ '(e.g. git config --global http.sslCAInfo <path>, credential.helper, core.sshCommand / ~/.ssh/config).');
|
|
67
|
+
}
|
|
68
|
+
let ignoredGitEnvWarned = false;
|
|
69
|
+
/** Test seam: clear the once-per-process SG-5 flag. */
|
|
70
|
+
function resetIgnoredGitEnvNoticeForTests() {
|
|
71
|
+
ignoredGitEnvWarned = false;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Before the first network registry git op: warn once listing ignored ambient
|
|
75
|
+
* transport/auth env names (except GIT_TERMINAL_PROMPT) and point at file-based remedies (SG-5).
|
|
76
|
+
*/
|
|
77
|
+
function warnIgnoredGitEnvOnce(log = (message) => console.warn(message), parent = process.env) {
|
|
78
|
+
if (ignoredGitEnvWarned)
|
|
79
|
+
return;
|
|
80
|
+
const names = listIgnoredGuardedGitEnvNames(parent);
|
|
81
|
+
if (names.length === 0)
|
|
82
|
+
return;
|
|
83
|
+
ignoredGitEnvWarned = true;
|
|
84
|
+
log(formatIgnoredGitEnvNotice(names));
|
|
85
|
+
}
|
|
86
|
+
function createRegistryGit(baseDir) {
|
|
87
|
+
const git = (0, simple_git_1.simpleGit)({
|
|
88
|
+
...(baseDir !== undefined ? { baseDir } : {}),
|
|
89
|
+
allowEnvironment: ['GIT_TERMINAL_PROMPT'],
|
|
90
|
+
timeout: { block: exports.REGISTRY_GIT_TIMEOUT_MS },
|
|
91
|
+
});
|
|
92
|
+
return git.env(buildRegistryGitEnv());
|
|
93
|
+
}
|