@dalzoubi/dev-agents-sync 2.0.16 → 2.0.18

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,491 @@
1
+ /**
2
+ * tests/steering-agent-behavior.test.mjs
3
+ *
4
+ * Slice: S2 — Steering agent behavior
5
+ * Spec: docs/specs/260616-agent-steering-and-hooks/
6
+ *
7
+ * Asserts the content contract that every S2 behavior criterion introduces into
8
+ * the canonical agent prompts under .agents/agents/. These tests FAIL before
9
+ * the implement agent edits the prompts and PASS after.
10
+ *
11
+ * Behavior anchors (one named test per criterion — §7):
12
+ *
13
+ * C1. Define reads steering — .agents/agents/define.md instructs reading
14
+ * steering/*.md (the steering files) at session start.
15
+ * C2. Define writes decisions — .agents/agents/define.md instructs appending
16
+ * one dated, one-line decision to steering/decisions.md at spec finalize
17
+ * (append-only, gated).
18
+ * C3. Implement reads steering — .agents/agents/implement.md instructs reading
19
+ * steering at session start.
20
+ * C4. Implement writes gotchas — .agents/agents/implement.md instructs
21
+ * appending dated gotchas/constraints discovered during build.
22
+ * C5a. Analyze is read-only on steering — .agents/agents/analyze.md does NOT
23
+ * instruct appending/writing to steering (may instruct reading).
24
+ * C5b. Test is read-only on steering — .agents/agents/test.md does NOT instruct
25
+ * appending/writing to steering (may instruct reading).
26
+ * C5c. Validate is read-only on steering — .agents/agents/validate.md does NOT
27
+ * instruct appending/writing to steering (may instruct reading).
28
+ * C5d. Supervise is read-only on steering — .agents/agents/supervise.md does NOT
29
+ * instruct appending/writing to steering (may instruct reading).
30
+ * C6. Shared read mechanism exists — there is a canonical source for the
31
+ * "read steering/*.md at session start" instruction (either a shared rule
32
+ * under .agents/rules/ or inline text in define.md + implement.md that
33
+ * references steering/ at session start).
34
+ * C7a. Mirror parity (define) — .claude/agents/define.md contains the same
35
+ * steering read instruction as the canonical source.
36
+ * C7b. Mirror parity (implement) — .claude/agents/implement.md contains the
37
+ * same steering write/gotcha instruction as the canonical source.
38
+ *
39
+ * Assertions match on stable substrings (e.g. "steering/decisions.md",
40
+ * "steering/", "session start", "append") rather than exact sentences, but are
41
+ * strict enough to fail when the instruction is absent.
42
+ *
43
+ * Test runner: node --test (Node built-in, >= 20)
44
+ * No third-party dependencies.
45
+ */
46
+
47
+ import { describe, it, before } from 'node:test';
48
+ import assert from 'node:assert/strict';
49
+ import { readFileSync, existsSync } from 'node:fs';
50
+ import { fileURLToPath } from 'node:url';
51
+ import path from 'node:path';
52
+
53
+ // ---------------------------------------------------------------------------
54
+ // Resolve paths relative to this file's location.
55
+ // Repo layout: packages/dev-agents-sync/tests/ -> repo root is 3 levels up.
56
+ // ---------------------------------------------------------------------------
57
+
58
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
59
+ const REPO_ROOT = path.resolve(__dirname, '../../..');
60
+
61
+ const AGENTS_DIR = path.join(REPO_ROOT, '.agents', 'agents');
62
+ const RULES_DIR = path.join(REPO_ROOT, '.agents', 'rules');
63
+ const MIRROR_DIR = path.join(REPO_ROOT, '.claude', 'agents');
64
+
65
+ // ---------------------------------------------------------------------------
66
+ // Load canonical agent files once.
67
+ // ---------------------------------------------------------------------------
68
+
69
+ let defineContent;
70
+ let implementContent;
71
+ let analyzeContent;
72
+ let testContent;
73
+ let validateContent;
74
+ let superviseContent;
75
+
76
+ // Mirror files — loaded lazily in tests that need parity checks.
77
+ let mirrorDefineContent;
78
+ let mirrorImplementContent;
79
+
80
+ before(() => {
81
+ defineContent = readFileSync(path.join(AGENTS_DIR, 'define.md'), 'utf8');
82
+ implementContent = readFileSync(path.join(AGENTS_DIR, 'implement.md'), 'utf8');
83
+ analyzeContent = readFileSync(path.join(AGENTS_DIR, 'analyze.md'), 'utf8');
84
+ testContent = readFileSync(path.join(AGENTS_DIR, 'test.md'), 'utf8');
85
+ validateContent = readFileSync(path.join(AGENTS_DIR, 'validate.md'), 'utf8');
86
+ superviseContent = readFileSync(path.join(AGENTS_DIR, 'supervise.md'), 'utf8');
87
+
88
+ mirrorDefineContent = existsSync(path.join(MIRROR_DIR, 'define.md'))
89
+ ? readFileSync(path.join(MIRROR_DIR, 'define.md'), 'utf8')
90
+ : '';
91
+ mirrorImplementContent = existsSync(path.join(MIRROR_DIR, 'implement.md'))
92
+ ? readFileSync(path.join(MIRROR_DIR, 'implement.md'), 'utf8')
93
+ : '';
94
+ });
95
+
96
+ // ---------------------------------------------------------------------------
97
+ // Helpers
98
+ // ---------------------------------------------------------------------------
99
+
100
+ /**
101
+ * Returns true if the content contains an instruction to read steering files
102
+ * at session start. Matches on stable substrings: "steering/" and "session start"
103
+ * both appear in close textual proximity, OR explicit "steering/*.md" reference
104
+ * paired with "session start".
105
+ */
106
+ function instructsReadSteeringAtStart(content) {
107
+ // Direct conjunction: both "steering/" and "session start" anywhere in the file.
108
+ // The spec says agents read steering/*.md at session start — both tokens must be present.
109
+ const hasSteeringRef = /steering\//i.test(content);
110
+ const hasSessionStart = /session\s+start/i.test(content);
111
+ return hasSteeringRef && hasSessionStart;
112
+ }
113
+
114
+ /**
115
+ * Returns true if content contains an instruction to APPEND to steering/decisions.md.
116
+ * Matches on "append" (or synonym) plus "steering/decisions.md" or "decisions.md".
117
+ */
118
+ function instructsAppendToDecisions(content) {
119
+ const hasDecisionsRef = /steering\/decisions\.md/i.test(content);
120
+ const hasAppend = /\bappend\b/i.test(content);
121
+ return hasDecisionsRef && hasAppend;
122
+ }
123
+
124
+ /**
125
+ * Returns true if content contains an instruction to APPEND gotchas/constraints
126
+ * to a steering file. Matches on "append" plus "steering/" plus a gotcha/constraint signal.
127
+ */
128
+ function instructsAppendGotchas(content) {
129
+ const hasSteeringRef = /steering\//i.test(content);
130
+ const hasAppend = /\bappend\b/i.test(content);
131
+ const hasGotchaSignal =
132
+ /gotcha/i.test(content) ||
133
+ /constraint/i.test(content) ||
134
+ /non[- ]obvious/i.test(content) ||
135
+ /discovered during build/i.test(content);
136
+ return hasSteeringRef && hasAppend && hasGotchaSignal;
137
+ }
138
+
139
+ /**
140
+ * Returns true if content contains an append-to-steering instruction.
141
+ * Used to assert ABSENCE in read-only agents.
142
+ */
143
+ function containsAnySteeringWriteInstruction(content) {
144
+ // "append" + "steering/" is the signal for a write instruction.
145
+ // Pure read references (e.g. "read steering/") are acceptable.
146
+ return /\bappend\b.*steering\//i.test(content) || /steering\/.*\bappend\b/i.test(content);
147
+ }
148
+
149
+ // ---------------------------------------------------------------------------
150
+ // C1 — Define reads steering at session start
151
+ // ---------------------------------------------------------------------------
152
+
153
+ describe('S2 — C1: define.md instructs reading steering/*.md at session start', () => {
154
+ it('define.md references steering/ AND session start (read-on-start instruction present)', () => {
155
+ assert.ok(
156
+ instructsReadSteeringAtStart(defineContent),
157
+ [
158
+ 'define.md must instruct the agent to read steering/*.md at session start.',
159
+ 'Expected both "steering/" and "session start" to be present in .agents/agents/define.md.',
160
+ 'Neither token was found — the read-on-start instruction is absent.',
161
+ ].join(' '),
162
+ );
163
+ });
164
+
165
+ it('define.md explicitly references steering/decisions.md (the primary steering file)', () => {
166
+ assert.ok(
167
+ /steering\/decisions\.md/i.test(defineContent),
168
+ [
169
+ 'define.md must reference "steering/decisions.md" so it is clear which',
170
+ 'file the Define agent appends decisions to.',
171
+ 'The reference is absent from .agents/agents/define.md.',
172
+ ].join(' '),
173
+ );
174
+ });
175
+ });
176
+
177
+ // ---------------------------------------------------------------------------
178
+ // C2 — Define appends one dated decision to steering/decisions.md at finalize
179
+ // ---------------------------------------------------------------------------
180
+
181
+ describe('S2 — C2: define.md instructs appending a dated decision to steering/decisions.md at spec finalize', () => {
182
+ it('define.md instructs appending to steering/decisions.md', () => {
183
+ assert.ok(
184
+ instructsAppendToDecisions(defineContent),
185
+ [
186
+ 'define.md must instruct the Define agent to append a decision to',
187
+ '"steering/decisions.md".',
188
+ 'Expected both "append" and "steering/decisions.md" in .agents/agents/define.md.',
189
+ 'At least one is absent — the append-decision instruction is missing.',
190
+ ].join(' '),
191
+ );
192
+ });
193
+
194
+ it('define.md gates the steering/decisions.md append to spec finalize (not just any time)', () => {
195
+ // The spec says appending to steering/decisions.md happens "at spec finalize" — the finalize
196
+ // gate must appear in the context of the steering decisions instruction, not just anywhere
197
+ // in the file (define.md already contains "finalize" for other purposes).
198
+ // We require the substring "steering/decisions.md" to appear AND a finalize gate to appear
199
+ // within the same section (within 500 characters) — or a phrase that combines both concepts.
200
+ const decisionsIdx = defineContent.indexOf('steering/decisions.md');
201
+ if (decisionsIdx === -1) {
202
+ // steering/decisions.md is absent entirely; the test for C2 will catch this.
203
+ // This test adds the additional gate constraint, so also fail here.
204
+ assert.fail(
205
+ 'define.md must reference "steering/decisions.md" before the finalize gate can be checked. ' +
206
+ 'The reference is absent from .agents/agents/define.md.',
207
+ );
208
+ }
209
+
210
+ // Extract a window of 600 chars around the decisions reference to check for finalize signal.
211
+ const window = defineContent.slice(
212
+ Math.max(0, decisionsIdx - 200),
213
+ Math.min(defineContent.length, decisionsIdx + 400),
214
+ );
215
+
216
+ const hasFinalize =
217
+ /finali[sz]e/i.test(window) ||
218
+ /spec\s+(?:is\s+)?(?:approved|confirmed)/i.test(window) ||
219
+ /at\s+(?:spec\s+)?(?:approv|confirm|finali)/i.test(window);
220
+
221
+ assert.ok(
222
+ hasFinalize,
223
+ [
224
+ 'define.md must gate the steering/decisions.md append to spec finalize.',
225
+ 'Expected a "finalize" (or "approved"/"confirmed") signal within 600 characters',
226
+ 'of the "steering/decisions.md" reference in .agents/agents/define.md.',
227
+ 'The finalize gate is absent near the append instruction.',
228
+ ].join(' '),
229
+ );
230
+ });
231
+
232
+ it('define.md specifies a dated format for decisions (YYYY-MM-DD or "dated") in the steering context', () => {
233
+ // The spec says decisions are "date-stamped, one-line". The dated-format signal must
234
+ // appear in the context of the steering/decisions instruction, not just anywhere in the file
235
+ // (define.md already contains date-like strings in folder-naming examples unrelated to steering).
236
+ const decisionsIdx = defineContent.indexOf('steering/decisions.md');
237
+ if (decisionsIdx === -1) {
238
+ assert.fail(
239
+ 'define.md must reference "steering/decisions.md" before a dated-format check can be applied. ' +
240
+ 'The reference is absent from .agents/agents/define.md.',
241
+ );
242
+ }
243
+
244
+ const window = defineContent.slice(
245
+ Math.max(0, decisionsIdx - 200),
246
+ Math.min(defineContent.length, decisionsIdx + 400),
247
+ );
248
+
249
+ const hasDatedFormat =
250
+ /\bdated\b/i.test(window) ||
251
+ /YYYY[- ]MM[- ]DD/i.test(window) ||
252
+ /date[- ]stamp/i.test(window) ||
253
+ /date\s+stamp/i.test(window);
254
+
255
+ assert.ok(
256
+ hasDatedFormat,
257
+ [
258
+ 'define.md must specify that appended decisions are dated',
259
+ '(e.g. "YYYY-MM-DD" format, "dated", or "date-stamped") in the steering context.',
260
+ 'Expected the dated-format signal within 600 characters of "steering/decisions.md"',
261
+ 'in .agents/agents/define.md. The dated-format requirement is absent.',
262
+ ].join(' '),
263
+ );
264
+ });
265
+ });
266
+
267
+ // ---------------------------------------------------------------------------
268
+ // C3 — Implement reads steering at session start
269
+ // ---------------------------------------------------------------------------
270
+
271
+ describe('S2 — C3: implement.md instructs reading steering at session start', () => {
272
+ it('implement.md references steering/ AND session start (read-on-start instruction present)', () => {
273
+ assert.ok(
274
+ instructsReadSteeringAtStart(implementContent),
275
+ [
276
+ 'implement.md must instruct the agent to read steering files at session start.',
277
+ 'Expected both "steering/" and "session start" in .agents/agents/implement.md.',
278
+ 'At least one token is absent — the read-on-start instruction is missing.',
279
+ ].join(' '),
280
+ );
281
+ });
282
+ });
283
+
284
+ // ---------------------------------------------------------------------------
285
+ // C4 — Implement appends dated gotchas discovered during build
286
+ // ---------------------------------------------------------------------------
287
+
288
+ describe('S2 — C4: implement.md instructs appending dated gotchas/constraints to steering during build', () => {
289
+ it('implement.md instructs appending gotchas/constraints to a steering file', () => {
290
+ assert.ok(
291
+ instructsAppendGotchas(implementContent),
292
+ [
293
+ 'implement.md must instruct the Implement agent to append gotchas or',
294
+ 'non-obvious constraints discovered during build to a steering file.',
295
+ 'Expected "append" + "steering/" + a gotcha signal ("gotcha", "constraint",',
296
+ '"non-obvious", or "discovered during build") in .agents/agents/implement.md.',
297
+ 'The gotcha-append instruction is absent.',
298
+ ].join(' '),
299
+ );
300
+ });
301
+
302
+ it('implement.md specifies that gotcha appends are dated', () => {
303
+ // The spec says "date-stamped, one-line rationale" — must see dated signal near steering append.
304
+ const hasDatedSignal =
305
+ /dated/i.test(implementContent) ||
306
+ /date[- ]stamp/i.test(implementContent) ||
307
+ /YYYY[- ]MM[- ]DD/i.test(implementContent);
308
+
309
+ assert.ok(
310
+ hasDatedSignal,
311
+ [
312
+ 'implement.md must specify that gotcha appends are dated',
313
+ '(e.g. "dated", "date-stamped", or "YYYY-MM-DD").',
314
+ 'The dated-append requirement is absent from .agents/agents/implement.md.',
315
+ ].join(' '),
316
+ );
317
+ });
318
+ });
319
+
320
+ // ---------------------------------------------------------------------------
321
+ // C5a — Analyze is read-only on steering (no append instruction)
322
+ // ---------------------------------------------------------------------------
323
+
324
+ describe('S2 — C5a: analyze.md does NOT instruct appending/writing to steering', () => {
325
+ it('analyze.md contains no append-to-steering instruction', () => {
326
+ assert.ok(
327
+ !containsAnySteeringWriteInstruction(analyzeContent),
328
+ [
329
+ 'analyze.md must NOT instruct the Analyze agent to append to steering files.',
330
+ 'Analyze is read-only on steering.',
331
+ 'Found an append-to-steering instruction in .agents/agents/analyze.md.',
332
+ ].join(' '),
333
+ );
334
+ });
335
+ });
336
+
337
+ // ---------------------------------------------------------------------------
338
+ // C5b — Test agent is read-only on steering (no append instruction)
339
+ // ---------------------------------------------------------------------------
340
+
341
+ describe('S2 — C5b: test.md does NOT instruct appending/writing to steering', () => {
342
+ it('test.md contains no append-to-steering instruction', () => {
343
+ assert.ok(
344
+ !containsAnySteeringWriteInstruction(testContent),
345
+ [
346
+ 'test.md must NOT instruct the Test agent to append to steering files.',
347
+ 'Test is read-only on steering.',
348
+ 'Found an append-to-steering instruction in .agents/agents/test.md.',
349
+ ].join(' '),
350
+ );
351
+ });
352
+ });
353
+
354
+ // ---------------------------------------------------------------------------
355
+ // C5c — Validate is read-only on steering (no append instruction)
356
+ // ---------------------------------------------------------------------------
357
+
358
+ describe('S2 — C5c: validate.md does NOT instruct appending/writing to steering', () => {
359
+ it('validate.md contains no append-to-steering instruction', () => {
360
+ assert.ok(
361
+ !containsAnySteeringWriteInstruction(validateContent),
362
+ [
363
+ 'validate.md must NOT instruct the Validate agent to append to steering files.',
364
+ 'Validate is read-only on steering.',
365
+ 'Found an append-to-steering instruction in .agents/agents/validate.md.',
366
+ ].join(' '),
367
+ );
368
+ });
369
+ });
370
+
371
+ // ---------------------------------------------------------------------------
372
+ // C5d — Supervise is read-only on steering (no append instruction)
373
+ // ---------------------------------------------------------------------------
374
+
375
+ describe('S2 — C5d: supervise.md does NOT instruct appending/writing to steering', () => {
376
+ it('supervise.md contains no append-to-steering instruction', () => {
377
+ assert.ok(
378
+ !containsAnySteeringWriteInstruction(superviseContent),
379
+ [
380
+ 'supervise.md must NOT instruct the Supervise agent to append to steering files.',
381
+ 'Supervise is read-only on steering.',
382
+ 'Found an append-to-steering instruction in .agents/agents/supervise.md.',
383
+ ].join(' '),
384
+ );
385
+ });
386
+ });
387
+
388
+ // ---------------------------------------------------------------------------
389
+ // C6 — Shared read mechanism: "read steering/*.md at session start" exists in
390
+ // canonical sources (either a shared rule or inline in both define + implement)
391
+ // ---------------------------------------------------------------------------
392
+
393
+ describe('S2 — C6: shared read-steering mechanism present in canonical source', () => {
394
+ it('a steering-read rule exists in .agents/rules/ OR both define.md and implement.md instruct reading steering at session start', () => {
395
+ // Acceptable mechanism 1: a dedicated rule file under .agents/rules/ that
396
+ // mentions steering/ and session start. Check well-known candidate names.
397
+ const candidateRuleFiles = [
398
+ path.join(RULES_DIR, 'steering.md'),
399
+ path.join(RULES_DIR, 'steering-read.md'),
400
+ path.join(RULES_DIR, 'read-steering.md'),
401
+ ];
402
+
403
+ let sharedRuleExists = false;
404
+ for (const ruleFile of candidateRuleFiles) {
405
+ if (existsSync(ruleFile)) {
406
+ const ruleContent = readFileSync(ruleFile, 'utf8');
407
+ if (instructsReadSteeringAtStart(ruleContent)) {
408
+ sharedRuleExists = true;
409
+ break;
410
+ }
411
+ }
412
+ }
413
+
414
+ // Acceptable mechanism 2: both define.md and implement.md carry the
415
+ // read-on-start instruction inline (shared by repetition in both entry-point agents).
416
+ const bothAgentsHaveReadInstruction =
417
+ instructsReadSteeringAtStart(defineContent) &&
418
+ instructsReadSteeringAtStart(implementContent);
419
+
420
+ assert.ok(
421
+ sharedRuleExists || bothAgentsHaveReadInstruction,
422
+ [
423
+ 'A shared read-steering mechanism must exist in canonical source.',
424
+ 'Expected EITHER (a) a rule file under .agents/rules/ (e.g. steering.md or steering-read.md)',
425
+ 'that instructs reading steering/*.md at session start,',
426
+ 'OR (b) both .agents/agents/define.md and .agents/agents/implement.md',
427
+ 'to each instruct reading steering files at session start.',
428
+ 'Neither condition is satisfied — the shared read mechanism is absent.',
429
+ ].join(' '),
430
+ );
431
+ });
432
+ });
433
+
434
+ // ---------------------------------------------------------------------------
435
+ // C7a — Mirror parity: .claude/agents/define.md contains the steering read instruction
436
+ // ---------------------------------------------------------------------------
437
+
438
+ describe('S2 — C7a: mirror parity — .claude/agents/define.md reflects steering read instruction', () => {
439
+ it('.claude/agents/define.md contains a steering/ reference (mirrors have been rebuilt)', () => {
440
+ assert.ok(
441
+ /steering\//i.test(mirrorDefineContent),
442
+ [
443
+ '.claude/agents/define.md must contain a "steering/" reference,',
444
+ 'proving the mirror was rebuilt after the canonical .agents/agents/define.md was edited.',
445
+ 'The "steering/" reference is absent from the mirror.',
446
+ 'Run `npm run build` to regenerate the committed mirror from the canonical source.',
447
+ ].join(' '),
448
+ );
449
+ });
450
+
451
+ it('.claude/agents/define.md references steering/decisions.md (append instruction mirrored)', () => {
452
+ assert.ok(
453
+ /steering\/decisions\.md/i.test(mirrorDefineContent),
454
+ [
455
+ '.claude/agents/define.md must contain "steering/decisions.md".',
456
+ 'The append-to-decisions instruction is missing from the mirror.',
457
+ 'Canonical source may have the instruction but the mirror has not been rebuilt.',
458
+ 'Run `npm run build` to regenerate.',
459
+ ].join(' '),
460
+ );
461
+ });
462
+ });
463
+
464
+ // ---------------------------------------------------------------------------
465
+ // C7b — Mirror parity: .claude/agents/implement.md contains the steering write instruction
466
+ // ---------------------------------------------------------------------------
467
+
468
+ describe('S2 — C7b: mirror parity — .claude/agents/implement.md reflects steering write instruction', () => {
469
+ it('.claude/agents/implement.md contains a steering/ reference (mirrors have been rebuilt)', () => {
470
+ assert.ok(
471
+ /steering\//i.test(mirrorImplementContent),
472
+ [
473
+ '.claude/agents/implement.md must contain a "steering/" reference,',
474
+ 'proving the mirror was rebuilt after the canonical .agents/agents/implement.md was edited.',
475
+ 'The "steering/" reference is absent from the mirror.',
476
+ 'Run `npm run build` to regenerate the committed mirror from the canonical source.',
477
+ ].join(' '),
478
+ );
479
+ });
480
+
481
+ it('.claude/agents/implement.md contains an append instruction for steering (gotcha instruction mirrored)', () => {
482
+ assert.ok(
483
+ /\bappend\b/i.test(mirrorImplementContent) && /steering\//i.test(mirrorImplementContent),
484
+ [
485
+ '.claude/agents/implement.md must contain both "append" and "steering/".',
486
+ 'The gotcha-append instruction is missing from the mirror.',
487
+ 'Run `npm run build` after editing .agents/agents/implement.md.',
488
+ ].join(' '),
489
+ );
490
+ });
491
+ });