@dalzoubi/dev-agents-sync 2.0.7 → 2.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -1
- package/package.json +1 -1
- package/src/lockfile.mjs +1 -1
- package/src/writer.mjs +13 -1
- package/tests/codex-cross-cutting.test.mjs +411 -0
- package/tests/codex-init-check.test.mjs +823 -0
- package/tests/codex-target.test.mjs +375 -0
- package/tests/e2e/codex-readme.test.mjs +335 -0
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tests/e2e/codex-readme.test.mjs
|
|
3
|
+
*
|
|
4
|
+
* Asserts that the consumer-facing README documents the Codex adoption flow.
|
|
5
|
+
*
|
|
6
|
+
* These tests are RED until the implement step writes the Codex section into
|
|
7
|
+
* packages/dev-agents-sync/README.md.
|
|
8
|
+
*
|
|
9
|
+
* The README (packages/dev-agents-sync/README.md) must:
|
|
10
|
+
*
|
|
11
|
+
* 1. Mention the `codex` target and `--targets codex` so consumers know the
|
|
12
|
+
* flag to run.
|
|
13
|
+
*
|
|
14
|
+
* 2. Document the output file shape: `.agents/skills/<agent>/SKILL.md` (the
|
|
15
|
+
* directory-per-skill layout that Codex discovers at runtime) AND the thin
|
|
16
|
+
* root `AGENTS.md`.
|
|
17
|
+
*
|
|
18
|
+
* 3. Explain that Codex skills are MODEL-INVOKED (not literal `/define` slash
|
|
19
|
+
* commands). Users invoke them via `/skills`, `$<name>`, or implicit
|
|
20
|
+
* selection — not by typing `/define`. The README must carry a phrase that
|
|
21
|
+
* captures this distinction (e.g. "model-invoked", "invoked by the model",
|
|
22
|
+
* "/skills", "$name" mention, or "not slash commands"/"not a slash
|
|
23
|
+
* command").
|
|
24
|
+
*
|
|
25
|
+
* 4. Document that Codex is OPT-IN ONLY — no auto-detection. The README must
|
|
26
|
+
* state (or clearly imply) that Codex is not auto-selected; the user must
|
|
27
|
+
* explicitly pass `--targets codex`.
|
|
28
|
+
*
|
|
29
|
+
* 5. Document MANUAL REMOVAL — there is no uninstall command. The consumer
|
|
30
|
+
* removes codex files manually (consistent with all targets). The README
|
|
31
|
+
* must mention manual removal or the absence of an uninstall command.
|
|
32
|
+
*
|
|
33
|
+
* 6. Document the TWO FILE SHAPES distinction: in this authoring repo the
|
|
34
|
+
* canonical `.agents/skills/*.md` are flat single files (source), while
|
|
35
|
+
* the codex output uses directory-per-skill `SKILL.md` (dist shape). This
|
|
36
|
+
* prevents a maintainer from confusing the two.
|
|
37
|
+
*
|
|
38
|
+
* Implement agent must update `packages/dev-agents-sync/README.md` before
|
|
39
|
+
* these tests go green.
|
|
40
|
+
*
|
|
41
|
+
* Proximity strategy: for multi-section assertions we look for key terms within
|
|
42
|
+
* a 600-character window of the `codex` mention(s) — tight enough to prove
|
|
43
|
+
* co-location without being brittle to minor rewording.
|
|
44
|
+
*/
|
|
45
|
+
|
|
46
|
+
import { describe, it } from 'node:test';
|
|
47
|
+
import assert from 'node:assert/strict';
|
|
48
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
49
|
+
import path from 'node:path';
|
|
50
|
+
import { fileURLToPath } from 'node:url';
|
|
51
|
+
|
|
52
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
53
|
+
// The consumer-facing README is in the CLI package (confirmed: readme-coverage.test.mjs
|
|
54
|
+
// uses the same path and the existing tests verify it documents init/update/check).
|
|
55
|
+
const README_PATH = path.join(__dirname, '..', '..', 'README.md');
|
|
56
|
+
|
|
57
|
+
function readReadme() {
|
|
58
|
+
assert.ok(
|
|
59
|
+
existsSync(README_PATH),
|
|
60
|
+
`README.md must exist at ${README_PATH}`,
|
|
61
|
+
);
|
|
62
|
+
return readFileSync(README_PATH, 'utf8');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Returns true if `needle` appears within `windowSize` characters of any
|
|
67
|
+
* occurrence of `anchor` in `haystack`.
|
|
68
|
+
*/
|
|
69
|
+
function appearsNear(haystack, anchor, needle, windowSize = 600) {
|
|
70
|
+
let searchFrom = 0;
|
|
71
|
+
while (true) {
|
|
72
|
+
const idx = haystack.indexOf(anchor, searchFrom);
|
|
73
|
+
if (idx === -1) return false;
|
|
74
|
+
const windowStart = Math.max(0, idx - windowSize / 2);
|
|
75
|
+
const windowEnd = Math.min(haystack.length, idx + windowSize / 2);
|
|
76
|
+
if (haystack.slice(windowStart, windowEnd).includes(needle)) return true;
|
|
77
|
+
searchFrom = idx + 1;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// ---------------------------------------------------------------------------
|
|
82
|
+
// Criterion 1 — README mentions the codex target and --targets codex
|
|
83
|
+
// ---------------------------------------------------------------------------
|
|
84
|
+
|
|
85
|
+
describe('README (codex): mentions the codex target', () => {
|
|
86
|
+
it('README.md contains the string "codex"', () => {
|
|
87
|
+
const content = readReadme();
|
|
88
|
+
assert.ok(
|
|
89
|
+
content.includes('codex'),
|
|
90
|
+
'README.md must mention "codex" to document the Codex target.\n' +
|
|
91
|
+
'The Codex adoption flow (init --targets codex) must be documented so\n' +
|
|
92
|
+
'consumers know how to adopt the toolkit with OpenAI Codex.',
|
|
93
|
+
);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('README.md contains "--targets codex" (the flag consumers must pass)', () => {
|
|
97
|
+
const content = readReadme();
|
|
98
|
+
assert.ok(
|
|
99
|
+
content.includes('--targets codex'),
|
|
100
|
+
'README.md must include "--targets codex" to document the opt-in invocation.\n' +
|
|
101
|
+
'Codex is not auto-detected; consumers must pass --targets codex explicitly.',
|
|
102
|
+
);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// ---------------------------------------------------------------------------
|
|
107
|
+
// Criterion 2 — README documents the output file shape
|
|
108
|
+
// ---------------------------------------------------------------------------
|
|
109
|
+
|
|
110
|
+
describe('README (codex): documents the output file shape', () => {
|
|
111
|
+
it('README.md mentions SKILL.md (the per-agent codex output file)', () => {
|
|
112
|
+
const content = readReadme();
|
|
113
|
+
assert.ok(
|
|
114
|
+
content.includes('SKILL.md'),
|
|
115
|
+
'README.md must mention "SKILL.md" to document the codex output shape.\n' +
|
|
116
|
+
'Each agent produces .agents/skills/<agent>/SKILL.md — Codex\'s discovery path.',
|
|
117
|
+
);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('README.md mentions the .agents/skills/ directory (codex discovery path)', () => {
|
|
121
|
+
const content = readReadme();
|
|
122
|
+
// Accept either the full path fragment or individual components near "codex".
|
|
123
|
+
const hasSkillsPath =
|
|
124
|
+
content.includes('.agents/skills') ||
|
|
125
|
+
appearsNear(content, 'codex', 'skills', 800);
|
|
126
|
+
assert.ok(
|
|
127
|
+
hasSkillsPath,
|
|
128
|
+
'README.md must document the .agents/skills/ output directory.\n' +
|
|
129
|
+
'Codex discovers skills at .agents/skills/<agent>/SKILL.md (cwd → repo root scan).',
|
|
130
|
+
);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('README.md mentions AGENTS.md (the thin root index file)', () => {
|
|
134
|
+
const content = readReadme();
|
|
135
|
+
// AGENTS.md is already in the README (for non-codex references), but we
|
|
136
|
+
// specifically need it documented in the context of the codex section.
|
|
137
|
+
// We assert it appears near "codex" (within 1000 chars) OR as a standalone
|
|
138
|
+
// mention near SKILL.md — either signals codex output shape coverage.
|
|
139
|
+
const nearCodex = appearsNear(content, 'codex', 'AGENTS.md', 1000);
|
|
140
|
+
const nearSkillMd = appearsNear(content, 'SKILL.md', 'AGENTS.md', 800);
|
|
141
|
+
assert.ok(
|
|
142
|
+
nearCodex || nearSkillMd,
|
|
143
|
+
'README.md must document the AGENTS.md thin root index in the codex section.\n' +
|
|
144
|
+
'The codex target emits both .agents/skills/<agent>/SKILL.md (six files) and\n' +
|
|
145
|
+
'a thin root AGENTS.md — both must be mentioned.',
|
|
146
|
+
);
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// ---------------------------------------------------------------------------
|
|
151
|
+
// Criterion 3 — README explains model-invoked (not slash commands)
|
|
152
|
+
// ---------------------------------------------------------------------------
|
|
153
|
+
|
|
154
|
+
describe('README (codex): explains model-invoked UX (not slash commands)', () => {
|
|
155
|
+
it('README.md contains a phrase explaining that Codex skills are model-invoked', () => {
|
|
156
|
+
const content = readReadme();
|
|
157
|
+
// Accept any of several phrasings that capture the model-invoked nature.
|
|
158
|
+
const phrases = [
|
|
159
|
+
'model-invoked',
|
|
160
|
+
'invoked by the model',
|
|
161
|
+
'model invoked',
|
|
162
|
+
'/skills',
|
|
163
|
+
'$name',
|
|
164
|
+
'not slash command',
|
|
165
|
+
'not a slash command',
|
|
166
|
+
'not literal',
|
|
167
|
+
'implicitly selected',
|
|
168
|
+
'implicit selection',
|
|
169
|
+
];
|
|
170
|
+
const lc = content.toLowerCase();
|
|
171
|
+
const found = phrases.some(
|
|
172
|
+
(p) => lc.includes(p.toLowerCase()),
|
|
173
|
+
);
|
|
174
|
+
assert.ok(
|
|
175
|
+
found,
|
|
176
|
+
'README.md must explain that Codex skills are model-invoked, not literal\n' +
|
|
177
|
+
'slash commands. Accepted phrases: ' + phrases.join(', ') + '.\n' +
|
|
178
|
+
'Codex users trigger an agent via /skills, $<name> mention, or implicit\n' +
|
|
179
|
+
'selection — NOT by typing /define. This distinction must be documented.',
|
|
180
|
+
);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it('README.md mentions the model-invoked UX in proximity to "codex"', () => {
|
|
184
|
+
const content = readReadme();
|
|
185
|
+
const phrases = [
|
|
186
|
+
'model-invoked',
|
|
187
|
+
'invoked by the model',
|
|
188
|
+
'/skills',
|
|
189
|
+
'$name',
|
|
190
|
+
'not slash',
|
|
191
|
+
'implicit',
|
|
192
|
+
];
|
|
193
|
+
const foundNear = phrases.some((p) =>
|
|
194
|
+
appearsNear(content, 'codex', p, 1200),
|
|
195
|
+
);
|
|
196
|
+
assert.ok(
|
|
197
|
+
foundNear,
|
|
198
|
+
'README.md must describe the model-invoked UX near the "codex" section.\n' +
|
|
199
|
+
'The distinction between Codex skills (model-invoked) and slash commands\n' +
|
|
200
|
+
'(/define, /test, etc.) must appear in the codex documentation context.',
|
|
201
|
+
);
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
// ---------------------------------------------------------------------------
|
|
206
|
+
// Criterion 4 — README documents opt-in only / no auto-detect
|
|
207
|
+
// ---------------------------------------------------------------------------
|
|
208
|
+
|
|
209
|
+
describe('README (codex): documents opt-in only — no auto-detect', () => {
|
|
210
|
+
it('README.md states codex is opt-in (not auto-detected)', () => {
|
|
211
|
+
const content = readReadme();
|
|
212
|
+
// Accept any phrasing that communicates opt-in / no auto-detect.
|
|
213
|
+
const phrases = [
|
|
214
|
+
'opt-in',
|
|
215
|
+
'opt in',
|
|
216
|
+
'not auto',
|
|
217
|
+
'no auto',
|
|
218
|
+
'must pass --targets',
|
|
219
|
+
'must be passed',
|
|
220
|
+
'explicit',
|
|
221
|
+
];
|
|
222
|
+
const lc = content.toLowerCase();
|
|
223
|
+
// The phrase must appear near "codex" to be meaningful.
|
|
224
|
+
const foundNear = phrases.some((p) =>
|
|
225
|
+
appearsNear(lc, 'codex', p.toLowerCase(), 1200),
|
|
226
|
+
);
|
|
227
|
+
assert.ok(
|
|
228
|
+
foundNear,
|
|
229
|
+
'README.md must state that the codex target is opt-in only (no auto-detection).\n' +
|
|
230
|
+
'Codex has no filesystem signal (no .codex/ dir); users must pass --targets codex\n' +
|
|
231
|
+
'explicitly. Accepted phrases near "codex": ' + phrases.join(', '),
|
|
232
|
+
);
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
// ---------------------------------------------------------------------------
|
|
237
|
+
// Criterion 5 — README documents manual removal (no uninstall command)
|
|
238
|
+
// ---------------------------------------------------------------------------
|
|
239
|
+
|
|
240
|
+
describe('README (codex): documents manual removal — no uninstall command', () => {
|
|
241
|
+
it('README.md mentions manual removal or absence of an uninstall command', () => {
|
|
242
|
+
const content = readReadme();
|
|
243
|
+
// Accept any phrasing that captures the no-cleanup contract.
|
|
244
|
+
const phrases = [
|
|
245
|
+
'manual',
|
|
246
|
+
'manually',
|
|
247
|
+
'no uninstall',
|
|
248
|
+
'no cleanup',
|
|
249
|
+
'remove manually',
|
|
250
|
+
'removed manually',
|
|
251
|
+
'does not delete',
|
|
252
|
+
'does not remove',
|
|
253
|
+
'uninstall',
|
|
254
|
+
];
|
|
255
|
+
const lc = content.toLowerCase();
|
|
256
|
+
const foundNear = phrases.some((p) =>
|
|
257
|
+
appearsNear(lc, 'codex', p.toLowerCase(), 1500),
|
|
258
|
+
);
|
|
259
|
+
assert.ok(
|
|
260
|
+
foundNear,
|
|
261
|
+
'README.md must document that codex files are removed manually (no uninstall).\n' +
|
|
262
|
+
'The CLI has no cleanup/uninstall for any target: dropping codex from targets\n' +
|
|
263
|
+
'stops future writes; file removal is manual and must be documented.\n' +
|
|
264
|
+
'Accepted phrases near "codex": ' + phrases.join(', '),
|
|
265
|
+
);
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
// ---------------------------------------------------------------------------
|
|
270
|
+
// Criterion 6 — README documents the two-file-shape distinction
|
|
271
|
+
// ---------------------------------------------------------------------------
|
|
272
|
+
|
|
273
|
+
describe('README (codex): documents the two file shapes (canonical flat vs codex output)', () => {
|
|
274
|
+
it('README.md distinguishes canonical flat .agents/skills/*.md from codex directory-per-skill output', () => {
|
|
275
|
+
const content = readReadme();
|
|
276
|
+
// The key distinction: canonical sources in THIS repo are flat single files
|
|
277
|
+
// (.agents/skills/<name>.md). The codex TARGET OUTPUT is directory-per-skill
|
|
278
|
+
// (.agents/skills/<agent>/SKILL.md). Both shapes share the .agents/skills/ path
|
|
279
|
+
// prefix, so the distinction is load-bearing for maintainers.
|
|
280
|
+
//
|
|
281
|
+
// We assert the README mentions at least one of these distinguishing phrases:
|
|
282
|
+
// - "flat" (contrasting the flat canonical vs dir-per-skill shape)
|
|
283
|
+
// - "directory-per-skill" or "dir-per-skill"
|
|
284
|
+
// - "canonical source" in proximity to "skill" (documenting the authoring shape)
|
|
285
|
+
// - "output only" or "output shape" (the dir shape is output, not authoring)
|
|
286
|
+
// - "authoring" near "skill" (the flat files are for authoring)
|
|
287
|
+
// - both "SKILL.md" AND ".md" near each other with a distinction word
|
|
288
|
+
//
|
|
289
|
+
// Accept any phrasing that conveys the two shapes.
|
|
290
|
+
const phrases = [
|
|
291
|
+
'flat',
|
|
292
|
+
'directory-per-skill',
|
|
293
|
+
'dir-per-skill',
|
|
294
|
+
'output only',
|
|
295
|
+
'output shape',
|
|
296
|
+
'authoring',
|
|
297
|
+
'canonical source',
|
|
298
|
+
'single file',
|
|
299
|
+
];
|
|
300
|
+
const lc = content.toLowerCase();
|
|
301
|
+
const foundNear = phrases.some((p) =>
|
|
302
|
+
// Search near "skill" broadly since the shape discussion may not be next to "codex"
|
|
303
|
+
appearsNear(lc, 'skill', p.toLowerCase(), 1500) ||
|
|
304
|
+
appearsNear(lc, 'codex', p.toLowerCase(), 1500),
|
|
305
|
+
);
|
|
306
|
+
assert.ok(
|
|
307
|
+
foundNear,
|
|
308
|
+
'README.md must document the two file shapes:\n' +
|
|
309
|
+
' - Canonical authoring: .agents/skills/*.md (flat single files in this repo)\n' +
|
|
310
|
+
' - Codex output: .agents/skills/<agent>/SKILL.md (directory-per-skill)\n' +
|
|
311
|
+
'Without this distinction, a maintainer may confuse the two.\n' +
|
|
312
|
+
'Accepted phrases near "skill" or "codex": ' + phrases.join(', '),
|
|
313
|
+
);
|
|
314
|
+
});
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
// ---------------------------------------------------------------------------
|
|
318
|
+
// Criterion 7 — Codex section is near existing init/update/check documentation
|
|
319
|
+
// (confirms Codex adoption is integrated into the Quick Start flow)
|
|
320
|
+
// ---------------------------------------------------------------------------
|
|
321
|
+
|
|
322
|
+
describe('README (codex): codex adoption is documented near the Quick Start / init section', () => {
|
|
323
|
+
it('"codex" appears within 3000 characters of the "init" section documentation', () => {
|
|
324
|
+
const content = readReadme();
|
|
325
|
+
// The init → update → check flow is the consumer Quick Start. The codex
|
|
326
|
+
// adoption path must be discoverable from the same section.
|
|
327
|
+
const foundNear = appearsNear(content, 'init', 'codex', 3000);
|
|
328
|
+
assert.ok(
|
|
329
|
+
foundNear,
|
|
330
|
+
'README.md must document the codex target near the init section.\n' +
|
|
331
|
+
'Consumers reading the Quick Start (init → update → check) must see\n' +
|
|
332
|
+
'"codex" within the same broad section — not buried far from the flow.',
|
|
333
|
+
);
|
|
334
|
+
});
|
|
335
|
+
});
|