@dalzoubi/dev-agents-sync 1.0.9 → 1.0.10
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/package.json +1 -1
- package/src/lockfile.mjs +3 -1
- package/src/writer.mjs +2 -1
- package/tests/fixtures/release-v1.0.0/copilot/prompts/define.prompt.md +17 -0
- package/tests/fixtures/release-v1.0.0/copilot/prompts/preflight.prompt.md +7 -0
- package/tests/lockfile.test.mjs +10 -0
- package/tests/paths.test.mjs +30 -2
- package/tests/update.test.mjs +113 -2
package/package.json
CHANGED
package/src/lockfile.mjs
CHANGED
|
@@ -14,7 +14,9 @@ export const DEFAULT_SOURCE = 'github:dalzoubi/dev-agents';
|
|
|
14
14
|
export const SCHEMA_URL =
|
|
15
15
|
'https://raw.githubusercontent.com/dalzoubi/dev-agents/v1/schema/lockfile.schema.json';
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
// TODO(ask-first follow-up): simplify targets — once all consumers adopt Copilot,
|
|
18
|
+
// adding 'cursor' could auto-include 'copilot' so callers don't need to list both.
|
|
19
|
+
const VALID_TARGETS = new Set(['claude', 'cursor', 'copilot']);
|
|
18
20
|
|
|
19
21
|
// Field order is load-bearing for determinism.
|
|
20
22
|
const FIELD_ORDER = [
|
package/src/writer.mjs
CHANGED
|
@@ -15,6 +15,7 @@ import { hasMarker } from './marker.mjs';
|
|
|
15
15
|
const TARGET_PREFIX = {
|
|
16
16
|
claude: '.claude',
|
|
17
17
|
cursor: '.cursor',
|
|
18
|
+
copilot: '.github',
|
|
18
19
|
};
|
|
19
20
|
|
|
20
21
|
// Subdirectories that uniquely identify a target. Used to normalize
|
|
@@ -39,7 +40,7 @@ export function normalizeFileMap(fileMap) {
|
|
|
39
40
|
for (const [key, content] of Object.entries(fileMap)) {
|
|
40
41
|
const norm = key.replace(/\\/g, '/');
|
|
41
42
|
const first = norm.split('/')[0];
|
|
42
|
-
if (first === 'claude' || first === 'cursor') {
|
|
43
|
+
if (first === 'claude' || first === 'cursor' || first === 'copilot') {
|
|
43
44
|
out[norm] = content;
|
|
44
45
|
continue;
|
|
45
46
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
mode: "agent"
|
|
3
|
+
description: "Planning and analysis agent for spec-driven development."
|
|
4
|
+
tools: ["Read", "Grep", "Glob", "WebFetch", "WebSearch", "Agent", "TodoWrite", "Write"]
|
|
5
|
+
---
|
|
6
|
+
<!-- managed-by: dev-agents-sync v1.0.0 -->
|
|
7
|
+
You are the **Define** agent: planning and analysis for spec-driven development.
|
|
8
|
+
|
|
9
|
+
## Your posture
|
|
10
|
+
|
|
11
|
+
Senior product-minded engineer. You do not write code. You produce a spec that is concrete enough for the Implement agent to execute.
|
|
12
|
+
|
|
13
|
+
## Hard rules
|
|
14
|
+
|
|
15
|
+
- **Read-only on source code.** You may only write the final spec markdown.
|
|
16
|
+
- **One question at a time.** Never ask multi-part questions.
|
|
17
|
+
- Honor the project's `CLAUDE.md` at the repo root.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
mode: "agent"
|
|
3
|
+
description: "Pre-task orientation command. Run at the start of any session."
|
|
4
|
+
tools: ["Read", "Grep", "Glob"]
|
|
5
|
+
---
|
|
6
|
+
<!-- managed-by: dev-agents-sync v1.0.0 -->
|
|
7
|
+
Read CLAUDE.md and any AGENTS.md files. Orient yourself to the codebase before taking any action.
|
package/tests/lockfile.test.mjs
CHANGED
|
@@ -86,6 +86,16 @@ describe('validateLockfile', () => {
|
|
|
86
86
|
assert.doesNotThrow(() => validateLockfile({ ...VALID_LOCKFILE, targets: ['cursor'] }));
|
|
87
87
|
});
|
|
88
88
|
|
|
89
|
+
it('accepts targets: ["copilot"]', () => {
|
|
90
|
+
assert.doesNotThrow(() => validateLockfile({ ...VALID_LOCKFILE, targets: ['copilot'] }));
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('accepts targets: ["claude", "cursor", "copilot"]', () => {
|
|
94
|
+
assert.doesNotThrow(() =>
|
|
95
|
+
validateLockfile({ ...VALID_LOCKFILE, targets: ['claude', 'cursor', 'copilot'] }),
|
|
96
|
+
);
|
|
97
|
+
});
|
|
98
|
+
|
|
89
99
|
it('rejects when source is missing', () => {
|
|
90
100
|
const bad = { ...VALID_LOCKFILE };
|
|
91
101
|
delete bad.source;
|
package/tests/paths.test.mjs
CHANGED
|
@@ -37,9 +37,9 @@ import { resolveConsumerPath } from '../src/writer.mjs';
|
|
|
37
37
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
38
38
|
const FIXTURE_DIR = path.join(__dirname, 'fixtures', 'release-v1.0.0');
|
|
39
39
|
|
|
40
|
-
function buildFixtureFileMap() {
|
|
40
|
+
function buildFixtureFileMap(targets = ['claude', 'cursor', 'copilot']) {
|
|
41
41
|
const map = {};
|
|
42
|
-
for (const target of
|
|
42
|
+
for (const target of targets) {
|
|
43
43
|
const targetDir = path.join(FIXTURE_DIR, target);
|
|
44
44
|
if (!existsSync(targetDir)) continue;
|
|
45
45
|
collectFiles(targetDir, targetDir, map, target);
|
|
@@ -113,6 +113,20 @@ describe('resolveConsumerPath', () => {
|
|
|
113
113
|
assert.equal(result, expected);
|
|
114
114
|
});
|
|
115
115
|
|
|
116
|
+
it('translates "copilot/prompts/define.prompt.md" to .github/prompts/define.prompt.md', () => {
|
|
117
|
+
const consumerRoot = '/some/repo';
|
|
118
|
+
const result = resolveConsumerPath(consumerRoot, 'copilot/prompts/define.prompt.md');
|
|
119
|
+
const expected = path.join(consumerRoot, '.github', 'prompts', 'define.prompt.md');
|
|
120
|
+
assert.equal(result, expected);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('translates "copilot/instructions/foo.instructions.md" to .github/instructions/foo.instructions.md', () => {
|
|
124
|
+
const consumerRoot = '/some/repo';
|
|
125
|
+
const result = resolveConsumerPath(consumerRoot, 'copilot/instructions/foo.instructions.md');
|
|
126
|
+
const expected = path.join(consumerRoot, '.github', 'instructions', 'foo.instructions.md');
|
|
127
|
+
assert.equal(result, expected);
|
|
128
|
+
});
|
|
129
|
+
|
|
116
130
|
it('works with a Windows-style absolute consumer root path', () => {
|
|
117
131
|
// Simulate a Windows-style path
|
|
118
132
|
const consumerRoot = 'C:\\Users\\developer\\projects\\my-app';
|
|
@@ -184,6 +198,20 @@ describe('init — platform-correct file paths', () => {
|
|
|
184
198
|
);
|
|
185
199
|
});
|
|
186
200
|
|
|
201
|
+
it('writes .github/prompts/define.prompt.md using the OS path separator', async () => {
|
|
202
|
+
await runInit(consumerDir, {
|
|
203
|
+
targets: ['copilot'],
|
|
204
|
+
fetcher: makeFixtureFetcher(),
|
|
205
|
+
availableTags: ['v1.0.0', 'v1.2.0'],
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
const expectedPath = path.join(consumerDir, '.github', 'prompts', 'define.prompt.md');
|
|
209
|
+
assert.ok(
|
|
210
|
+
existsSync(expectedPath),
|
|
211
|
+
`file must exist at OS-correct path: ${expectedPath}`,
|
|
212
|
+
);
|
|
213
|
+
});
|
|
214
|
+
|
|
187
215
|
it('written file content is readable via path.join on this platform', async () => {
|
|
188
216
|
await runInit(consumerDir, {
|
|
189
217
|
targets: ['claude'],
|
package/tests/update.test.mjs
CHANGED
|
@@ -42,9 +42,9 @@ function readFixtureContent(relativePath) {
|
|
|
42
42
|
* Builds a FileMap from the fixture directory.
|
|
43
43
|
* Keys are relative paths using forward slashes.
|
|
44
44
|
*/
|
|
45
|
-
function buildFixtureFileMap() {
|
|
45
|
+
function buildFixtureFileMap(targets = ['claude', 'cursor', 'copilot']) {
|
|
46
46
|
const map = {};
|
|
47
|
-
for (const target of
|
|
47
|
+
for (const target of targets) {
|
|
48
48
|
const targetDir = path.join(FIXTURE_DIR, target);
|
|
49
49
|
if (!existsSync(targetDir)) continue;
|
|
50
50
|
collectFiles(targetDir, targetDir, map, target);
|
|
@@ -320,3 +320,114 @@ describe('update — lockfile required', () => {
|
|
|
320
320
|
}
|
|
321
321
|
});
|
|
322
322
|
});
|
|
323
|
+
|
|
324
|
+
// ---------------------------------------------------------------------------
|
|
325
|
+
// Copilot target (.github/)
|
|
326
|
+
// ---------------------------------------------------------------------------
|
|
327
|
+
|
|
328
|
+
describe('update — copilot target writes .github/', () => {
|
|
329
|
+
let consumerDir;
|
|
330
|
+
|
|
331
|
+
beforeEach(() => {
|
|
332
|
+
consumerDir = makeTmpDir();
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
afterEach(() => {
|
|
336
|
+
rmSync(consumerDir, { recursive: true, force: true });
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
it('writes .github/prompts/ files when targets includes copilot', async () => {
|
|
340
|
+
writeLockfile(consumerDir, {
|
|
341
|
+
source: 'github:dalzoubi/dev-agents',
|
|
342
|
+
range: '^1',
|
|
343
|
+
resolvedVersion: '1.0.0',
|
|
344
|
+
targets: ['copilot'],
|
|
345
|
+
lastUpdated: '2026-04-01T00:00:00Z',
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
await runUpdate(consumerDir, {
|
|
349
|
+
fetcher: makeV120Fetcher(),
|
|
350
|
+
availableTags: ['v1.0.0', 'v1.2.0'],
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
const expectedPath = path.join(consumerDir, '.github', 'prompts', 'define.prompt.md');
|
|
354
|
+
assert.ok(existsSync(expectedPath), `.github/prompts/define.prompt.md must be written`);
|
|
355
|
+
const content = readFileSync(expectedPath, 'utf8');
|
|
356
|
+
assert.ok(hasMarker(content), 'written file must have the managed-by marker');
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
it('does not write .claude/ or .cursor/ files when targets is only copilot', async () => {
|
|
360
|
+
writeLockfile(consumerDir, {
|
|
361
|
+
source: 'github:dalzoubi/dev-agents',
|
|
362
|
+
range: '^1',
|
|
363
|
+
resolvedVersion: '1.0.0',
|
|
364
|
+
targets: ['copilot'],
|
|
365
|
+
lastUpdated: '2026-04-01T00:00:00Z',
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
await runUpdate(consumerDir, {
|
|
369
|
+
fetcher: makeV120Fetcher(),
|
|
370
|
+
availableTags: ['v1.0.0', 'v1.2.0'],
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
assert.ok(
|
|
374
|
+
!existsSync(path.join(consumerDir, '.claude')),
|
|
375
|
+
'.claude/ must not be created for copilot-only target',
|
|
376
|
+
);
|
|
377
|
+
assert.ok(
|
|
378
|
+
!existsSync(path.join(consumerDir, '.cursor')),
|
|
379
|
+
'.cursor/ must not be created for copilot-only target',
|
|
380
|
+
);
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
it('writes all three target dirs when targets includes claude, cursor, and copilot', async () => {
|
|
384
|
+
writeLockfile(consumerDir, {
|
|
385
|
+
source: 'github:dalzoubi/dev-agents',
|
|
386
|
+
range: '^1',
|
|
387
|
+
resolvedVersion: '1.0.0',
|
|
388
|
+
targets: ['claude', 'cursor', 'copilot'],
|
|
389
|
+
lastUpdated: '2026-04-01T00:00:00Z',
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
await runUpdate(consumerDir, {
|
|
393
|
+
fetcher: makeV120Fetcher(),
|
|
394
|
+
availableTags: ['v1.0.0', 'v1.2.0'],
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
assert.ok(
|
|
398
|
+
existsSync(path.join(consumerDir, '.claude', 'agents', 'define.md')),
|
|
399
|
+
'.claude/agents/define.md must exist',
|
|
400
|
+
);
|
|
401
|
+
assert.ok(
|
|
402
|
+
existsSync(path.join(consumerDir, '.cursor', 'rules', 'define.mdc')),
|
|
403
|
+
'.cursor/rules/define.mdc must exist',
|
|
404
|
+
);
|
|
405
|
+
assert.ok(
|
|
406
|
+
existsSync(path.join(consumerDir, '.github', 'prompts', 'define.prompt.md')),
|
|
407
|
+
'.github/prompts/define.prompt.md must exist',
|
|
408
|
+
);
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
it('refuses to overwrite an unmanaged .github/ file without --force', async () => {
|
|
412
|
+
writeLockfile(consumerDir, {
|
|
413
|
+
source: 'github:dalzoubi/dev-agents',
|
|
414
|
+
range: '^1',
|
|
415
|
+
resolvedVersion: '1.0.0',
|
|
416
|
+
targets: ['copilot'],
|
|
417
|
+
lastUpdated: '2026-04-01T00:00:00Z',
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
// Pre-create an unmanaged file at the target path
|
|
421
|
+
const promptsDir = path.join(consumerDir, '.github', 'prompts');
|
|
422
|
+
mkdirSync(promptsDir, { recursive: true });
|
|
423
|
+
writeFileSync(path.join(promptsDir, 'define.prompt.md'), '# Unmanaged', 'utf8');
|
|
424
|
+
|
|
425
|
+
await assert.rejects(
|
|
426
|
+
runUpdate(consumerDir, {
|
|
427
|
+
fetcher: makeV120Fetcher(),
|
|
428
|
+
availableTags: ['v1.0.0', 'v1.2.0'],
|
|
429
|
+
}),
|
|
430
|
+
/unmanaged|marker|force/i,
|
|
431
|
+
);
|
|
432
|
+
});
|
|
433
|
+
});
|