agentic-workflow-manager 6.5.2 → 8.0.0
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/scripts/sensor-support-matrix.js +103 -0
- package/dist/scripts/support-matrix.js +40 -17
- package/dist/src/commands/init.js +1 -3
- package/dist/src/commands/ledger/index.js +6 -0
- package/dist/src/commands/preflight/checks.js +4 -4
- package/dist/src/commands/preflight/index.js +2 -2
- package/dist/src/commands/registry/add.js +2 -2
- package/dist/src/commands/registry/index.js +5 -0
- package/dist/src/commands/registry/status.js +7 -0
- package/dist/src/commands/sensors/compatibility/contract.js +261 -0
- package/dist/src/commands/sensors/compatibility/discovery.js +207 -0
- package/dist/src/commands/sensors/compatibility/live.js +59 -0
- package/dist/src/commands/sensors/compatibility/manifest.js +197 -0
- package/dist/src/commands/sensors/compatibility/materialize.js +126 -0
- package/dist/src/commands/sensors/compatibility/pack-source.js +78 -0
- package/dist/src/commands/sensors/compatibility/probe.js +47 -0
- package/dist/src/commands/sensors/compatibility/resolve.js +96 -0
- package/dist/src/commands/sensors/compatibility/types.js +2 -0
- package/dist/src/commands/sensors/coverage/contract.js +0 -50
- package/dist/src/commands/sensors/coverage/empirical.js +131 -0
- package/dist/src/commands/sensors/coverage/evaluate.js +85 -7
- package/dist/src/commands/sensors/coverage/evidence.js +5 -1
- package/dist/src/commands/sensors/coverage/index.js +44 -7
- package/dist/src/commands/sensors/coverage/render.js +214 -14
- package/dist/src/commands/sensors/coverage/resolve.js +24 -38
- package/dist/src/commands/sensors/exec.js +150 -9
- package/dist/src/commands/sensors/index.js +18 -6
- package/dist/src/commands/sensors/init.js +92 -6
- package/dist/src/commands/sensors/run.js +71 -0
- package/dist/src/commands/sensors/status.js +48 -4
- package/dist/src/commands/sync.js +7 -0
- package/dist/src/core/bundles.js +38 -3
- package/dist/src/core/init/steps.js +7 -4
- package/dist/src/core/ledger/scan.js +228 -0
- package/dist/src/core/ledger/store.js +89 -25
- package/dist/src/core/ledger/types.js +54 -0
- package/dist/src/core/paths.js +10 -9
- package/dist/src/core/registries.js +134 -14
- package/dist/src/index.js +24 -1
- package/dist/tests/commands/ledger/index.test.js +11 -0
- package/dist/tests/commands/preflight/preflight.test.js +64 -64
- package/dist/tests/commands/registry/add.test.js +69 -2
- package/dist/tests/commands/registry/status.test.js +14 -0
- package/dist/tests/commands/sensors/compatibility/contract.test.js +98 -0
- package/dist/tests/commands/sensors/compatibility/discovery.test.js +119 -0
- package/dist/tests/commands/sensors/compatibility/manifest.test.js +68 -0
- package/dist/tests/commands/sensors/compatibility/materialize.test.js +53 -0
- package/dist/tests/commands/sensors/compatibility/pack-source.test.js +55 -0
- package/dist/tests/commands/sensors/compatibility/probe.test.js +27 -0
- package/dist/tests/commands/sensors/compatibility/resolve.test.js +75 -0
- package/dist/tests/commands/sensors/coverage/contract.test.js +8 -22
- package/dist/tests/commands/sensors/coverage/empirical.test.js +111 -0
- package/dist/tests/commands/sensors/coverage/evaluate.test.js +29 -0
- package/dist/tests/commands/sensors/coverage/evidence.test.js +9 -0
- package/dist/tests/commands/sensors/coverage/index.test.js +40 -13
- package/dist/tests/commands/sensors/coverage/render.test.js +187 -12
- package/dist/tests/commands/sensors/coverage/resolve.test.js +25 -4
- package/dist/tests/commands/sensors/exec-windows.test.js +92 -2
- package/dist/tests/commands/sensors/exec.test.js +67 -5
- package/dist/tests/commands/sensors/index.test.js +8 -0
- package/dist/tests/commands/sensors/init-pack-unavailable.test.js +12 -12
- package/dist/tests/commands/sensors/init.test.js +132 -48
- package/dist/tests/commands/sensors/run-inconclusive.test.js +9 -2
- package/dist/tests/commands/sensors/run-is-read-only.test.js +0 -0
- package/dist/tests/commands/sensors/run.test.js +66 -3
- package/dist/tests/commands/sensors/status-windows.test.js +7 -7
- package/dist/tests/commands/sensors/status.test.js +74 -23
- package/dist/tests/commands/update.test.js +1 -1
- package/dist/tests/core/bundles.test.js +37 -0
- package/dist/tests/core/discovery.test.js +4 -0
- package/dist/tests/core/init/orchestrator.test.js +5 -1
- package/dist/tests/core/init/steps.test.js +20 -4
- package/dist/tests/core/ledger/scan.test.js +217 -0
- package/dist/tests/core/ledger/store.test.js +108 -0
- package/dist/tests/core/profile-pins.test.js +2 -1
- package/dist/tests/core/project-skill-links.test.js +22 -0
- package/dist/tests/core/registries-capability.test.js +10 -0
- package/dist/tests/core/registries-sync.test.js +16 -0
- package/dist/tests/core/registries.test.js +129 -1
- package/dist/tests/core/registry-manifest.test.js +7 -0
- package/dist/tests/core/same-existing-path.test.js +35 -0
- package/dist/tests/core/sync-gates.test.js +2 -1
- package/dist/tests/integration/copilot-init-isolated.test.js +18 -0
- package/dist/tests/integration/sensor-compatibility.e2e.test.js +141 -0
- package/dist/tests/integration/sensor-coverage.e2e.test.js +35 -6
- package/dist/tests/integration/sensor-structured-run.e2e.test.js +127 -0
- package/dist/tests/structural/active-documentation.test.js +128 -0
- package/dist/tests/structural/async-entrypoint.test.js +15 -0
- package/dist/tests/structural/jest-environment-is-isolated.test.js +44 -0
- package/dist/tests/structural/r3-cli-major-version.test.js +37 -0
- package/dist/tests/structural/sensor-documentation-contract.test.js +77 -0
- package/dist/tests/structural/support-matrix-is-current.test.js +69 -2
- package/package.json +8 -2
|
@@ -14,6 +14,7 @@ const fixture = path_1.default.join(cliDir, 'tests/fixtures/sensor-coverage/js-t
|
|
|
14
14
|
const registryFixture = path_1.default.join(cliDir, 'tests/fixtures/sensor-coverage/registry');
|
|
15
15
|
const noFollowUnavailableOnNativeWindows = process.platform === 'win32'
|
|
16
16
|
&& typeof fs_1.default.constants.O_NOFOLLOW !== 'number';
|
|
17
|
+
const testWithNoFollow = noFollowUnavailableOnNativeWindows ? test.skip : test;
|
|
17
18
|
const hashTree = (root) => {
|
|
18
19
|
const hash = crypto_1.default.createHash('sha256');
|
|
19
20
|
const walk = (directory) => {
|
|
@@ -63,7 +64,7 @@ function runWithFixture(mutatePack) {
|
|
|
63
64
|
}
|
|
64
65
|
return { tmp, project, awmHome };
|
|
65
66
|
}
|
|
66
|
-
test('compiled CLI reports
|
|
67
|
+
test('compiled CLI reports static and read-only empirical coverage without leaking ledger text (R5.6, R5.10)', () => {
|
|
67
68
|
const { tmp, project, awmHome } = runWithFixture();
|
|
68
69
|
try {
|
|
69
70
|
const before = [hashTree(project), hashTree(awmHome)];
|
|
@@ -78,9 +79,16 @@ test('compiled CLI reports gaps or fails closed when no-follow safety is unavail
|
|
|
78
79
|
expect(result.status).toBe(0);
|
|
79
80
|
const report = JSON.parse(result.stdout ?? '');
|
|
80
81
|
expect(report.static.classes).toEqual(expect.arrayContaining([
|
|
81
|
-
expect.objectContaining({ id: 'formatting', status: '
|
|
82
|
-
expect.objectContaining({ id: 'project-style-conventions', status: '
|
|
82
|
+
expect.objectContaining({ id: 'formatting', status: 'unverifiable' }),
|
|
83
|
+
expect.objectContaining({ id: 'project-style-conventions', status: 'unverifiable' }),
|
|
83
84
|
]));
|
|
85
|
+
expect(report).toMatchObject({ schemaVersion: 2, empirical: {
|
|
86
|
+
status: 'partial', classes: [expect.objectContaining({ defectClass: 'lint-errors', occurrences: 2, recurrent: true })],
|
|
87
|
+
unclassified: { occurrences: 1 },
|
|
88
|
+
} });
|
|
89
|
+
expect(result.stdout).not.toContain('fixture-secret-description');
|
|
90
|
+
expect(result.stdout).not.toContain('private-lint-signature');
|
|
91
|
+
expect(result.stdout).not.toContain('windows secret');
|
|
84
92
|
}
|
|
85
93
|
expect([hashTree(project), hashTree(awmHome)]).toEqual(before);
|
|
86
94
|
}
|
|
@@ -88,7 +96,25 @@ test('compiled CLI reports gaps or fails closed when no-follow safety is unavail
|
|
|
88
96
|
fs_1.default.rmSync(tmp, { recursive: true, force: true });
|
|
89
97
|
}
|
|
90
98
|
});
|
|
91
|
-
|
|
99
|
+
testWithNoFollow('compiled coverage keeps static verdict stable while --min changes only recurrence emphasis', () => {
|
|
100
|
+
const { tmp, project, awmHome } = runWithFixture();
|
|
101
|
+
try {
|
|
102
|
+
const before = [hashTree(project), hashTree(awmHome)];
|
|
103
|
+
const run = (min) => (0, child_process_1.spawnSync)(process.execPath, [bin, 'sensors', 'coverage', '--json', '--min', min], {
|
|
104
|
+
cwd: project, encoding: 'utf8', env: { ...process.env, AWM_HOME: awmHome, AWM_NO_UPDATE_CHECK: '1' },
|
|
105
|
+
});
|
|
106
|
+
const defaultReport = JSON.parse(run('2').stdout ?? '');
|
|
107
|
+
const minThree = JSON.parse(run('3').stdout ?? '');
|
|
108
|
+
expect(defaultReport.static).toEqual(minThree.static);
|
|
109
|
+
expect(defaultReport.overall).toBe(minThree.overall);
|
|
110
|
+
expect(defaultReport.empirical.classes[0]).toMatchObject({ occurrences: 2, recurrent: true });
|
|
111
|
+
expect(minThree.empirical.classes[0]).toMatchObject({ occurrences: 2, recurrent: false });
|
|
112
|
+
expect([hashTree(project), hashTree(awmHome)]).toEqual(before);
|
|
113
|
+
}
|
|
114
|
+
finally {
|
|
115
|
+
fs_1.default.rmSync(tmp, { recursive: true, force: true });
|
|
116
|
+
}
|
|
117
|
+
});
|
|
92
118
|
testWithNoFollow('informative states exit zero; malformed contract exits non-zero (R2.7, R2.9)', () => {
|
|
93
119
|
const run = (mutatePack) => {
|
|
94
120
|
const { tmp, project, awmHome } = runWithFixture(mutatePack);
|
|
@@ -105,9 +131,12 @@ testWithNoFollow('informative states exit zero; malformed contract exits non-zer
|
|
|
105
131
|
const noReference = run((pack) => { delete pack.coverage; });
|
|
106
132
|
const malformed = run((pack) => { pack.coverage.schemaVersion = 2; });
|
|
107
133
|
expect(gaps.status).toBe(0);
|
|
108
|
-
expect(JSON.parse(gaps.stdout ?? '')).toMatchObject({ overall: '
|
|
134
|
+
expect(JSON.parse(gaps.stdout ?? '')).toMatchObject({ overall: 'inconclusive' });
|
|
109
135
|
expect(noReference.status).toBe(0);
|
|
110
|
-
expect(JSON.parse(noReference.stdout ?? '')).toMatchObject({
|
|
136
|
+
expect(JSON.parse(noReference.stdout ?? '')).toMatchObject({
|
|
137
|
+
static: { reason: 'no_reference' },
|
|
138
|
+
empirical: { classes: [expect.objectContaining({ outcome: 'coverage-unverifiable' })] },
|
|
139
|
+
});
|
|
111
140
|
expect(malformed.status).toBe(1);
|
|
112
141
|
expect(`${malformed.stdout ?? ''}${malformed.stderr ?? ''}`).toContain('schemaVersion must be 1');
|
|
113
142
|
});
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const child_process_1 = require("child_process");
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const os_1 = __importDefault(require("os"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const cliDir = path_1.default.resolve(__dirname, '../..');
|
|
11
|
+
const bin = path_1.default.join(cliDir, 'dist/src/index.js');
|
|
12
|
+
function writeJson(file, value) {
|
|
13
|
+
fs_1.default.mkdirSync(path_1.default.dirname(file), { recursive: true });
|
|
14
|
+
fs_1.default.writeFileSync(file, JSON.stringify(value, null, 2));
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* This is deliberately an argument which changes meaning under a shell on both
|
|
18
|
+
* POSIX and cmd.exe. The controlled Node tool records argv before the possible
|
|
19
|
+
* injected command can run, while the injected command would create the marker.
|
|
20
|
+
* A shell:true regression must therefore either alter argv or create the marker.
|
|
21
|
+
*/
|
|
22
|
+
const shellSensitiveLiteral = '--literal=preserve-this&node -e "require(\'fs\').writeFileSync(\'structured-shell-injection\', \'unexpected\')"';
|
|
23
|
+
function createFixture() {
|
|
24
|
+
const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-structured-run-'));
|
|
25
|
+
const project = path_1.default.join(root, 'project');
|
|
26
|
+
const awmHome = path_1.default.join(root, 'awm-home');
|
|
27
|
+
const registryRoot = path_1.default.join(awmHome, 'registries', 'baseline');
|
|
28
|
+
const literal = shellSensitiveLiteral;
|
|
29
|
+
writeJson(path_1.default.join(project, 'package.json'), { name: 'structured-run-fixture', private: true });
|
|
30
|
+
// Local metadata is the compatibility evidence. It is not a tool found in a
|
|
31
|
+
// global installation, so init and run must keep resolving this fixture's pack.
|
|
32
|
+
writeJson(path_1.default.join(project, 'node_modules', 'fixture-sensor', 'package.json'), {
|
|
33
|
+
name: 'fixture-sensor', version: '1.0.0',
|
|
34
|
+
});
|
|
35
|
+
fs_1.default.writeFileSync(path_1.default.join(project, 'fixture-sensor.config.mjs'), 'export default {};\n');
|
|
36
|
+
fs_1.default.writeFileSync(path_1.default.join(project, 'fixture-sensor.mjs'), [
|
|
37
|
+
"import fs from 'fs';",
|
|
38
|
+
"fs.writeFileSync('structured-argv.json', JSON.stringify(process.argv.slice(2)));",
|
|
39
|
+
].join('\n'));
|
|
40
|
+
writeJson(path_1.default.join(registryRoot, 'sensor-packs', 'fixture', 'pack.json'), {
|
|
41
|
+
schemaVersion: 2,
|
|
42
|
+
name: 'fixture',
|
|
43
|
+
description: 'compiled structured-run fixture',
|
|
44
|
+
detects: ['package.json'],
|
|
45
|
+
sensors: {
|
|
46
|
+
structured: {
|
|
47
|
+
applicability: { allFiles: ['package.json'] },
|
|
48
|
+
fast: true,
|
|
49
|
+
variants: [{
|
|
50
|
+
id: 'fixture-v1', priority: 100,
|
|
51
|
+
requirements: {
|
|
52
|
+
tool: 'fixture-sensor', toolRange: '>=1 <2',
|
|
53
|
+
runtime: 'node', runtimeRange: '>=20', configFiles: ['fixture-sensor.config.mjs'],
|
|
54
|
+
},
|
|
55
|
+
certifiedRange: '>=1 <2',
|
|
56
|
+
// `node` is resolved from the process environment, but the actual
|
|
57
|
+
// executable payload is the fixture-local script. Keeping the
|
|
58
|
+
// literal as one argv element proves no shell parses it.
|
|
59
|
+
command: { executable: 'node', resolution: 'path', args: ['fixture-sensor.mjs', literal] },
|
|
60
|
+
assets: ['fixture-sensor.config.mjs'], formatter: 'generic', probe: { kind: 'config-present' },
|
|
61
|
+
}],
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
coverage: {
|
|
65
|
+
schemaVersion: 1,
|
|
66
|
+
classes: {
|
|
67
|
+
'fixture-output': {
|
|
68
|
+
description: 'fixture structured output', detectors: [{ sensor: 'structured' }],
|
|
69
|
+
remedy: { summary: 'run the fixture sensor', command: 'awm sensors init --pack fixture' },
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
writeJson(path_1.default.join(awmHome, 'registries.json'), [{ name: 'baseline', remote: 'fixture' }]);
|
|
75
|
+
return { root, project, awmHome, registryRoot, literal };
|
|
76
|
+
}
|
|
77
|
+
function runCli(fixture, ...args) {
|
|
78
|
+
return (0, child_process_1.spawnSync)(process.execPath, [bin, 'sensors', ...args], {
|
|
79
|
+
cwd: fixture.project,
|
|
80
|
+
encoding: 'utf8',
|
|
81
|
+
env: { ...process.env, AWM_HOME: fixture.awmHome, AWM_NO_UPDATE_CHECK: '1' },
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
function assertSuccessfulCli(result, phase, requireStdout = false) {
|
|
85
|
+
const details = [
|
|
86
|
+
`status=${String(result.status)}`,
|
|
87
|
+
`signal=${String(result.signal)}`,
|
|
88
|
+
`error=${result.error ? `${result.error.name}: ${result.error.message}` : 'none'}`,
|
|
89
|
+
`stdout=${JSON.stringify(result.stdout ?? '')}`,
|
|
90
|
+
`stderr=${JSON.stringify(result.stderr ?? '')}`,
|
|
91
|
+
].join('\n');
|
|
92
|
+
if (result.error || result.status !== 0 || result.signal !== null) {
|
|
93
|
+
throw new Error(`${phase} child process failed:\n${details}`);
|
|
94
|
+
}
|
|
95
|
+
if (requireStdout && !(result.stdout ?? '').trim()) {
|
|
96
|
+
throw new Error(`${phase} child process emitted no JSON:\n${details}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
beforeAll(() => {
|
|
100
|
+
if (!fs_1.default.existsSync(bin))
|
|
101
|
+
throw new Error(`Structured sensor E2E requires the compiled CLI at ${bin}; run npm run build before this test.`);
|
|
102
|
+
});
|
|
103
|
+
test('compiled sensors run materializes a v2 registry command and passes its literal argv without a shell', () => {
|
|
104
|
+
const fixture = createFixture();
|
|
105
|
+
try {
|
|
106
|
+
const initialized = runCli(fixture, 'init', '--registry-root', fixture.registryRoot, '--pack', 'fixture', '--no-configure');
|
|
107
|
+
assertSuccessfulCli(initialized, 'init');
|
|
108
|
+
const manifest = JSON.parse(fs_1.default.readFileSync(path_1.default.join(fixture.project, '.awm', 'sensors.json'), 'utf8'));
|
|
109
|
+
expect(manifest).toMatchObject({
|
|
110
|
+
schemaVersion: 2,
|
|
111
|
+
pack: 'fixture',
|
|
112
|
+
sensors: { structured: { variantId: 'fixture-v1', command: { executable: 'node', resolution: 'path', args: ['fixture-sensor.mjs', fixture.literal] } } },
|
|
113
|
+
});
|
|
114
|
+
const result = runCli(fixture, 'run', '--fast');
|
|
115
|
+
assertSuccessfulCli(result, 'run --fast', true);
|
|
116
|
+
expect(JSON.parse(result.stdout)).toMatchObject({
|
|
117
|
+
overall: 'pass', sensors: [expect.objectContaining({ name: 'structured', status: 'pass' })],
|
|
118
|
+
});
|
|
119
|
+
expect(JSON.parse(fs_1.default.readFileSync(path_1.default.join(fixture.project, 'structured-argv.json'), 'utf8'))).toEqual([fixture.literal]);
|
|
120
|
+
// A shell:true mutation executes the trailing `& node -e ...` (or fails to
|
|
121
|
+
// preserve argv); this sentinel is the side-effect half of the regression.
|
|
122
|
+
expect(fs_1.default.existsSync(path_1.default.join(fixture.project, 'structured-shell-injection'))).toBe(false);
|
|
123
|
+
}
|
|
124
|
+
finally {
|
|
125
|
+
fs_1.default.rmSync(fixture.root, { recursive: true, force: true });
|
|
126
|
+
}
|
|
127
|
+
});
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const fs_1 = __importDefault(require("fs"));
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
|
+
const providers_1 = require("../../src/providers");
|
|
9
|
+
const ROOT = path_1.default.resolve(__dirname, '../../..');
|
|
10
|
+
const ACTIVE = [
|
|
11
|
+
'README.md',
|
|
12
|
+
'docs/README.md',
|
|
13
|
+
'docs/framework.md',
|
|
14
|
+
'docs/installation.md',
|
|
15
|
+
'docs/configuration.md',
|
|
16
|
+
'docs/project-setup.md',
|
|
17
|
+
'docs/agents-setup.md',
|
|
18
|
+
'docs/runbook.md',
|
|
19
|
+
'docs/cli-reference.md',
|
|
20
|
+
'docs/support-matrix.md',
|
|
21
|
+
'docs/architecture.md',
|
|
22
|
+
'docs/decisions.md',
|
|
23
|
+
'docs/guides/product-process.md',
|
|
24
|
+
'docs/guides/development-process.md',
|
|
25
|
+
'docs/guides/parallel-tracks.md',
|
|
26
|
+
];
|
|
27
|
+
const read = (file) => fs_1.default.readFileSync(path_1.default.join(ROOT, file), 'utf8');
|
|
28
|
+
function slug(text) {
|
|
29
|
+
return text
|
|
30
|
+
.trim()
|
|
31
|
+
.toLowerCase()
|
|
32
|
+
.replace(/[`*_~]/g, '')
|
|
33
|
+
.replace(/[^\p{L}\p{N}\s-]/gu, '')
|
|
34
|
+
.replace(/\s+/g, '-')
|
|
35
|
+
.replace(/-+/g, '-');
|
|
36
|
+
}
|
|
37
|
+
function anchors(markdown) {
|
|
38
|
+
const seen = new Map();
|
|
39
|
+
const out = new Set();
|
|
40
|
+
for (const match of markdown.matchAll(/^#{1,6}\s+(.+)$/gm)) {
|
|
41
|
+
const base = slug(match[1]);
|
|
42
|
+
const count = seen.get(base) ?? 0;
|
|
43
|
+
seen.set(base, count + 1);
|
|
44
|
+
out.add(count === 0 ? base : `${base}-${count}`);
|
|
45
|
+
}
|
|
46
|
+
for (const match of markdown.matchAll(/<a\s+(?:name|id)="([^"]+)"/g))
|
|
47
|
+
out.add(match[1]);
|
|
48
|
+
return out;
|
|
49
|
+
}
|
|
50
|
+
function localLinks(markdown) {
|
|
51
|
+
return Array.from(markdown.matchAll(/(?<!!)\[[^\]]+\]\(([^)]+)\)/g), (m) => m[1])
|
|
52
|
+
.filter((target) => !/^(?:https?:|mailto:)/.test(target));
|
|
53
|
+
}
|
|
54
|
+
describe('active documentation contract', () => {
|
|
55
|
+
it('has one reachable active editorial set (R1, R11, R14.4)', () => {
|
|
56
|
+
const navigation = read('README.md') + read('docs/README.md');
|
|
57
|
+
for (const file of ACTIVE) {
|
|
58
|
+
expect(fs_1.default.existsSync(path_1.default.join(ROOT, file))).toBe(true);
|
|
59
|
+
if (file !== 'README.md' && file !== 'docs/README.md') {
|
|
60
|
+
expect(navigation).toContain(file.replace(/^docs\//, ''));
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
it('keeps every relative link and anchor valid (R14.1)', () => {
|
|
65
|
+
for (const source of ACTIVE) {
|
|
66
|
+
for (const raw of localLinks(read(source))) {
|
|
67
|
+
const [filePart, fragment] = raw.split('#');
|
|
68
|
+
const target = filePart
|
|
69
|
+
? path_1.default.resolve(path_1.default.dirname(path_1.default.join(ROOT, source)), decodeURI(filePart))
|
|
70
|
+
: path_1.default.join(ROOT, source);
|
|
71
|
+
expect(fs_1.default.existsSync(target)).toBe(true);
|
|
72
|
+
if (fragment && target.endsWith('.md')) {
|
|
73
|
+
expect(anchors(fs_1.default.readFileSync(target, 'utf8'))).toContain(decodeURI(fragment));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
it('documents the two-stage boundary and official bundle scopes (R3, R8)', () => {
|
|
79
|
+
expect(read('docs/installation.md')).toContain('Prepare the machine');
|
|
80
|
+
expect(read('docs/project-setup.md')).toContain('Initialize a project');
|
|
81
|
+
expect(read('docs/configuration.md')).toContain('`dev` and `product`');
|
|
82
|
+
expect(read('docs/project-setup.md')).toContain('`frontend`');
|
|
83
|
+
});
|
|
84
|
+
it('documents every init provider and option (R5, R14.2)', () => {
|
|
85
|
+
const config = read('docs/configuration.md');
|
|
86
|
+
for (const agent of providers_1.AGENT_TARGETS) {
|
|
87
|
+
expect(config).toContain(`awm init --agent ${agent} --machine-only`);
|
|
88
|
+
}
|
|
89
|
+
for (const flag of ['--agent', '--machine-only', '--yes', '--json']) {
|
|
90
|
+
expect(config).toContain(`\`${flag}\``);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
it('assigns canonical topics instead of copying provider paths (R12)', () => {
|
|
94
|
+
expect(read('docs/configuration.md')).toContain('[Support matrix](support-matrix.md)');
|
|
95
|
+
expect(read('docs/configuration.md')).not.toMatch(/~\/\.(?:claude|codex|cursor|gemini)/);
|
|
96
|
+
});
|
|
97
|
+
it('keeps generated provider support prose in the canonical language (R2)', () => {
|
|
98
|
+
const matrix = read('docs/support-matrix.md');
|
|
99
|
+
expect(matrix).toContain('### Where each artifact is installed');
|
|
100
|
+
expect(matrix).toContain('### Agent profiles, workflows, hooks, and context');
|
|
101
|
+
expect(matrix).not.toContain('Dónde aterriza cada artefacto');
|
|
102
|
+
expect(matrix).not.toContain('Perfiles de agente, workflows, hooks y contexto');
|
|
103
|
+
});
|
|
104
|
+
it('keeps R3 canonical documentation in English and reachable from the hub', () => {
|
|
105
|
+
const hub = read('docs/README.md');
|
|
106
|
+
expect(hub).toContain('decisions.md');
|
|
107
|
+
for (const file of [
|
|
108
|
+
'docs/framework.md', 'docs/configuration.md', 'docs/project-setup.md',
|
|
109
|
+
'docs/runbook.md', 'docs/cli-reference.md', 'docs/support-matrix.md',
|
|
110
|
+
'docs/architecture.md',
|
|
111
|
+
]) {
|
|
112
|
+
expect(read(file)).not.toContain('### Metodología');
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
it('keeps provider tiers, registries, and hook targets technically precise (R2)', () => {
|
|
116
|
+
const matrix = read('docs/support-matrix.md');
|
|
117
|
+
const runbook = read('docs/runbook.md');
|
|
118
|
+
const cliReference = read('docs/cli-reference.md');
|
|
119
|
+
expect(matrix).toContain('Re-anchors process guidance');
|
|
120
|
+
expect(matrix).toContain('Only `awm sensors run` is an enforceable deterministic gate');
|
|
121
|
+
expect(runbook).toContain('A registry needs at least one content directory');
|
|
122
|
+
expect(runbook).toMatch(/`catalog\.json` is required only when the registry\s+publishes bundles/);
|
|
123
|
+
expect(cliReference).toContain('`claude-code` and `codex`');
|
|
124
|
+
expect(cliReference).toContain('`--agent` is an alias for `--target`');
|
|
125
|
+
expect(cliReference).toContain('`dev` and `product` baseline bundles');
|
|
126
|
+
expect(runbook).toContain('bundles/<bundle-name>/bundle.json');
|
|
127
|
+
});
|
|
128
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const fs_1 = __importDefault(require("fs"));
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
|
+
const entrypoint = path_1.default.resolve(__dirname, '../../src/index.ts');
|
|
9
|
+
describe('CLI async entrypoint contract', () => {
|
|
10
|
+
it('awaits Commander actions instead of starting them with parse()', () => {
|
|
11
|
+
const source = fs_1.default.readFileSync(entrypoint, 'utf8');
|
|
12
|
+
expect(source).toMatch(/program\.parseAsync\(\)\.catch\(/);
|
|
13
|
+
expect(source).not.toMatch(/\bprogram\.parse\(\);/);
|
|
14
|
+
});
|
|
15
|
+
});
|
|
@@ -4,6 +4,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
const os_1 = __importDefault(require("os"));
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
7
9
|
describe('Jest environment isolation', () => {
|
|
8
10
|
it('does not inherit the operator Codex home', () => {
|
|
9
11
|
expect(process.env.CODEX_HOME).toBeUndefined();
|
|
@@ -11,4 +13,46 @@ describe('Jest environment isolation', () => {
|
|
|
11
13
|
it('uses a suite-owned temporary directory', () => {
|
|
12
14
|
expect(os_1.default.tmpdir()).toContain('awm-jest-');
|
|
13
15
|
});
|
|
16
|
+
it('falls back to the system temp directory when the home cache is read-only', async () => {
|
|
17
|
+
const originalEnv = { ...process.env };
|
|
18
|
+
const fallback = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-jest-fallback-'));
|
|
19
|
+
try {
|
|
20
|
+
jest.resetModules();
|
|
21
|
+
jest.doMock('os', () => ({
|
|
22
|
+
...jest.requireActual('os'),
|
|
23
|
+
homedir: () => '/readonly-home',
|
|
24
|
+
tmpdir: () => fallback,
|
|
25
|
+
}));
|
|
26
|
+
const actualFs = jest.requireActual('fs');
|
|
27
|
+
jest.doMock('fs', () => ({
|
|
28
|
+
...actualFs,
|
|
29
|
+
mkdirSync: jest.fn((target, ...args) => {
|
|
30
|
+
if (String(target).includes('readonly-home')) {
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
return actualFs.mkdirSync(target, ...args);
|
|
34
|
+
}),
|
|
35
|
+
mkdtempSync: jest.fn((prefix, ...args) => {
|
|
36
|
+
if (prefix.includes('readonly-home')) {
|
|
37
|
+
const error = new Error('read-only');
|
|
38
|
+
error.code = 'EROFS';
|
|
39
|
+
throw error;
|
|
40
|
+
}
|
|
41
|
+
return actualFs.mkdtempSync(prefix, ...args);
|
|
42
|
+
}),
|
|
43
|
+
}));
|
|
44
|
+
const setup = require('../../jest.global-setup.js');
|
|
45
|
+
await expect(setup()).resolves.toBeUndefined();
|
|
46
|
+
expect(process.env.AWM_JEST_TMPDIR?.startsWith(path_1.default.join(fallback, 'awm-cache', 'awm-jest-'))).toBe(true);
|
|
47
|
+
}
|
|
48
|
+
finally {
|
|
49
|
+
jest.dontMock('fs');
|
|
50
|
+
jest.dontMock('os');
|
|
51
|
+
jest.resetModules();
|
|
52
|
+
for (const key of Object.keys(process.env))
|
|
53
|
+
delete process.env[key];
|
|
54
|
+
Object.assign(process.env, originalEnv);
|
|
55
|
+
fs_1.default.rmSync(fallback, { recursive: true, force: true });
|
|
56
|
+
}
|
|
57
|
+
});
|
|
14
58
|
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const fs_1 = __importDefault(require("fs"));
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
|
+
const child_process_1 = require("child_process");
|
|
9
|
+
const CLI_ROOT = path_1.default.resolve(__dirname, '../..');
|
|
10
|
+
const DIST_ENTRYPOINT = path_1.default.join(CLI_ROOT, 'dist', 'src', 'index.js');
|
|
11
|
+
const TARGET_VERSION = '7.0.0';
|
|
12
|
+
function readJson(file) {
|
|
13
|
+
return JSON.parse(fs_1.default.readFileSync(path_1.default.join(CLI_ROOT, file), 'utf8'));
|
|
14
|
+
}
|
|
15
|
+
function runCompiledCli(...args) {
|
|
16
|
+
const result = (0, child_process_1.spawnSync)(process.execPath, [DIST_ENTRYPOINT, ...args], {
|
|
17
|
+
cwd: CLI_ROOT,
|
|
18
|
+
encoding: 'utf8',
|
|
19
|
+
});
|
|
20
|
+
expect(result.error).toBeUndefined();
|
|
21
|
+
expect(result.status).toBe(0);
|
|
22
|
+
return `${result.stdout}${result.stderr}`;
|
|
23
|
+
}
|
|
24
|
+
describe('R3 public major CLI release contract', () => {
|
|
25
|
+
it('declares 7.0.0 consistently in package metadata and the lockfile root', () => {
|
|
26
|
+
const pkg = readJson('package.json');
|
|
27
|
+
const lock = readJson('package-lock.json');
|
|
28
|
+
const root = lock.packages[''];
|
|
29
|
+
expect(pkg.version).toBe(TARGET_VERSION);
|
|
30
|
+
expect(lock.version).toBe(TARGET_VERSION);
|
|
31
|
+
expect(root.version).toBe(TARGET_VERSION);
|
|
32
|
+
});
|
|
33
|
+
it('exposes 7.0.0 from the compiled CLI while retaining its help banner', () => {
|
|
34
|
+
expect(runCompiledCli('--version').trim()).toBe(TARGET_VERSION);
|
|
35
|
+
expect(runCompiledCli('--help')).toContain('Usage: awm');
|
|
36
|
+
});
|
|
37
|
+
});
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const fs_1 = __importDefault(require("fs"));
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
|
+
const commander_1 = require("commander");
|
|
9
|
+
jest.mock('@clack/prompts', () => ({
|
|
10
|
+
log: { error: jest.fn(), success: jest.fn(), warn: jest.fn(), info: jest.fn() },
|
|
11
|
+
}));
|
|
12
|
+
const manifest_1 = require("../../src/commands/sensors/compatibility/manifest");
|
|
13
|
+
const ledger_1 = require("../../src/commands/ledger");
|
|
14
|
+
const sensors_1 = require("../../src/commands/sensors");
|
|
15
|
+
const ROOT = path_1.default.resolve(__dirname, '../../..');
|
|
16
|
+
const read = (file) => fs_1.default.readFileSync(path_1.default.join(ROOT, file), 'utf8');
|
|
17
|
+
function documentedJson(files) {
|
|
18
|
+
return files.flatMap((file) => Array.from(read(file).matchAll(/```json\s*\n([\s\S]*?)```/g), (match) => JSON.parse(match[1])));
|
|
19
|
+
}
|
|
20
|
+
function helpFor(command) {
|
|
21
|
+
const program = new commander_1.Command().name('awm');
|
|
22
|
+
(0, sensors_1.registerSensorsCommand)(program);
|
|
23
|
+
(0, ledger_1.registerLedgerCommand)(program);
|
|
24
|
+
const [parent, child] = command.split(' ');
|
|
25
|
+
return program.commands.find((entry) => entry.name() === parent)
|
|
26
|
+
.commands.find((entry) => entry.name() === child).helpInformation();
|
|
27
|
+
}
|
|
28
|
+
describe('R3 canonical sensor documentation', () => {
|
|
29
|
+
test.each([
|
|
30
|
+
['docs/framework.md', ['Static coverage', 'Empirical coverage', 'retrospective feedback loop']],
|
|
31
|
+
['docs/configuration.md', ['pack schema v2', 'legacy pack', 'compatible-unverified']],
|
|
32
|
+
['docs/project-setup.md', ['version-aware', 'hardening opt-in']],
|
|
33
|
+
['docs/runbook.md', ['awm sensors coverage --min', 'compatibility drift', 'orphaned asset']],
|
|
34
|
+
['docs/cli-reference.md', ['schemaVersion: 2', '--defect-class', '--min']],
|
|
35
|
+
['docs/architecture.md', ['compatibility resolver', 'bounded probe']],
|
|
36
|
+
])('%s owns its R3 subject', (file, phrases) => {
|
|
37
|
+
const text = read(file);
|
|
38
|
+
phrases.forEach((phrase) => expect(text).toContain(phrase));
|
|
39
|
+
});
|
|
40
|
+
test('parses every documented v2 sensor manifest example with the production parser', () => {
|
|
41
|
+
const examples = documentedJson(['docs/configuration.md', 'docs/cli-reference.md'])
|
|
42
|
+
.filter((value) => typeof value === 'object' && value !== null && value.schemaVersion === 2);
|
|
43
|
+
expect(examples.length).toBeGreaterThan(0);
|
|
44
|
+
examples.forEach((example) => expect(() => (0, manifest_1.parseSensorManifest)(example, 'documented example')).not.toThrow());
|
|
45
|
+
});
|
|
46
|
+
test('keeps documented v2 sensor manifests portable across native platforms', () => {
|
|
47
|
+
const examples = documentedJson(['docs/configuration.md', 'docs/cli-reference.md'])
|
|
48
|
+
.filter((value) => typeof value === 'object' && value !== null && value.schemaVersion === 2);
|
|
49
|
+
expect(examples.length).toBeGreaterThan(0);
|
|
50
|
+
examples.forEach((example) => expect(example).not.toHaveProperty('registryRoot'));
|
|
51
|
+
});
|
|
52
|
+
test('documents exact Commander flags', () => {
|
|
53
|
+
expect(helpFor('sensors coverage')).toContain('--min <count>');
|
|
54
|
+
expect(helpFor('ledger add')).toContain('--defect-class <id>');
|
|
55
|
+
const reference = read('docs/cli-reference.md');
|
|
56
|
+
expect(reference).toContain('awm sensors coverage [--json] [--min <count>]');
|
|
57
|
+
expect(reference).toContain('--defect-class <id>');
|
|
58
|
+
});
|
|
59
|
+
test('does not overstate the human coverage renderer or retrospective archive wiring', () => {
|
|
60
|
+
const reference = read('docs/cli-reference.md');
|
|
61
|
+
const runbook = read('docs/runbook.md');
|
|
62
|
+
expect(reference).toMatch(/safe,\s+sanitized evidence references and safe cluster signatures/);
|
|
63
|
+
expect(reference).toMatch(/never ledger\s+descriptions, raw ledger lines, or unsafe values/);
|
|
64
|
+
expect(runbook).toContain('Run `awm sensors coverage --min 2` manually before archive');
|
|
65
|
+
expect(runbook).toMatch(/Automatic\s+coverage-before-archive enforcement is delivered by the registry in T12/);
|
|
66
|
+
expect(runbook).not.toContain('it automatically evaluates\ncoverage feedback');
|
|
67
|
+
});
|
|
68
|
+
test('keeps archival coverage manual until registry enforcement and scopes v2 binary resolution', () => {
|
|
69
|
+
const framework = read('docs/framework.md');
|
|
70
|
+
const osMatrix = read('docs/testing/os-matrix.md');
|
|
71
|
+
expect(framework).toMatch(/manual operator step[\s\S]*until the registry delivers automatic enforcement in T12/i);
|
|
72
|
+
expect(osMatrix).toMatch(/v2 local resolution modes use project-local `node_modules` or `.venv`\/`venv`\s+environments/i);
|
|
73
|
+
expect(osMatrix).toMatch(/Windows `.cmd`\/`.bat` wrappers are rejected for structured v2\s+commands/i);
|
|
74
|
+
expect(osMatrix).toMatch(/Legacy string\s+commands remain separate and retain their documented shell\/PATH semantics/i);
|
|
75
|
+
expect(osMatrix).not.toContain('Sensors resolve their binaries on PATH');
|
|
76
|
+
});
|
|
77
|
+
});
|
|
@@ -16,7 +16,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
16
16
|
// Este test hace que ese desvio sea imposible de mergear. No verifica que la tabla sea
|
|
17
17
|
// "linda": verifica que sea LA MISMA que produce el codigo hoy.
|
|
18
18
|
const fs_1 = __importDefault(require("fs"));
|
|
19
|
+
const path_1 = __importDefault(require("path"));
|
|
19
20
|
const support_matrix_1 = require("../../scripts/support-matrix");
|
|
21
|
+
const sensor_support_matrix_1 = require("../../scripts/sensor-support-matrix");
|
|
22
|
+
const SENSOR_FIXTURE_REGISTRY = path_1.default.join(__dirname, '..', 'fixtures', 'sensor-support-matrix', 'registry');
|
|
20
23
|
describe('docs/support-matrix.md refleja el codigo', () => {
|
|
21
24
|
it('el bloque generado esta al dia', () => {
|
|
22
25
|
const doc = fs_1.default.readFileSync(support_matrix_1.DOC_PATH, 'utf-8');
|
|
@@ -30,6 +33,18 @@ describe('docs/support-matrix.md refleja el codigo', () => {
|
|
|
30
33
|
expect(doc).toContain(support_matrix_1.BEGIN_MARKER);
|
|
31
34
|
expect(doc).toContain(support_matrix_1.END_MARKER);
|
|
32
35
|
});
|
|
36
|
+
it('the generated pre-publication v2 sensor-pack contract fixture is current', () => {
|
|
37
|
+
const doc = fs_1.default.readFileSync(support_matrix_1.DOC_PATH, 'utf-8');
|
|
38
|
+
expect(doc).toContain(sensor_support_matrix_1.SENSOR_BEGIN_MARKER);
|
|
39
|
+
expect(doc).toContain(sensor_support_matrix_1.SENSOR_END_MARKER);
|
|
40
|
+
expect(doc).toContain(sensor_support_matrix_1.R3_PREPUBLICATION_FIXTURE_PURPOSE);
|
|
41
|
+
expect(doc).toContain('not the published `awm-baseline-registry` manifests');
|
|
42
|
+
expect(doc).toBe((0, sensor_support_matrix_1.spliceSensorSupportMatrix)(doc, (0, sensor_support_matrix_1.renderSensorSupportMatrix)(SENSOR_FIXTURE_REGISTRY)));
|
|
43
|
+
});
|
|
44
|
+
it('pins the documentation generator to the declared R3 pre-publication fixture', () => {
|
|
45
|
+
const packageJson = JSON.parse(fs_1.default.readFileSync(path_1.default.join(__dirname, '..', '..', 'package.json'), 'utf8'));
|
|
46
|
+
expect(packageJson.scripts?.['docs:matrix']).toContain(`--registry-root ${sensor_support_matrix_1.R3_PREPUBLICATION_FIXTURE_RELATIVE_PATH}`);
|
|
47
|
+
});
|
|
33
48
|
it('la tabla nombra a los seis providers declarados', () => {
|
|
34
49
|
// Que el bloque este "al dia" no sirve si el generador se olvidara de un provider:
|
|
35
50
|
// doc y generador coincidirian, los dos incompletos.
|
|
@@ -61,6 +76,58 @@ describe('docs/support-matrix.md refleja el codigo', () => {
|
|
|
61
76
|
jest.resetModules();
|
|
62
77
|
}
|
|
63
78
|
});
|
|
79
|
+
it('documents default provider paths even when this machine overrides CODEX_HOME', () => {
|
|
80
|
+
const saved = process.env.CODEX_HOME;
|
|
81
|
+
process.env.CODEX_HOME = '/private/codex-home';
|
|
82
|
+
try {
|
|
83
|
+
jest.resetModules();
|
|
84
|
+
const { renderProviderTables: fresh } = require('../../scripts/support-matrix');
|
|
85
|
+
expect(fresh()).toContain('`~/.codex/agents`');
|
|
86
|
+
expect(fresh()).not.toContain('/private/codex-home');
|
|
87
|
+
}
|
|
88
|
+
finally {
|
|
89
|
+
if (saved === undefined)
|
|
90
|
+
delete process.env.CODEX_HOME;
|
|
91
|
+
else
|
|
92
|
+
process.env.CODEX_HOME = saved;
|
|
93
|
+
jest.resetModules();
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
it('does not leak a normalized CODEX_HOME override into generated documentation', () => {
|
|
97
|
+
const saved = process.env.CODEX_HOME;
|
|
98
|
+
process.env.CODEX_HOME = '/private/codex/../secret-codex/';
|
|
99
|
+
try {
|
|
100
|
+
jest.resetModules();
|
|
101
|
+
const { renderProviderTables: fresh } = require('../../scripts/support-matrix');
|
|
102
|
+
const generated = fresh();
|
|
103
|
+
expect(generated).toContain('`~/.codex/agents`');
|
|
104
|
+
expect(generated).not.toContain('/private/');
|
|
105
|
+
expect(generated).not.toContain('secret-codex');
|
|
106
|
+
}
|
|
107
|
+
finally {
|
|
108
|
+
if (saved === undefined)
|
|
109
|
+
delete process.env.CODEX_HOME;
|
|
110
|
+
else
|
|
111
|
+
process.env.CODEX_HOME = saved;
|
|
112
|
+
jest.resetModules();
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
it('keeps a separator when CODEX_HOME has a trailing slash', () => {
|
|
116
|
+
const saved = process.env.CODEX_HOME;
|
|
117
|
+
process.env.CODEX_HOME = '/private/codex-home/';
|
|
118
|
+
try {
|
|
119
|
+
jest.resetModules();
|
|
120
|
+
const { renderProviderTables: fresh } = require('../../scripts/support-matrix');
|
|
121
|
+
expect(fresh()).toContain('`~/.codex/agents`');
|
|
122
|
+
}
|
|
123
|
+
finally {
|
|
124
|
+
if (saved === undefined)
|
|
125
|
+
delete process.env.CODEX_HOME;
|
|
126
|
+
else
|
|
127
|
+
process.env.CODEX_HOME = saved;
|
|
128
|
+
jest.resetModules();
|
|
129
|
+
}
|
|
130
|
+
});
|
|
64
131
|
// La abreviacion a `~` se probaba solo end-to-end, y en Linux eso no distingue entre
|
|
65
132
|
// "normaliza bien" y "los separadores ya coincidian". La CI de Windows encontro que no
|
|
66
133
|
// normalizaba: comparaba el prefijo ANTES de convertir separadores, asi que alla no
|
|
@@ -89,12 +156,12 @@ describe('docs/support-matrix.md refleja el codigo', () => {
|
|
|
89
156
|
expect(out).toContain('linea uno\r\nlinea dos');
|
|
90
157
|
expect(out.split('\r\n').length - 1).toBe(out.split('\n').length - 1); // ni un LF suelto
|
|
91
158
|
});
|
|
92
|
-
it('
|
|
159
|
+
it('marks an unsupported scope rather than leaving it absent', () => {
|
|
93
160
|
// La diferencia entre "no soportado" y una celda vacia es exactamente lo que el
|
|
94
161
|
// documento existe para no dejar ambiguo: Copilot no tiene scope global por
|
|
95
162
|
// decision del producto que integramos, no porque falte implementarlo.
|
|
96
163
|
const generated = (0, support_matrix_1.renderProviderTables)();
|
|
97
164
|
const copilotRow = generated.split('\n').find((l) => l.startsWith('| `copilot` |'));
|
|
98
|
-
expect(copilotRow).toContain('**
|
|
165
|
+
expect(copilotRow).toContain('**unsupported**');
|
|
99
166
|
});
|
|
100
167
|
});
|