agentic-workflow-manager 9.22.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 +2 -2
- package/dist/src/commands/init.js +8 -4
- package/dist/src/commands/registry/add.js +9 -3
- 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/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 +103 -57
- 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/registry-sync-honesty.test.js +175 -0
- package/dist/tests/commands/sensors/exec.test.js +3 -1
- 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 +18 -6
- 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 +2 -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
- package/dist/tests/structural/sensor-execution-optimization-docs-contract.test.js +0 -102
- package/dist/tests/structural/sensor-slice-decision-contract.test.js +0 -63
|
@@ -1,7 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
3
|
exports.normalizePin = normalizePin;
|
|
7
4
|
exports.resolveTargetRef = resolveTargetRef;
|
|
@@ -12,7 +9,7 @@ exports.compareSemver = compareSemver;
|
|
|
12
9
|
//
|
|
13
10
|
// Resolver de versiones por tags semver — WS-3. Sin estado propio: git es la
|
|
14
11
|
// única fuente de verdad (la versión vigente se deriva de `git describe`).
|
|
15
|
-
const
|
|
12
|
+
const registry_git_1 = require("./registry-git");
|
|
16
13
|
const config_1 = require("../utils/config");
|
|
17
14
|
const TAG_RE = /^v(\d+)\.(\d+)\.(\d+)$/;
|
|
18
15
|
function normalizePin(pin) {
|
|
@@ -59,7 +56,7 @@ async function fetchVersionTags(git) {
|
|
|
59
56
|
* pin declarado > último tag semver (canal stable) > HEAD (canal dev o repo sin tags).
|
|
60
57
|
*/
|
|
61
58
|
async function resolveTargetRef(repoDir, opts) {
|
|
62
|
-
const git = (0,
|
|
59
|
+
const git = (0, registry_git_1.createRegistryGit)(repoDir);
|
|
63
60
|
const tags = await fetchVersionTags(git);
|
|
64
61
|
if (opts.pin) {
|
|
65
62
|
const want = `v${normalizePin(opts.pin)}`;
|
|
@@ -82,7 +79,7 @@ async function resolveTargetRef(repoDir, opts) {
|
|
|
82
79
|
/** Versión checkouteada actual ("X.Y.Z") si HEAD coincide exactamente con un tag semver; null si sigue un branch. */
|
|
83
80
|
async function currentVersion(repoDir) {
|
|
84
81
|
try {
|
|
85
|
-
const out = (await (0,
|
|
82
|
+
const out = (await (0, registry_git_1.createRegistryGit)(repoDir).raw(['describe', '--tags', '--exact-match', 'HEAD'])).trim();
|
|
86
83
|
return TAG_RE.test(out) ? out.slice(1) : null;
|
|
87
84
|
}
|
|
88
85
|
catch {
|
package/dist/src/index.js
CHANGED
|
@@ -19,6 +19,7 @@ const provider_preflight_1 = require("./ui/provider-preflight");
|
|
|
19
19
|
const discovery_1 = require("./core/discovery");
|
|
20
20
|
const bundles_1 = require("./core/bundles");
|
|
21
21
|
const registries_1 = require("./core/registries");
|
|
22
|
+
const registry_sync_report_1 = require("./core/registry-sync-report");
|
|
22
23
|
const install_planner_1 = require("./core/install-planner");
|
|
23
24
|
const install_transaction_1 = require("./core/install-transaction");
|
|
24
25
|
const profile_1 = require("./core/profile");
|
|
@@ -129,10 +130,8 @@ exports.program.command('add [name]')
|
|
|
129
130
|
s.start('Syncing registries...');
|
|
130
131
|
const results = await (0, registries_1.syncRegistries)();
|
|
131
132
|
s.stop('Registries synced.');
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
console.warn(picocolors_1.default.yellow(` ⚠ registry ${r.name}: ${r.error}`));
|
|
135
|
-
}
|
|
133
|
+
// SG-7 soft path: usable cache may continue after stating cached version + SG-6 cause.
|
|
134
|
+
await (0, registry_sync_report_1.reportRegistrySyncSoftFailures)(results, (m) => console.warn(picocolors_1.default.yellow(m)));
|
|
136
135
|
try {
|
|
137
136
|
(0, registries_1.assertSyncedRegistriesUsable)(results);
|
|
138
137
|
}
|
|
@@ -458,10 +457,8 @@ exports.program.command('list [package]')
|
|
|
458
457
|
s.start('Syncing registries...');
|
|
459
458
|
const listSyncResults = await (0, registries_1.syncRegistries)();
|
|
460
459
|
s.stop('Registries synced.');
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
console.warn(picocolors_1.default.yellow(` ⚠ registry ${r.name}: ${r.error}`));
|
|
464
|
-
}
|
|
460
|
+
// SG-7 soft path: usable cache may continue after stating cached version + SG-6 cause.
|
|
461
|
+
await (0, registry_sync_report_1.reportRegistrySyncSoftFailures)(listSyncResults, (m) => console.warn(picocolors_1.default.yellow(m)));
|
|
465
462
|
try {
|
|
466
463
|
(0, registries_1.assertSyncedRegistriesUsable)(listSyncResults);
|
|
467
464
|
}
|
|
@@ -92,6 +92,37 @@ function sharedRepo() {
|
|
|
92
92
|
beforeAll(() => { newRepo(); receiptForHead(); });
|
|
93
93
|
afterAll(() => repo.cleanup());
|
|
94
94
|
}
|
|
95
|
+
/**
|
|
96
|
+
* Prebuilt states: each is built once, lazily, in its own clone of the template (the git work happens there once), and every test
|
|
97
|
+
* that starts from it gets a private file copy (no git process) that it may change freely. `build` returns what the test needs
|
|
98
|
+
* of how the state was made (a command result, a text), handed back on every use.
|
|
99
|
+
*/
|
|
100
|
+
const fixtures = new Map();
|
|
101
|
+
afterAll(() => { for (const f of fixtures.values())
|
|
102
|
+
f.state.cleanup(); });
|
|
103
|
+
function fromFixture(key, build) {
|
|
104
|
+
let built = fixtures.get(key);
|
|
105
|
+
if (built === undefined) {
|
|
106
|
+
const own = repo;
|
|
107
|
+
repo = (0, helpers_1.cloneRepo)(template);
|
|
108
|
+
try {
|
|
109
|
+
built = { state: repo, value: build() };
|
|
110
|
+
fixtures.set(key, built);
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
repo.cleanup();
|
|
114
|
+
throw error;
|
|
115
|
+
}
|
|
116
|
+
finally {
|
|
117
|
+
repo = own;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
repo.cleanup();
|
|
121
|
+
repo = (0, helpers_1.cloneRepo)(built.state);
|
|
122
|
+
return built.value;
|
|
123
|
+
}
|
|
124
|
+
/** The repository after `receiptForHead()` and `run('change-record')` (the record is created, not committed): that run's result. */
|
|
125
|
+
const created = () => fromFixture('created', () => { receiptForHead(); return run('change-record'); });
|
|
95
126
|
// The command writes only into the throwaway repository: the real checkout is never touched.
|
|
96
127
|
afterEach(() => expect(listReal()).toEqual(realBefore));
|
|
97
128
|
afterAll(() => expect(listReal()).toEqual(realBefore));
|
|
@@ -181,20 +212,17 @@ describe('awm change new: receipt and clean tree (R3.6)', () => {
|
|
|
181
212
|
describe('awm change new: skeleton (R3.7, R3.9)', () => {
|
|
182
213
|
freshRepo();
|
|
183
214
|
test('prints the created path and writes exactly that file', () => {
|
|
184
|
-
|
|
185
|
-
const r = run('change-record');
|
|
215
|
+
const r = created();
|
|
186
216
|
expect(r).toEqual({ code: 0, stdout: 'Created docs/changes/2026-10-17-change-record.md\n', stderr: '' });
|
|
187
217
|
expect(recordFiles()).toEqual(['2026-10-17-change-record.md']);
|
|
188
218
|
});
|
|
189
219
|
test('derives type, scope, semver, breaking and modules from the branch commits', () => {
|
|
190
|
-
|
|
191
|
-
run('change-record');
|
|
220
|
+
created();
|
|
192
221
|
const fm = frontmatter(read());
|
|
193
222
|
expect(fm).toMatchObject({ schema: 'awm.change-record/v1', id: '2026-10-17-change-record', type: 'feat', scope: 'changes', semver: 'minor', breaking: false, modules: ['cli/src'] });
|
|
194
223
|
});
|
|
195
224
|
test('copies commit, overall, sensors and the digest of the receipt into verification', () => {
|
|
196
|
-
|
|
197
|
-
run('change-record');
|
|
225
|
+
created();
|
|
198
226
|
const expected = {
|
|
199
227
|
commit: repo.git('rev-parse', 'HEAD'),
|
|
200
228
|
overall: 'pass',
|
|
@@ -211,8 +239,7 @@ describe('awm change new: skeleton (R3.7, R3.9)', () => {
|
|
|
211
239
|
expect(frontmatter(read()).verification.receipt_digest).toBe(digest);
|
|
212
240
|
});
|
|
213
241
|
test('every narrative field and every body section carries the pending marker', () => {
|
|
214
|
-
|
|
215
|
-
run('change-record');
|
|
242
|
+
created();
|
|
216
243
|
const text = read();
|
|
217
244
|
const fm = frontmatter(text);
|
|
218
245
|
expect(fm.title).toBe(types_1.PENDING_MARKER);
|
|
@@ -225,8 +252,7 @@ describe('awm change new: skeleton (R3.7, R3.9)', () => {
|
|
|
225
252
|
}
|
|
226
253
|
});
|
|
227
254
|
test('the skeleton fails validation only because of its pending markers', () => {
|
|
228
|
-
|
|
229
|
-
run('change-record');
|
|
255
|
+
created();
|
|
230
256
|
const { diagnostics } = (0, validate_shape_1.validateRecordShape)('2026-10-17-change-record.md', read(), { root: repo.root });
|
|
231
257
|
expect(diagnostics.map((d) => d.code)).toEqual(['pending-content']);
|
|
232
258
|
});
|
|
@@ -246,13 +272,11 @@ describe('awm change new: skeleton (R3.7, R3.9)', () => {
|
|
|
246
272
|
expect(frontmatter(read()).links).toEqual({ plan: 'docs/plans/p.md', issue: 'https://github.com/o/r/issues/217' });
|
|
247
273
|
});
|
|
248
274
|
test('without --plan and --issue there is no links key', () => {
|
|
249
|
-
|
|
250
|
-
run('change-record');
|
|
275
|
+
created();
|
|
251
276
|
expect(frontmatter(read())).not.toHaveProperty('links');
|
|
252
277
|
});
|
|
253
278
|
test('the record ends with a newline and uses LF only', () => {
|
|
254
|
-
|
|
255
|
-
run('change-record');
|
|
279
|
+
created();
|
|
256
280
|
const text = read();
|
|
257
281
|
expect(text.endsWith('\n')).toBe(true);
|
|
258
282
|
expect(text).not.toContain('\r');
|
|
@@ -310,8 +334,20 @@ describe('awm change new: the 65536-byte boundary of the record (R3.4, R1.8)', (
|
|
|
310
334
|
...Array.from({ length: fillers }, (_, i) => ({ name: `f${String(i).padStart(4, '0')}${'x'.repeat(95)}`, status: 'pass', errors: [] })),
|
|
311
335
|
{ name: `v${'x'.repeat(variable - 1)}`, status: 'pass', errors: [] },
|
|
312
336
|
];
|
|
313
|
-
|
|
314
|
-
|
|
337
|
+
/** The receipt for HEAD of (fillers, variable), not written. */
|
|
338
|
+
const receiptOf = (head, fillers, variable) => (0, receipt_1.buildReceipt)({ commit: head, ranAt: RECEIPT_TIME, cliVersion: '9.20.0', output: { overall: 'pass', sensors: sensorList(fillers, variable) } });
|
|
339
|
+
const setReceipt = (fillers, variable) => { (0, receipt_1.writeReceipt)(repo.root, receiptOf(repo.git('rev-parse', 'HEAD'), fillers, variable)); };
|
|
340
|
+
/** The verification block the command copies from a receipt. */
|
|
341
|
+
const verificationOf = (r) => ({ commit: r.commit, overall: r.overall, sensors: r.sensors, receipt_digest: r.digest });
|
|
342
|
+
/** The derived fields a record states. */
|
|
343
|
+
const fieldsOf = (text) => {
|
|
344
|
+
const fm = frontmatter(text);
|
|
345
|
+
return { type: fm.type, scope: fm.scope, semver: fm.semver, breaking: fm.breaking, modules: fm.modules };
|
|
346
|
+
};
|
|
347
|
+
const bytesOf = (text) => {
|
|
348
|
+
if (text === null)
|
|
349
|
+
throw new Error('the record could not be rendered');
|
|
350
|
+
return Buffer.byteLength(text, 'utf8');
|
|
315
351
|
};
|
|
316
352
|
/** Largest n in [lo, hi] for which `fits(n)` holds, `fits` being true up to a point and false after it. */
|
|
317
353
|
const largest = (lo, hi, fits) => {
|
|
@@ -326,12 +362,17 @@ describe('awm change new: the 65536-byte boundary of the record (R3.4, R1.8)', (
|
|
|
326
362
|
}
|
|
327
363
|
return a;
|
|
328
364
|
};
|
|
329
|
-
/**
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
365
|
+
/**
|
|
366
|
+
* The receipt (fillers, variable) whose record is exactly at the limit is searched with `predict` (the size of the record the
|
|
367
|
+
* command would write for it: the pure renderer the command writes with, no git), then the command itself is run on that receipt
|
|
368
|
+
* and on the one a byte larger: `attempt` reports the size of the file it wrote, null when it failed as too large.
|
|
369
|
+
*/
|
|
370
|
+
function findBoundary(predict, attempt) {
|
|
371
|
+
const fits = (fillers, variable) => predict(fillers, variable) <= types_1.MAX_RECORD_BYTES;
|
|
372
|
+
const fillers = largest(0, 700, (k) => fits(k, 1));
|
|
373
|
+
const variable = largest(1, 128, (l) => fits(fillers, l));
|
|
374
|
+
const real = (f, v) => { setReceipt(f, v); return attempt().bytes; };
|
|
375
|
+
return { fillers, variable, atLimit: real(fillers, variable), oneMore: variable < 128 ? real(fillers, variable + 1) : -1 };
|
|
335
376
|
}
|
|
336
377
|
test('change new: a record of exactly 65536 bytes is written, one of 65537 is record-too-large', () => {
|
|
337
378
|
const attempt = () => {
|
|
@@ -344,7 +385,15 @@ describe('awm change new: the 65536-byte boundary of the record (R3.4, R1.8)', (
|
|
|
344
385
|
expect(r.stderr).toContain('record-too-large');
|
|
345
386
|
return { bytes: null };
|
|
346
387
|
};
|
|
347
|
-
const
|
|
388
|
+
const head = repo.git('rev-parse', 'HEAD');
|
|
389
|
+
setReceipt(0, 1);
|
|
390
|
+
expect(run('change-record').code).toBe(0);
|
|
391
|
+
const fields = fieldsOf(read());
|
|
392
|
+
const predict = (f, v) => bytesOf(renderModule.renderSkeleton({ id: '2026-10-17-change-record', fields, verification: verificationOf(receiptOf(head, f, v)), links: {}, provenance: {} }));
|
|
393
|
+
// The prediction is the command's own output: the record it just wrote, byte for byte.
|
|
394
|
+
expect(read()).toBe(renderModule.renderSkeleton({ id: '2026-10-17-change-record', fields, verification: verificationOf(receiptOf(head, 0, 1)), links: {}, provenance: {} }));
|
|
395
|
+
fs_1.default.rmSync(file());
|
|
396
|
+
const found = findBoundary(predict, attempt);
|
|
348
397
|
expect(found.variable).toBeLessThan(128);
|
|
349
398
|
expect(found.atLimit).toBe(65536);
|
|
350
399
|
expect(found.oneMore).toBeNull();
|
|
@@ -366,7 +415,16 @@ describe('awm change new: the 65536-byte boundary of the record (R3.4, R1.8)', (
|
|
|
366
415
|
expect(read()).toBe(original);
|
|
367
416
|
return { bytes: null };
|
|
368
417
|
};
|
|
369
|
-
const
|
|
418
|
+
const head = repo.git('rev-parse', 'HEAD');
|
|
419
|
+
setReceipt(0, 1);
|
|
420
|
+
expect(run('change-record', { refresh: true }).code).toBe(0);
|
|
421
|
+
const refreshed = read();
|
|
422
|
+
const fields = fieldsOf(refreshed);
|
|
423
|
+
fs_1.default.writeFileSync(file(), original);
|
|
424
|
+
const predict = (f, v) => bytesOf(renderModule.refreshDerived(original, fields, verificationOf(receiptOf(head, f, v))));
|
|
425
|
+
// The prediction is the command's own output: the record it just refreshed, byte for byte.
|
|
426
|
+
expect(renderModule.refreshDerived(original, fields, verificationOf(receiptOf(head, 0, 1)))).toBe(refreshed);
|
|
427
|
+
const found = findBoundary(predict, attempt);
|
|
370
428
|
expect(found.variable).toBeLessThan(128);
|
|
371
429
|
expect(found.atLimit).toBe(65536);
|
|
372
430
|
expect(found.oneMore).toBeNull();
|
|
@@ -389,8 +447,7 @@ describe('awm change new: HEAD moving while the command runs', () => {
|
|
|
389
447
|
expect(recordFiles()).toEqual([]);
|
|
390
448
|
});
|
|
391
449
|
test('a HEAD that moves before a refresh is written is head-moved and the record is untouched', () => {
|
|
392
|
-
|
|
393
|
-
run('change-record');
|
|
450
|
+
created();
|
|
394
451
|
repo.git('add', '-A');
|
|
395
452
|
repo.git('commit', '-q', '-m', 'docs(changes): record');
|
|
396
453
|
receiptForHead();
|
|
@@ -462,8 +519,7 @@ describe('awm change new: provenance (R3.8)', () => {
|
|
|
462
519
|
return r.state;
|
|
463
520
|
}
|
|
464
521
|
test('omits the provenance key entirely when no source exists', () => {
|
|
465
|
-
|
|
466
|
-
run('change-record');
|
|
522
|
+
created();
|
|
467
523
|
expect(frontmatter(read())).not.toHaveProperty('provenance');
|
|
468
524
|
});
|
|
469
525
|
test('model and session come only from flags', () => {
|
|
@@ -526,23 +582,20 @@ describe('awm change new: provenance (R3.8)', () => {
|
|
|
526
582
|
describe('awm change new: no overwrite (R3.10)', () => {
|
|
527
583
|
freshRepo();
|
|
528
584
|
test('refuses to overwrite on the same date and leaves the file untouched', () => {
|
|
529
|
-
|
|
530
|
-
run('change-record');
|
|
585
|
+
created();
|
|
531
586
|
fs_1.default.writeFileSync(file(), 'edited by hand');
|
|
532
587
|
expectFailure(run('change-record'), 'record-exists', 'use --refresh to update derived fields');
|
|
533
588
|
expect(read()).toBe('edited by hand');
|
|
534
589
|
});
|
|
535
590
|
test('refuses the same slug on another date', () => {
|
|
536
|
-
|
|
537
|
-
run('change-record');
|
|
591
|
+
created();
|
|
538
592
|
const before = read();
|
|
539
593
|
expectFailure(run('change-record', {}, { now: () => new Date('2026-10-20T12:00:00.000Z') }), 'record-exists', 'use --refresh to update derived fields');
|
|
540
594
|
expect(recordFiles()).toEqual(['2026-10-17-change-record.md']);
|
|
541
595
|
expect(read()).toBe(before);
|
|
542
596
|
});
|
|
543
597
|
test('a different slug is not blocked by an existing record', () => {
|
|
544
|
-
|
|
545
|
-
run('change-record');
|
|
598
|
+
created();
|
|
546
599
|
expect(run('change-record-2').code).toBe(0);
|
|
547
600
|
expect(recordFiles()).toEqual(['2026-10-17-change-record-2.md', '2026-10-17-change-record.md']);
|
|
548
601
|
});
|
|
@@ -561,8 +614,7 @@ describe('awm change new: no overwrite (R3.10)', () => {
|
|
|
561
614
|
expect(fs_1.default.readFileSync(outside, 'utf8')).toBe('precious');
|
|
562
615
|
});
|
|
563
616
|
test('a listing that misses an existing file still never overwrites it (exclusive create)', () => {
|
|
564
|
-
|
|
565
|
-
run('change-record');
|
|
617
|
+
created();
|
|
566
618
|
fs_1.default.writeFileSync(file(), 'edited by hand');
|
|
567
619
|
interceptFs('readdirSync', (a) => isRecordsDirArg(a[0]), () => []);
|
|
568
620
|
expectFailure(run('change-record'), 'record-exists', 'use --refresh to update derived fields');
|
|
@@ -842,7 +894,7 @@ describe('awm change new: base and derivation diagnostics (R3.1, R3.2, R3.5)', (
|
|
|
842
894
|
});
|
|
843
895
|
describe('awm change new --refresh (R3.11)', () => {
|
|
844
896
|
freshRepo();
|
|
845
|
-
function
|
|
897
|
+
function buildRecordCommitted(mutate) {
|
|
846
898
|
receiptForHead();
|
|
847
899
|
run('change-record');
|
|
848
900
|
const edited = mutate(read().split(types_1.PENDING_MARKER).join('written by agent'));
|
|
@@ -853,6 +905,10 @@ describe('awm change new --refresh (R3.11)', () => {
|
|
|
853
905
|
receiptForHead();
|
|
854
906
|
return edited;
|
|
855
907
|
}
|
|
908
|
+
/** A filled record committed, a later fix(core) commit and a receipt for it. Without `mutate` the state is prebuilt (fromFixture). */
|
|
909
|
+
function recordCommitted(mutate) {
|
|
910
|
+
return mutate === undefined ? fromFixture('record committed', () => buildRecordCommitted((t) => t)) : buildRecordCommitted(mutate);
|
|
911
|
+
}
|
|
856
912
|
const strip = (s) => s.replace(/^(type|scope|semver|breaking|modules|verification):[\s\S]*?(?=^[a-z_]+:|^---$)/gm, '');
|
|
857
913
|
test('recomputes the derived fields and verification', () => {
|
|
858
914
|
recordCommitted();
|
|
@@ -908,8 +964,7 @@ describe('awm change new --refresh (R3.11)', () => {
|
|
|
908
964
|
expectFailure(run('change-record', { refresh: true }), 'record-missing', 'run awm change new change-record to create it');
|
|
909
965
|
});
|
|
910
966
|
test('two records with the same slug are record-ambiguous and neither is modified', () => {
|
|
911
|
-
|
|
912
|
-
run('change-record');
|
|
967
|
+
created();
|
|
913
968
|
run('change-record', {}, { now });
|
|
914
969
|
fs_1.default.copyFileSync(file(), path_1.default.join(recordsDir(), '2026-11-01-change-record.md'));
|
|
915
970
|
const before = recordFiles().map((f) => fs_1.default.readFileSync(path_1.default.join(recordsDir(), f), 'utf8'));
|
|
@@ -927,8 +982,7 @@ describe('awm change new --refresh (R3.11)', () => {
|
|
|
927
982
|
expect(read()).toBe('---\n: : :\n---\n');
|
|
928
983
|
});
|
|
929
984
|
test('a record missing a derived key is record-invalid and untouched', () => {
|
|
930
|
-
|
|
931
|
-
run('change-record');
|
|
985
|
+
created();
|
|
932
986
|
const broken = read().replace(/^semver: .*\n/m, '');
|
|
933
987
|
fs_1.default.writeFileSync(file(), broken);
|
|
934
988
|
repo.git('add', '-A');
|
|
@@ -938,8 +992,7 @@ describe('awm change new --refresh (R3.11)', () => {
|
|
|
938
992
|
expect(read()).toBe(broken);
|
|
939
993
|
});
|
|
940
994
|
test('R3.4 a refresh that would push the record over 64 KiB is record-too-large with a remedy, and the record is untouched', () => {
|
|
941
|
-
|
|
942
|
-
run('change-record');
|
|
995
|
+
created();
|
|
943
996
|
repo.git('add', '-A');
|
|
944
997
|
repo.git('commit', '-q', '-m', 'docs(changes): record');
|
|
945
998
|
const files = Object.fromEntries(Array.from({ length: 4000 }, (_, i) => [`vendor/package-${String(i).padStart(5, '0')}/index.js`, '1']));
|
|
@@ -950,8 +1003,7 @@ describe('awm change new --refresh (R3.11)', () => {
|
|
|
950
1003
|
expect(read()).toBe(before);
|
|
951
1004
|
});
|
|
952
1005
|
test('a record already over 64 KiB on disk is record-invalid and untouched (it cannot be read for a rewrite)', () => {
|
|
953
|
-
|
|
954
|
-
run('change-record');
|
|
1006
|
+
created();
|
|
955
1007
|
fs_1.default.writeFileSync(file(), `${read()}\n${'x'.repeat(70 * 1024)}\n`);
|
|
956
1008
|
repo.git('add', '-A');
|
|
957
1009
|
repo.git('commit', '-q', '-m', 'docs(changes): huge');
|
|
@@ -961,8 +1013,7 @@ describe('awm change new --refresh (R3.11)', () => {
|
|
|
961
1013
|
expect(read()).toBe(before);
|
|
962
1014
|
});
|
|
963
1015
|
test('a record that is not valid UTF-8 is record-invalid and untouched', () => {
|
|
964
|
-
|
|
965
|
-
run('change-record');
|
|
1016
|
+
created();
|
|
966
1017
|
const bytes = Buffer.concat([fs_1.default.readFileSync(file()), Buffer.from([0xff, 0xfe, 0x0a])]);
|
|
967
1018
|
fs_1.default.writeFileSync(file(), bytes);
|
|
968
1019
|
repo.git('add', '-A');
|
|
@@ -972,8 +1023,7 @@ describe('awm change new --refresh (R3.11)', () => {
|
|
|
972
1023
|
expect(fs_1.default.readFileSync(file()).equals(bytes)).toBe(true);
|
|
973
1024
|
});
|
|
974
1025
|
test('a record with a UTF-8 BOM is record-invalid and its bytes are untouched', () => {
|
|
975
|
-
|
|
976
|
-
run('change-record');
|
|
1026
|
+
created();
|
|
977
1027
|
const bytes = Buffer.concat([Buffer.from([0xef, 0xbb, 0xbf]), fs_1.default.readFileSync(file())]);
|
|
978
1028
|
fs_1.default.writeFileSync(file(), bytes);
|
|
979
1029
|
repo.git('add', '-A');
|
|
@@ -983,8 +1033,7 @@ describe('awm change new --refresh (R3.11)', () => {
|
|
|
983
1033
|
expect(fs_1.default.readFileSync(file()).equals(bytes)).toBe(true);
|
|
984
1034
|
});
|
|
985
1035
|
test('a record without a BOM but with non-ASCII text still refreshes and keeps those bytes', () => {
|
|
986
|
-
|
|
987
|
-
run('change-record');
|
|
1036
|
+
created();
|
|
988
1037
|
fs_1.default.writeFileSync(file(), fs_1.default.readFileSync(file(), 'utf8').split(types_1.PENDING_MARKER).join('Qué se probó ñandú \u{1F600}'));
|
|
989
1038
|
repo.git('add', '-A');
|
|
990
1039
|
repo.git('commit', '-q', '-m', 'docs(changes): text');
|
|
@@ -995,8 +1044,7 @@ describe('awm change new --refresh (R3.11)', () => {
|
|
|
995
1044
|
expect(read().charCodeAt(0)).toBe('-'.charCodeAt(0));
|
|
996
1045
|
});
|
|
997
1046
|
test('a record larger than the record limit is record-invalid', () => {
|
|
998
|
-
|
|
999
|
-
run('change-record');
|
|
1047
|
+
created();
|
|
1000
1048
|
fs_1.default.appendFileSync(file(), 'x'.repeat(70 * 1024));
|
|
1001
1049
|
repo.git('add', '-A');
|
|
1002
1050
|
repo.git('commit', '-q', '-m', 'docs(changes): big');
|
|
@@ -1004,8 +1052,7 @@ describe('awm change new --refresh (R3.11)', () => {
|
|
|
1004
1052
|
expectFailure(run('change-record', { refresh: true }), 'record-invalid');
|
|
1005
1053
|
});
|
|
1006
1054
|
test('a record whose derived keys are quoted is refreshed and still validates', () => {
|
|
1007
|
-
|
|
1008
|
-
run('change-record');
|
|
1055
|
+
created();
|
|
1009
1056
|
const quoted = read().replace(/^(type|semver) *:/gm, '"$1" :');
|
|
1010
1057
|
fs_1.default.writeFileSync(file(), quoted.split(types_1.PENDING_MARKER).join('written by agent'));
|
|
1011
1058
|
repo.git('add', '-A');
|
|
@@ -1043,8 +1090,7 @@ describe('awm change new --refresh (R3.11)', () => {
|
|
|
1043
1090
|
expect(run('change-record', { refresh: true, base: 'main' }).code).toBe(0);
|
|
1044
1091
|
});
|
|
1045
1092
|
test('a derived key spelled with a YAML escape is record-invalid and the bytes are untouched', () => {
|
|
1046
|
-
|
|
1047
|
-
run('change-record');
|
|
1093
|
+
created();
|
|
1048
1094
|
const bytes = Buffer.from(read().replace(/^type: .*$/m, '"t\\x79pe": fix'));
|
|
1049
1095
|
expect(bytes.toString('utf8')).toContain('"t\\x79pe": fix');
|
|
1050
1096
|
fs_1.default.writeFileSync(file(), bytes);
|