@abarcenas/ai-workflow-template 1.45.0 → 1.47.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/config/README.md +43 -0
- package/.agents/config/domain.md +37 -0
- package/.agents/instructions/learned-knowledge.instructions.md +128 -0
- package/.agents/instructions/memory-bank.instructions.md +11 -0
- package/.agents/skills/domain-modeling/ADR-FORMAT.md +47 -0
- package/.agents/skills/domain-modeling/GLOSSARY-FORMAT.md +60 -0
- package/.agents/skills/domain-modeling/SKILL.md +74 -0
- package/.agents/skills/grill-me/SKILL.md +7 -0
- package/.agents/skills/grill-with-docs/SKILL.md +7 -0
- package/.agents/skills/grilling/SKILL.md +28 -0
- package/.agents/skills/prototype/LOGIC.md +67 -0
- package/.agents/skills/prototype/SKILL.md +26 -0
- package/.agents/skills/prototype/UI.md +112 -0
- package/.github/workflows/npm-publish.yml +10 -0
- package/.github/workflows/pr-test.yml +31 -0
- package/.opencode/agents/orchestrator/orchestrator.agent.md +68 -3
- package/.opencode/agents/scope.agent.md +299 -0
- package/.prettierignore +26 -0
- package/AGENTS.md +46 -3
- package/README.md +29 -2
- package/opencode.mcp.example.json +1 -0
- package/package.json +3 -1
- package/scripts/bump-version.test.js +238 -0
- package/scripts/mcp/playwright-mcp-launcher.js +55 -29
- package/scripts/mcp/playwright-mcp-launcher.test.js +217 -0
- package/scripts/mcp-memory-server.js +93 -76
- package/scripts/mcp-memory-server.test.js +398 -0
- package/scripts/memory-cli.test.js +297 -0
- package/scripts/memory-index.js +119 -31
- package/scripts/memory-index.test.js +1173 -0
- package/scripts/normalize-memory.js +26 -1
- package/scripts/normalize-memory.test.js +753 -0
- package/scripts/setup/discover.test.js +124 -2
- package/scripts/setup/hooks.test.js +188 -1
- package/scripts/setup/husky-init.test.js +311 -0
- package/scripts/setup/index.js +35 -5
- package/scripts/setup/index.test.js +279 -0
- package/scripts/setup/knowledgebase.test.js +100 -0
- package/scripts/setup/skills-phase.js +568 -0
- package/scripts/setup/skills-phase.test.js +1274 -0
- package/scripts/setup/sync-phase.js +6 -1
- package/scripts/setup/sync-phase.test.js +180 -2
- package/scripts/setup/ui.test.js +579 -0
- package/scripts/setup/utils.test.js +586 -0
- package/scripts/sync-unit.test.js +1881 -0
- package/scripts/sync.js +816 -239
- package/scripts/sync.test.js +283 -0
- package/scripts/vocab-sync.test.js +88 -0
- package/skills-lock.json +90 -0
package/scripts/sync.test.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { describe, expect, it, afterAll } from 'vitest'
|
|
2
2
|
import { spawnSync } from 'child_process'
|
|
3
|
+
import { createHash } from 'crypto'
|
|
3
4
|
import {
|
|
4
5
|
copyFileSync,
|
|
5
6
|
existsSync,
|
|
@@ -36,6 +37,54 @@ afterAll(() => {
|
|
|
36
37
|
}
|
|
37
38
|
})
|
|
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
|
+
|
|
39
88
|
describe('sync utility functions', () => {
|
|
40
89
|
it('should validate basic assertions work', () => {
|
|
41
90
|
expect(1 + 1).toBe(2)
|
|
@@ -187,3 +236,237 @@ describe('__scripts__ sync behavior', () => {
|
|
|
187
236
|
}
|
|
188
237
|
})
|
|
189
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
|
+
})
|
|
@@ -812,3 +812,91 @@ describe('direct-run guard', () => {
|
|
|
812
812
|
expect(exitSpy).toHaveBeenCalledWith(1)
|
|
813
813
|
})
|
|
814
814
|
})
|
|
815
|
+
|
|
816
|
+
// ── Additional branch coverage (scalar quoting, empty tags, guard) ─────────
|
|
817
|
+
|
|
818
|
+
describe('parseFrontmatter — scalar quoting', () => {
|
|
819
|
+
it('strips single quotes from scalar values', async () => {
|
|
820
|
+
const vs = await loadFresh()
|
|
821
|
+
const content = "---\ntitle: 'Single Quoted'\n---\nBody"
|
|
822
|
+
|
|
823
|
+
expect(vs.parseFrontmatter(content).title).toBe('Single Quoted')
|
|
824
|
+
})
|
|
825
|
+
|
|
826
|
+
it('leaves unquoted scalars untouched', async () => {
|
|
827
|
+
const vs = await loadFresh()
|
|
828
|
+
const content = '---\ncategory: context\n---\nBody'
|
|
829
|
+
|
|
830
|
+
expect(vs.parseFrontmatter(content).category).toBe('context')
|
|
831
|
+
})
|
|
832
|
+
|
|
833
|
+
it('keeps a value that only starts with a quote as-is', async () => {
|
|
834
|
+
const vs = await loadFresh()
|
|
835
|
+
const content = "---\ntitle: 'unterminated\n---\nBody"
|
|
836
|
+
|
|
837
|
+
expect(vs.parseFrontmatter(content).title).toBe("'unterminated")
|
|
838
|
+
})
|
|
839
|
+
})
|
|
840
|
+
|
|
841
|
+
describe('buildKnownTags — degenerate entries', () => {
|
|
842
|
+
it('ignores whitespace-only tags inside a group', async () => {
|
|
843
|
+
const vs = await loadFresh()
|
|
844
|
+
const known = vs.buildKnownTags({ tags: { topic: [' ', 'real'] } })
|
|
845
|
+
|
|
846
|
+
expect(known.has('real')).toBe(true)
|
|
847
|
+
expect(known.size).toBe(1)
|
|
848
|
+
})
|
|
849
|
+
|
|
850
|
+
it('ignores entity_patterns keys that normalize to an empty string', async () => {
|
|
851
|
+
const vs = await loadFresh()
|
|
852
|
+
const known = vs.buildKnownTags({
|
|
853
|
+
tags: { topic: ['real'] },
|
|
854
|
+
entity_patterns: { ' ': ['x'], '': ['y'] },
|
|
855
|
+
})
|
|
856
|
+
|
|
857
|
+
expect([...known]).toEqual(['real'])
|
|
858
|
+
})
|
|
859
|
+
})
|
|
860
|
+
|
|
861
|
+
describe('collectNewTags — normalization edge cases', () => {
|
|
862
|
+
it('drops tags that normalize to an empty string but survive parsing', async () => {
|
|
863
|
+
const dir = createTempDir()
|
|
864
|
+
// Quoted whitespace is kept by parseFrontmatter (truthy raw string) and
|
|
865
|
+
// only becomes empty after normalizeTag().
|
|
866
|
+
writeRepoFile(dir, 'memory-bank/a.md', '---\ntags: [" "]\n---\nBody')
|
|
867
|
+
const vs = await loadVocabSyncIn(dir)
|
|
868
|
+
|
|
869
|
+
expect(
|
|
870
|
+
vs.collectNewTags(['memory-bank/a.md'], { tags: { topic: [] } }),
|
|
871
|
+
).toEqual([])
|
|
872
|
+
})
|
|
873
|
+
|
|
874
|
+
it('still collects real tags alongside the empty ones', async () => {
|
|
875
|
+
const dir = createTempDir()
|
|
876
|
+
writeRepoFile(dir, 'memory-bank/a.md', '---\ntags: [" ", "real-tag"]\n---\nBody')
|
|
877
|
+
const vs = await loadVocabSyncIn(dir)
|
|
878
|
+
|
|
879
|
+
expect(
|
|
880
|
+
vs.collectNewTags(['memory-bank/a.md'], { tags: { topic: [] } }),
|
|
881
|
+
).toEqual(['real-tag'])
|
|
882
|
+
})
|
|
883
|
+
})
|
|
884
|
+
|
|
885
|
+
describe('direct-run guard — argv edge cases', () => {
|
|
886
|
+
it('does not run main() when process.argv[1] is empty', async () => {
|
|
887
|
+
const dir = createTempDir()
|
|
888
|
+
gitInitRepo(dir)
|
|
889
|
+
writeRepoFile(dir, 'memory-bank/a.md', frontmatterWithTags(['new-tag']))
|
|
890
|
+
gitAdd(dir, ['memory-bank/a.md'])
|
|
891
|
+
writeVocab(dir, { tags: { topic: [] } })
|
|
892
|
+
|
|
893
|
+
process.chdir(dir)
|
|
894
|
+
process.argv[1] = ''
|
|
895
|
+
vi.resetModules()
|
|
896
|
+
await import('./vocab-sync.js')
|
|
897
|
+
|
|
898
|
+
// main() never ran, so nothing was written or warned about
|
|
899
|
+
expect(readVocab(dir).tags.topic).toEqual([])
|
|
900
|
+
expect(warnSpy).not.toHaveBeenCalled()
|
|
901
|
+
})
|
|
902
|
+
})
|
package/skills-lock.json
CHANGED
|
@@ -48,6 +48,96 @@
|
|
|
48
48
|
"sourceType": "github",
|
|
49
49
|
"skillPath": "skills/compress/SKILL.md",
|
|
50
50
|
"computedHash": "c91bdf427a68b686ee9d6f63766c72d164b16915434736edfedf13e24dae0e8c"
|
|
51
|
+
},
|
|
52
|
+
"domain-modeling": {
|
|
53
|
+
"source": "mattpocock/skills",
|
|
54
|
+
"sourceType": "github",
|
|
55
|
+
"skillPath": "skills/engineering/domain-modeling/SKILL.md",
|
|
56
|
+
"ref": "v1.3.1",
|
|
57
|
+
"computedHash": "7b925d7b1e341a2eeae33ad68a8a8c0ab889a38ddd22a7598e4c240e5a7556a3",
|
|
58
|
+
"files": [
|
|
59
|
+
{
|
|
60
|
+
"path": "SKILL.md",
|
|
61
|
+
"sha256": "7b925d7b1e341a2eeae33ad68a8a8c0ab889a38ddd22a7598e4c240e5a7556a3",
|
|
62
|
+
"upstreamPath": "skills/engineering/domain-modeling/SKILL.md"
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"path": "GLOSSARY-FORMAT.md",
|
|
66
|
+
"sha256": "21dcc40cdec8e151a829b98ab1517c1867bc444e3665134a98c6983411996801",
|
|
67
|
+
"upstreamPath": "skills/engineering/domain-modeling/GLOSSARY-FORMAT.md"
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"path": "ADR-FORMAT.md",
|
|
71
|
+
"sha256": "944c92aa790e8fbdc9199640b170979abb8a34ba8d0fe18c2a01a63bce140ca0",
|
|
72
|
+
"upstreamPath": "skills/engineering/domain-modeling/ADR-FORMAT.md"
|
|
73
|
+
}
|
|
74
|
+
]
|
|
75
|
+
},
|
|
76
|
+
"grill-me": {
|
|
77
|
+
"source": "mattpocock/skills",
|
|
78
|
+
"sourceType": "github",
|
|
79
|
+
"skillPath": "skills/productivity/grill-me/SKILL.md",
|
|
80
|
+
"ref": "v1.3.1",
|
|
81
|
+
"computedHash": "caaf8b8de1684f96e26b28f3c29189db5c89cce4b73e1c93d86164f66ef88637",
|
|
82
|
+
"files": [
|
|
83
|
+
{
|
|
84
|
+
"path": "SKILL.md",
|
|
85
|
+
"sha256": "caaf8b8de1684f96e26b28f3c29189db5c89cce4b73e1c93d86164f66ef88637",
|
|
86
|
+
"upstreamPath": "skills/productivity/grill-me/SKILL.md"
|
|
87
|
+
}
|
|
88
|
+
]
|
|
89
|
+
},
|
|
90
|
+
"grill-with-docs": {
|
|
91
|
+
"source": "mattpocock/skills",
|
|
92
|
+
"sourceType": "github",
|
|
93
|
+
"skillPath": "skills/engineering/grill-with-docs/SKILL.md",
|
|
94
|
+
"ref": "v1.3.1",
|
|
95
|
+
"computedHash": "7de372c13488f1ee96cc11cd8907b56b6809cc93eef776eeddd37de6b6cbe3fe",
|
|
96
|
+
"files": [
|
|
97
|
+
{
|
|
98
|
+
"path": "SKILL.md",
|
|
99
|
+
"sha256": "7de372c13488f1ee96cc11cd8907b56b6809cc93eef776eeddd37de6b6cbe3fe",
|
|
100
|
+
"upstreamPath": "skills/engineering/grill-with-docs/SKILL.md"
|
|
101
|
+
}
|
|
102
|
+
]
|
|
103
|
+
},
|
|
104
|
+
"grilling": {
|
|
105
|
+
"source": "mattpocock/skills",
|
|
106
|
+
"sourceType": "github",
|
|
107
|
+
"skillPath": "skills/productivity/grilling/SKILL.md",
|
|
108
|
+
"ref": "v1.3.1",
|
|
109
|
+
"computedHash": "10ff989e7498b23b5acb49d5048f11dcd906757d2f79c5cdf8a00001381296f2",
|
|
110
|
+
"files": [
|
|
111
|
+
{
|
|
112
|
+
"path": "SKILL.md",
|
|
113
|
+
"sha256": "10ff989e7498b23b5acb49d5048f11dcd906757d2f79c5cdf8a00001381296f2",
|
|
114
|
+
"upstreamPath": "skills/productivity/grilling/SKILL.md"
|
|
115
|
+
}
|
|
116
|
+
]
|
|
117
|
+
},
|
|
118
|
+
"prototype": {
|
|
119
|
+
"source": "mattpocock/skills",
|
|
120
|
+
"sourceType": "github",
|
|
121
|
+
"skillPath": "skills/engineering/prototype/SKILL.md",
|
|
122
|
+
"ref": "v1.3.1",
|
|
123
|
+
"computedHash": "714de632d116bb73f65cdb5a882db15b9369a6713b9a47c0fad827848f0bfbe3",
|
|
124
|
+
"files": [
|
|
125
|
+
{
|
|
126
|
+
"path": "SKILL.md",
|
|
127
|
+
"sha256": "714de632d116bb73f65cdb5a882db15b9369a6713b9a47c0fad827848f0bfbe3",
|
|
128
|
+
"upstreamPath": "skills/engineering/prototype/SKILL.md"
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
"path": "LOGIC.md",
|
|
132
|
+
"sha256": "f61c7d249e786a79ef289018901c348271e1798dd0b0bc5607b5c6f4d4a01ab9",
|
|
133
|
+
"upstreamPath": "skills/engineering/prototype/LOGIC.md"
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
"path": "UI.md",
|
|
137
|
+
"sha256": "723211e878acbc7b6ff09755263f3295cde724ba902ff0064da41eed51d45ad3",
|
|
138
|
+
"upstreamPath": "skills/engineering/prototype/UI.md"
|
|
139
|
+
}
|
|
140
|
+
]
|
|
51
141
|
}
|
|
52
142
|
}
|
|
53
143
|
}
|