@dalzoubi/dev-agents-sync 2.0.7 → 2.0.9

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.
@@ -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
+ });