@abarcenas/ai-workflow-template 1.48.0 → 2.0.0
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/.agents/instructions/knowledgebase.instructions.md +2 -2
- package/.agents/instructions/learned-knowledge.instructions.md +47 -2
- package/.agents/instructions/memory-schema.instructions.md +1 -1
- package/.github/workflows/npm-publish.yml +69 -1
- package/.opencode/agents/implementer.md +3 -1
- package/.opencode/agents/unit-tester.md +21 -2
- package/.opencode/plugins/graphify.js +22 -0
- package/LICENSE +15 -0
- package/README.md +56 -22
- package/memory-bank/.vocabulary.json +3 -0
- package/opencode.mcp.example.json +13 -0
- package/package.json +20 -10
- package/scripts/bump-version.js +18 -2
- package/scripts/mcp/playwright-mcp-launcher.js +45 -2
- package/scripts/bump-version.test.js +0 -238
- package/scripts/knowledgebase-cli.test.js +0 -576
- package/scripts/knowledgebase-index.test.js +0 -759
- package/scripts/mcp/playwright-mcp-launcher.test.js +0 -217
- package/scripts/mcp-knowledgebase-server.test.js +0 -745
- package/scripts/mcp-memory-server.test.js +0 -398
- package/scripts/memory-cli.test.js +0 -297
- package/scripts/memory-index.test.js +0 -1173
- package/scripts/normalize-memory.test.js +0 -753
- package/scripts/setup/discover.test.js +0 -530
- package/scripts/setup/hooks.test.js +0 -722
- package/scripts/setup/husky-init.test.js +0 -311
- package/scripts/setup/index.test.js +0 -1021
- package/scripts/setup/knowledgebase.test.js +0 -352
- package/scripts/setup/prepare.test.js +0 -718
- package/scripts/setup/skills-phase.test.js +0 -1274
- package/scripts/setup/sync-phase.test.js +0 -373
- package/scripts/setup/ui.test.js +0 -579
- package/scripts/setup/utils.test.js +0 -586
- package/scripts/sync-unit.test.js +0 -1881
- package/scripts/sync.test.js +0 -472
- package/scripts/validate-memory-schema.test.js +0 -328
- package/scripts/vocab-sync.test.js +0 -902
package/scripts/sync.test.js
DELETED
|
@@ -1,472 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, afterAll } from 'vitest'
|
|
2
|
-
import { spawnSync } from 'child_process'
|
|
3
|
-
import { createHash } from 'crypto'
|
|
4
|
-
import {
|
|
5
|
-
copyFileSync,
|
|
6
|
-
existsSync,
|
|
7
|
-
mkdirSync,
|
|
8
|
-
readFileSync,
|
|
9
|
-
writeFileSync,
|
|
10
|
-
rmSync,
|
|
11
|
-
mkdtempSync,
|
|
12
|
-
statSync,
|
|
13
|
-
} from 'fs'
|
|
14
|
-
import { join, dirname } from 'path'
|
|
15
|
-
import { tmpdir } from 'os'
|
|
16
|
-
|
|
17
|
-
const projectRoot = process.cwd()
|
|
18
|
-
const tempDirs = []
|
|
19
|
-
|
|
20
|
-
function createTempDir() {
|
|
21
|
-
const tmpDir = mkdtempSync(join(tmpdir(), 'sync-test-'))
|
|
22
|
-
tempDirs.push(tmpDir)
|
|
23
|
-
return tmpDir
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function runSync(tempDir, args = []) {
|
|
27
|
-
return spawnSync('node', ['scripts/sync.js', ...args], {
|
|
28
|
-
cwd: projectRoot,
|
|
29
|
-
env: { ...process.env, INIT_CWD: tempDir },
|
|
30
|
-
encoding: 'utf8',
|
|
31
|
-
})
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
afterAll(() => {
|
|
35
|
-
for (const dir of tempDirs) {
|
|
36
|
-
rmSync(dir, { recursive: true, force: true })
|
|
37
|
-
}
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
function sha256(contents) {
|
|
41
|
-
return createHash('sha256').update(contents).digest('hex')
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function writeFixtureFile(baseDir, relativePath, contents) {
|
|
45
|
-
const filePath = join(baseDir, relativePath)
|
|
46
|
-
mkdirSync(dirname(filePath), { recursive: true })
|
|
47
|
-
writeFileSync(filePath, contents)
|
|
48
|
-
return filePath
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// Builds a throwaway "published package" containing its own copy of sync.js, so
|
|
52
|
-
// prune tests never depend on the real repository's file contents.
|
|
53
|
-
function createFakePackage({ agents = {} } = {}) {
|
|
54
|
-
const packageDir = createTempDir()
|
|
55
|
-
writeFileSync(join(packageDir, 'package.json'), '{ "type": "module" }\n')
|
|
56
|
-
mkdirSync(join(packageDir, 'scripts'), { recursive: true })
|
|
57
|
-
copyFileSync(
|
|
58
|
-
join(projectRoot, 'scripts', 'sync.js'),
|
|
59
|
-
join(packageDir, 'scripts', 'sync.js'),
|
|
60
|
-
)
|
|
61
|
-
mkdirSync(join(packageDir, '.agents'), { recursive: true })
|
|
62
|
-
|
|
63
|
-
for (const [relativePath, contents] of Object.entries(agents)) {
|
|
64
|
-
writeFixtureFile(packageDir, join('.agents', relativePath), contents)
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
return packageDir
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function runSyncFrom(packageDir, consumerDir, args = []) {
|
|
71
|
-
return spawnSync(
|
|
72
|
-
'node',
|
|
73
|
-
[join(packageDir, 'scripts', 'sync.js'), ...args],
|
|
74
|
-
{
|
|
75
|
-
cwd: packageDir,
|
|
76
|
-
env: { ...process.env, INIT_CWD: consumerDir },
|
|
77
|
-
encoding: 'utf8',
|
|
78
|
-
},
|
|
79
|
-
)
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function readConsumerManifest(consumerDir) {
|
|
83
|
-
return JSON.parse(
|
|
84
|
-
readFileSync(join(consumerDir, '.agents-sync-manifest.json'), 'utf8'),
|
|
85
|
-
)
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
describe('sync utility functions', () => {
|
|
89
|
-
it('should validate basic assertions work', () => {
|
|
90
|
-
expect(1 + 1).toBe(2)
|
|
91
|
-
})
|
|
92
|
-
|
|
93
|
-
it('should handle string operations', () => {
|
|
94
|
-
const result = 'ai-workflow-template'.split('-').join(' ')
|
|
95
|
-
expect(result).toBe('ai workflow template')
|
|
96
|
-
})
|
|
97
|
-
})
|
|
98
|
-
|
|
99
|
-
describe('__scripts__ sync behavior', () => {
|
|
100
|
-
const scriptsToSync = [
|
|
101
|
-
'memory-cli.js',
|
|
102
|
-
'memory-index.js',
|
|
103
|
-
'bump-version.js',
|
|
104
|
-
'validate-memory-schema.js',
|
|
105
|
-
'vocab-sync.js',
|
|
106
|
-
'mcp-memory-server.js',
|
|
107
|
-
'mcp/playwright-mcp-launcher.js',
|
|
108
|
-
]
|
|
109
|
-
|
|
110
|
-
it('should copy scripts when they do not exist in consumer project', () => {
|
|
111
|
-
const tempDir = createTempDir()
|
|
112
|
-
|
|
113
|
-
// Create a manifest with __scripts__ entries (initial tracking)
|
|
114
|
-
const manifest = {
|
|
115
|
-
version: 1,
|
|
116
|
-
files: Object.fromEntries(
|
|
117
|
-
scriptsToSync.map((s) => [`__scripts__/${s}`, null]),
|
|
118
|
-
),
|
|
119
|
-
}
|
|
120
|
-
writeFileSync(
|
|
121
|
-
join(tempDir, '.agents-sync-manifest.json'),
|
|
122
|
-
JSON.stringify(manifest, null, 2),
|
|
123
|
-
)
|
|
124
|
-
|
|
125
|
-
const result = runSync(tempDir)
|
|
126
|
-
expect(result.status).toBe(0)
|
|
127
|
-
|
|
128
|
-
// Verify all 7 script files were created in the consumer's scripts/ dir
|
|
129
|
-
for (const script of scriptsToSync) {
|
|
130
|
-
const targetFile = join(tempDir, 'scripts', script)
|
|
131
|
-
expect(
|
|
132
|
-
existsSync(targetFile),
|
|
133
|
-
`Expected scripts/${script} to exist`,
|
|
134
|
-
).toBe(true)
|
|
135
|
-
}
|
|
136
|
-
})
|
|
137
|
-
|
|
138
|
-
it('should skip scripts that exist in target without manifest tracking', () => {
|
|
139
|
-
const tempDir = createTempDir()
|
|
140
|
-
const sourceScriptsDir = join(projectRoot, 'scripts')
|
|
141
|
-
|
|
142
|
-
// Pre-populate target with files matching source content (same hash)
|
|
143
|
-
// but create a manifest that has NO __scripts__ entries
|
|
144
|
-
for (const script of scriptsToSync) {
|
|
145
|
-
const sourceFile = join(sourceScriptsDir, script)
|
|
146
|
-
const targetFile = join(tempDir, 'scripts', script)
|
|
147
|
-
mkdirSync(dirname(targetFile), { recursive: true })
|
|
148
|
-
copyFileSync(sourceFile, targetFile)
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// Empty manifest — no __scripts__ keys → isUntouched = false for all
|
|
152
|
-
writeFileSync(
|
|
153
|
-
join(tempDir, '.agents-sync-manifest.json'),
|
|
154
|
-
JSON.stringify({ version: 1, files: {} }),
|
|
155
|
-
)
|
|
156
|
-
|
|
157
|
-
// Record mtimes before sync
|
|
158
|
-
const mtimesBefore = {}
|
|
159
|
-
for (const script of scriptsToSync) {
|
|
160
|
-
mtimesBefore[script] = statSync(
|
|
161
|
-
join(tempDir, 'scripts', script),
|
|
162
|
-
).mtimeMs
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
const result = runSync(tempDir)
|
|
166
|
-
expect(result.status).toBe(0)
|
|
167
|
-
|
|
168
|
-
// Verify mtimes are unchanged (files were NOT overwritten)
|
|
169
|
-
for (const script of scriptsToSync) {
|
|
170
|
-
expect(statSync(join(tempDir, 'scripts', script)).mtimeMs).toBe(
|
|
171
|
-
mtimesBefore[script],
|
|
172
|
-
)
|
|
173
|
-
}
|
|
174
|
-
})
|
|
175
|
-
|
|
176
|
-
it('should force overwrite all scripts with --force', () => {
|
|
177
|
-
const tempDir = createTempDir()
|
|
178
|
-
const sourceScriptsDir = join(projectRoot, 'scripts')
|
|
179
|
-
|
|
180
|
-
// Pre-populate target with dummy content (different from source)
|
|
181
|
-
for (const script of scriptsToSync) {
|
|
182
|
-
const targetFile = join(tempDir, 'scripts', script)
|
|
183
|
-
mkdirSync(dirname(targetFile), { recursive: true })
|
|
184
|
-
writeFileSync(targetFile, `// modified ${script}`)
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
const result = runSync(tempDir, ['--force'])
|
|
188
|
-
expect(result.status).toBe(0)
|
|
189
|
-
|
|
190
|
-
// Verify all 7 files were overwritten with source content
|
|
191
|
-
for (const script of scriptsToSync) {
|
|
192
|
-
const targetFile = join(tempDir, 'scripts', script)
|
|
193
|
-
const sourceFile = join(sourceScriptsDir, script)
|
|
194
|
-
expect(existsSync(targetFile)).toBe(true)
|
|
195
|
-
expect(readFileSync(targetFile, 'utf8')).toBe(
|
|
196
|
-
readFileSync(sourceFile, 'utf8'),
|
|
197
|
-
)
|
|
198
|
-
}
|
|
199
|
-
})
|
|
200
|
-
|
|
201
|
-
it('should show scripts in --dry-run output but not write files', () => {
|
|
202
|
-
const tempDir = createTempDir()
|
|
203
|
-
|
|
204
|
-
const result = runSync(tempDir, ['--dry-run'])
|
|
205
|
-
expect(result.status).toBe(0)
|
|
206
|
-
expect(result.stdout).toContain('dry-run')
|
|
207
|
-
|
|
208
|
-
// Verify no script files were written to disk
|
|
209
|
-
for (const script of scriptsToSync) {
|
|
210
|
-
expect(existsSync(join(tempDir, 'scripts', script))).toBe(false)
|
|
211
|
-
}
|
|
212
|
-
})
|
|
213
|
-
|
|
214
|
-
it('should track new manifest entries after sync', () => {
|
|
215
|
-
const tempDir = createTempDir()
|
|
216
|
-
|
|
217
|
-
// Create empty manifest
|
|
218
|
-
writeFileSync(
|
|
219
|
-
join(tempDir, '.agents-sync-manifest.json'),
|
|
220
|
-
JSON.stringify({ version: 1, files: {} }),
|
|
221
|
-
)
|
|
222
|
-
|
|
223
|
-
const result = runSync(tempDir)
|
|
224
|
-
expect(result.status).toBe(0)
|
|
225
|
-
|
|
226
|
-
// Read back the updated manifest
|
|
227
|
-
const updatedManifest = JSON.parse(
|
|
228
|
-
readFileSync(join(tempDir, '.agents-sync-manifest.json'), 'utf8'),
|
|
229
|
-
)
|
|
230
|
-
|
|
231
|
-
// Verify all 7 __scripts__ entries are tracked with valid SHA-256 hashes
|
|
232
|
-
for (const script of scriptsToSync) {
|
|
233
|
-
const key = `__scripts__/${script}`
|
|
234
|
-
expect(updatedManifest.files[key]).toBeDefined()
|
|
235
|
-
expect(updatedManifest.files[key]).toMatch(/^[a-f0-9]{64}$/)
|
|
236
|
-
}
|
|
237
|
-
})
|
|
238
|
-
})
|
|
239
|
-
|
|
240
|
-
describe('--prune behavior', () => {
|
|
241
|
-
const KEEP_CONTENT = 'keep skill v1\n'
|
|
242
|
-
const STALE_CONTENT = 'retired skill content\n'
|
|
243
|
-
const MODIFIED_ORIGINAL = 'modifiable skill v1\n'
|
|
244
|
-
const MODIFIED_LOCAL = 'local edits that must survive\n'
|
|
245
|
-
const GONE_CONTENT = 'already deleted by the user\n'
|
|
246
|
-
|
|
247
|
-
function createPruneFixture() {
|
|
248
|
-
// The fake package only ships `skills/keep/SKILL.md`. The consumer's `stale`,
|
|
249
|
-
// `modified`, and `gone` entries therefore all have an absent package source.
|
|
250
|
-
const packageDir = createFakePackage({
|
|
251
|
-
agents: { 'skills/keep/SKILL.md': KEEP_CONTENT },
|
|
252
|
-
})
|
|
253
|
-
const consumerDir = createTempDir()
|
|
254
|
-
|
|
255
|
-
writeFixtureFile(consumerDir, '.agents/skills/keep/SKILL.md', KEEP_CONTENT)
|
|
256
|
-
writeFixtureFile(consumerDir, '.agents/skills/stale/SKILL.md', STALE_CONTENT)
|
|
257
|
-
writeFixtureFile(
|
|
258
|
-
consumerDir,
|
|
259
|
-
'.agents/skills/modified/SKILL.md',
|
|
260
|
-
MODIFIED_LOCAL,
|
|
261
|
-
)
|
|
262
|
-
|
|
263
|
-
writeFileSync(
|
|
264
|
-
join(consumerDir, '.agents-sync-manifest.json'),
|
|
265
|
-
`${JSON.stringify(
|
|
266
|
-
{
|
|
267
|
-
version: 1,
|
|
268
|
-
files: {
|
|
269
|
-
'skills/keep/SKILL.md': sha256(KEEP_CONTENT),
|
|
270
|
-
'skills/stale/SKILL.md': sha256(STALE_CONTENT),
|
|
271
|
-
'skills/modified/SKILL.md': sha256(MODIFIED_ORIGINAL),
|
|
272
|
-
'skills/gone/SKILL.md': sha256(GONE_CONTENT),
|
|
273
|
-
},
|
|
274
|
-
},
|
|
275
|
-
null,
|
|
276
|
-
2,
|
|
277
|
-
)}\n`,
|
|
278
|
-
)
|
|
279
|
-
|
|
280
|
-
return { packageDir, consumerDir }
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
const stalePath = (consumerDir) =>
|
|
284
|
-
join(consumerDir, '.agents/skills/stale/SKILL.md')
|
|
285
|
-
const modifiedPath = (consumerDir) =>
|
|
286
|
-
join(consumerDir, '.agents/skills/modified/SKILL.md')
|
|
287
|
-
const keepPath = (consumerDir) =>
|
|
288
|
-
join(consumerDir, '.agents/skills/keep/SKILL.md')
|
|
289
|
-
const gonePath = (consumerDir) =>
|
|
290
|
-
join(consumerDir, '.agents/skills/gone/SKILL.md')
|
|
291
|
-
|
|
292
|
-
it('TEST-02: without --prune, a package-absent manifest entry is left untouched', () => {
|
|
293
|
-
const { packageDir, consumerDir } = createPruneFixture()
|
|
294
|
-
|
|
295
|
-
const result = runSyncFrom(packageDir, consumerDir)
|
|
296
|
-
expect(result.status).toBe(0)
|
|
297
|
-
|
|
298
|
-
// Tree unchanged: stale + modified files survive, and no entry is dropped.
|
|
299
|
-
expect(existsSync(stalePath(consumerDir))).toBe(true)
|
|
300
|
-
expect(readFileSync(stalePath(consumerDir), 'utf8')).toBe(STALE_CONTENT)
|
|
301
|
-
expect(existsSync(modifiedPath(consumerDir))).toBe(true)
|
|
302
|
-
|
|
303
|
-
const manifest = readConsumerManifest(consumerDir)
|
|
304
|
-
expect(manifest.files['skills/stale/SKILL.md']).toBe(sha256(STALE_CONTENT))
|
|
305
|
-
expect(manifest.files['skills/modified/SKILL.md']).toBe(
|
|
306
|
-
sha256(MODIFIED_ORIGINAL),
|
|
307
|
-
)
|
|
308
|
-
expect(manifest.files['skills/gone/SKILL.md']).toBeDefined()
|
|
309
|
-
})
|
|
310
|
-
|
|
311
|
-
it('TEST-03: --prune removes only unmodified files whose package source is gone', () => {
|
|
312
|
-
const { packageDir, consumerDir } = createPruneFixture()
|
|
313
|
-
|
|
314
|
-
const result = runSyncFrom(packageDir, consumerDir, ['--prune'])
|
|
315
|
-
expect(result.status).toBe(0)
|
|
316
|
-
|
|
317
|
-
// Hash-identical + package-absent => deleted, entry dropped.
|
|
318
|
-
expect(existsSync(stalePath(consumerDir))).toBe(false)
|
|
319
|
-
expect(readConsumerManifest(consumerDir).files['skills/stale/SKILL.md']).toBe(
|
|
320
|
-
undefined,
|
|
321
|
-
)
|
|
322
|
-
|
|
323
|
-
// Package-owned files are never candidates.
|
|
324
|
-
expect(existsSync(keepPath(consumerDir))).toBe(true)
|
|
325
|
-
expect(readConsumerManifest(consumerDir).files['skills/keep/SKILL.md']).toBe(
|
|
326
|
-
sha256(KEEP_CONTENT),
|
|
327
|
-
)
|
|
328
|
-
|
|
329
|
-
// Directories are never deleted: the now-empty skill dir is reported, not removed.
|
|
330
|
-
expect(existsSync(join(consumerDir, '.agents/skills/stale'))).toBe(true)
|
|
331
|
-
expect(result.stdout).toContain(
|
|
332
|
-
'Left empty directory in place: .agents/skills/stale',
|
|
333
|
-
)
|
|
334
|
-
expect(result.stdout).toContain(
|
|
335
|
-
'Pruned 1 stale file(s); skipped 1 locally modified.',
|
|
336
|
-
)
|
|
337
|
-
})
|
|
338
|
-
|
|
339
|
-
it('TEST-04: --prune never removes a locally modified file and retains its entry', () => {
|
|
340
|
-
const { packageDir, consumerDir } = createPruneFixture()
|
|
341
|
-
|
|
342
|
-
const result = runSyncFrom(packageDir, consumerDir, ['--prune'])
|
|
343
|
-
expect(result.status).toBe(0)
|
|
344
|
-
|
|
345
|
-
expect(existsSync(modifiedPath(consumerDir))).toBe(true)
|
|
346
|
-
expect(readFileSync(modifiedPath(consumerDir), 'utf8')).toBe(
|
|
347
|
-
MODIFIED_LOCAL,
|
|
348
|
-
)
|
|
349
|
-
// Warning names the file, and the manifest entry is retained with its original hash.
|
|
350
|
-
expect(result.stderr).toContain(
|
|
351
|
-
'Skipped pruning locally modified file: .agents/skills/modified/SKILL.md',
|
|
352
|
-
)
|
|
353
|
-
expect(readConsumerManifest(consumerDir).files['skills/modified/SKILL.md']).toBe(
|
|
354
|
-
sha256(MODIFIED_ORIGINAL),
|
|
355
|
-
)
|
|
356
|
-
})
|
|
357
|
-
|
|
358
|
-
it('TEST-05: --prune never removes a path that is still shipped in the package', () => {
|
|
359
|
-
const { packageDir, consumerDir } = createPruneFixture()
|
|
360
|
-
|
|
361
|
-
// The consumer copy is byte-identical to the package and tracked by hash —
|
|
362
|
-
// exactly the state that would qualify for deletion if the package had dropped it.
|
|
363
|
-
expect(readFileSync(keepPath(consumerDir), 'utf8')).toBe(KEEP_CONTENT)
|
|
364
|
-
|
|
365
|
-
const result = runSyncFrom(packageDir, consumerDir, ['--prune'])
|
|
366
|
-
expect(result.status).toBe(0)
|
|
367
|
-
|
|
368
|
-
expect(existsSync(keepPath(consumerDir))).toBe(true)
|
|
369
|
-
expect(readFileSync(keepPath(consumerDir), 'utf8')).toBe(KEEP_CONTENT)
|
|
370
|
-
expect(readConsumerManifest(consumerDir).files['skills/keep/SKILL.md']).toBe(
|
|
371
|
-
sha256(KEEP_CONTENT),
|
|
372
|
-
)
|
|
373
|
-
})
|
|
374
|
-
|
|
375
|
-
it('--prune --dry-run reports candidates but deletes nothing', () => {
|
|
376
|
-
const { packageDir, consumerDir } = createPruneFixture()
|
|
377
|
-
|
|
378
|
-
const result = runSyncFrom(packageDir, consumerDir, [
|
|
379
|
-
'--prune',
|
|
380
|
-
'--dry-run',
|
|
381
|
-
])
|
|
382
|
-
expect(result.status).toBe(0)
|
|
383
|
-
|
|
384
|
-
expect(result.stdout).toContain(
|
|
385
|
-
'Would prune: .agents/skills/stale/SKILL.md',
|
|
386
|
-
)
|
|
387
|
-
expect(result.stdout).toContain(
|
|
388
|
-
'Would prune 1 stale file(s); skipped 1 locally modified.',
|
|
389
|
-
)
|
|
390
|
-
|
|
391
|
-
// Nothing deleted, and the manifest on disk is unchanged.
|
|
392
|
-
expect(existsSync(stalePath(consumerDir))).toBe(true)
|
|
393
|
-
expect(readFileSync(stalePath(consumerDir), 'utf8')).toBe(STALE_CONTENT)
|
|
394
|
-
expect(readConsumerManifest(consumerDir).files['skills/stale/SKILL.md']).toBe(
|
|
395
|
-
sha256(STALE_CONTENT),
|
|
396
|
-
)
|
|
397
|
-
})
|
|
398
|
-
|
|
399
|
-
it('--prune drops entries whose consumer file is already missing, without error', () => {
|
|
400
|
-
const { packageDir, consumerDir } = createPruneFixture()
|
|
401
|
-
expect(existsSync(gonePath(consumerDir))).toBe(false)
|
|
402
|
-
|
|
403
|
-
const result = runSyncFrom(packageDir, consumerDir, ['--prune'])
|
|
404
|
-
expect(result.status).toBe(0)
|
|
405
|
-
expect(result.stderr).not.toContain('Error')
|
|
406
|
-
expect(readConsumerManifest(consumerDir).files['skills/gone/SKILL.md']).toBe(
|
|
407
|
-
undefined,
|
|
408
|
-
)
|
|
409
|
-
})
|
|
410
|
-
|
|
411
|
-
it('--prune never removes scaffold-only manifest entries (memory-bank, learned knowledge)', () => {
|
|
412
|
-
const { packageDir, consumerDir } = createPruneFixture()
|
|
413
|
-
|
|
414
|
-
// First sync scaffolds these files and records their hashes.
|
|
415
|
-
expect(runSyncFrom(packageDir, consumerDir).status).toBe(0)
|
|
416
|
-
|
|
417
|
-
const result = runSyncFrom(packageDir, consumerDir, ['--prune'])
|
|
418
|
-
expect(result.status).toBe(0)
|
|
419
|
-
|
|
420
|
-
expect(existsSync(join(consumerDir, 'memory-bank/projectbrief.md'))).toBe(
|
|
421
|
-
true,
|
|
422
|
-
)
|
|
423
|
-
expect(
|
|
424
|
-
existsSync(
|
|
425
|
-
join(
|
|
426
|
-
consumerDir,
|
|
427
|
-
'.agents/instructions/learned-knowledge.instructions.md',
|
|
428
|
-
),
|
|
429
|
-
),
|
|
430
|
-
).toBe(true)
|
|
431
|
-
|
|
432
|
-
const manifest = readConsumerManifest(consumerDir)
|
|
433
|
-
expect(manifest.files['__memory-bank__/projectbrief.md']).toBeDefined()
|
|
434
|
-
expect(
|
|
435
|
-
manifest.files['__agents-instructions__/learned-knowledge.instructions.md'],
|
|
436
|
-
).toBeDefined()
|
|
437
|
-
})
|
|
438
|
-
|
|
439
|
-
it('--prune resolves __scripts__ and __root__ keys against the package', () => {
|
|
440
|
-
const packageDir = createFakePackage({
|
|
441
|
-
agents: { 'skills/keep/SKILL.md': KEEP_CONTENT },
|
|
442
|
-
})
|
|
443
|
-
const consumerDir = createTempDir()
|
|
444
|
-
|
|
445
|
-
writeFixtureFile(consumerDir, 'scripts/old-tool.js', 'console.log(1)\n')
|
|
446
|
-
writeFixtureFile(consumerDir, 'AGENTS.md', '# locally edited agents\n')
|
|
447
|
-
writeFileSync(
|
|
448
|
-
join(consumerDir, '.agents-sync-manifest.json'),
|
|
449
|
-
`${JSON.stringify(
|
|
450
|
-
{
|
|
451
|
-
version: 1,
|
|
452
|
-
files: {
|
|
453
|
-
'__scripts__/old-tool.js': sha256('console.log(1)\n'),
|
|
454
|
-
'__root__/AGENTS.md': sha256('# locally edited agents\n'),
|
|
455
|
-
},
|
|
456
|
-
},
|
|
457
|
-
null,
|
|
458
|
-
2,
|
|
459
|
-
)}\n`,
|
|
460
|
-
)
|
|
461
|
-
|
|
462
|
-
const result = runSyncFrom(packageDir, consumerDir, ['--prune'])
|
|
463
|
-
expect(result.status).toBe(0)
|
|
464
|
-
|
|
465
|
-
expect(existsSync(join(consumerDir, 'scripts/old-tool.js'))).toBe(false)
|
|
466
|
-
expect(existsSync(join(consumerDir, 'AGENTS.md'))).toBe(false)
|
|
467
|
-
|
|
468
|
-
const manifest = readConsumerManifest(consumerDir)
|
|
469
|
-
expect(manifest.files['__scripts__/old-tool.js']).toBeUndefined()
|
|
470
|
-
expect(manifest.files['__root__/AGENTS.md']).toBeUndefined()
|
|
471
|
-
})
|
|
472
|
-
})
|