@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.
- package/README.md +39 -0
- package/package.json +1 -1
- package/src/cli.mjs +1 -0
- package/src/commands/init.mjs +34 -0
- package/src/commands/update.mjs +63 -2
- package/src/hooks.mjs +47 -0
- package/src/lockfile.mjs +20 -0
- package/src/settings.mjs +268 -0
- package/src/steering.mjs +117 -0
- package/src/writer.mjs +19 -1
- package/tests/e2e/steering-hooks-flow.test.mjs +654 -0
- package/tests/hooks-cli-install.test.mjs +1666 -0
- package/tests/steering-agent-behavior.test.mjs +491 -0
- package/tests/steering-seed.test.mjs +645 -0
|
@@ -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
|
+
});
|