agent-facets 0.2.2 → 0.3.3

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.
Files changed (59) hide show
  1. package/bin/facet +181 -0
  2. package/bin/package.json +3 -0
  3. package/package.json +17 -37
  4. package/postinstall.mjs +210 -0
  5. package/.package.json.bak +0 -44
  6. package/.turbo/turbo-build.log +0 -3
  7. package/CHANGELOG.md +0 -85
  8. package/bunfig.toml +0 -2
  9. package/dist/facet +0 -0
  10. package/src/__tests__/cli.test.ts +0 -195
  11. package/src/__tests__/create-build.test.ts +0 -227
  12. package/src/__tests__/edit-integration.test.ts +0 -171
  13. package/src/__tests__/resolve-dir.test.ts +0 -95
  14. package/src/commands/build.ts +0 -58
  15. package/src/commands/create/index.ts +0 -76
  16. package/src/commands/create/types.ts +0 -9
  17. package/src/commands/create/wizard.tsx +0 -75
  18. package/src/commands/create-scaffold.ts +0 -184
  19. package/src/commands/edit/index.ts +0 -144
  20. package/src/commands/edit/wizard.tsx +0 -74
  21. package/src/commands/resolve-dir.ts +0 -98
  22. package/src/commands.ts +0 -40
  23. package/src/help.ts +0 -43
  24. package/src/index.ts +0 -10
  25. package/src/run.ts +0 -82
  26. package/src/suggest.ts +0 -35
  27. package/src/tui/components/asset-description.tsx +0 -17
  28. package/src/tui/components/asset-field-picker.tsx +0 -78
  29. package/src/tui/components/asset-inline-input.tsx +0 -91
  30. package/src/tui/components/asset-item.tsx +0 -44
  31. package/src/tui/components/asset-section.tsx +0 -191
  32. package/src/tui/components/button.tsx +0 -92
  33. package/src/tui/components/editable-field.tsx +0 -172
  34. package/src/tui/components/exit-toast.tsx +0 -20
  35. package/src/tui/components/reconciliation-item.tsx +0 -129
  36. package/src/tui/components/stage-row.tsx +0 -45
  37. package/src/tui/components/version-selector.tsx +0 -79
  38. package/src/tui/context/focus-mode-context.ts +0 -36
  39. package/src/tui/context/focus-order-context.ts +0 -68
  40. package/src/tui/context/form-state-context.ts +0 -260
  41. package/src/tui/editor.ts +0 -40
  42. package/src/tui/gradient.ts +0 -1
  43. package/src/tui/hooks/use-exit-keys.ts +0 -75
  44. package/src/tui/hooks/use-navigation-keys.ts +0 -34
  45. package/src/tui/layouts/wizard-layout.tsx +0 -41
  46. package/src/tui/theme.ts +0 -1
  47. package/src/tui/views/build/build-view.tsx +0 -152
  48. package/src/tui/views/create/confirm-view.tsx +0 -74
  49. package/src/tui/views/create/create-view.tsx +0 -158
  50. package/src/tui/views/create/wizard.tsx +0 -97
  51. package/src/tui/views/edit/edit-confirm-view.tsx +0 -93
  52. package/src/tui/views/edit/edit-types.ts +0 -34
  53. package/src/tui/views/edit/edit-view.tsx +0 -140
  54. package/src/tui/views/edit/manifest-to-form.ts +0 -38
  55. package/src/tui/views/edit/reconciliation-view.tsx +0 -170
  56. package/src/tui/views/edit/use-edit-session.ts +0 -125
  57. package/src/tui/views/edit/wizard.tsx +0 -129
  58. package/src/version.ts +0 -3
  59. package/tsconfig.json +0 -4
@@ -1,195 +0,0 @@
1
- import { describe, expect, test } from 'bun:test'
2
- import { resolve } from 'node:path'
3
-
4
- const CLI_PATH = resolve(import.meta.dir, '../../dist/facet')
5
- const COMMAND_NAMES = ['add', 'build', 'create', 'edit', 'info', 'install', 'list', 'publish', 'remove', 'upgrade']
6
- const STUB_COMMAND_NAMES = ['add', 'info', 'install', 'list', 'publish', 'remove', 'upgrade']
7
-
8
- type ExecResult = {
9
- stdout: string
10
- stderr: string
11
- exitCode: number
12
- }
13
-
14
- async function runCli(...args: string[]): Promise<ExecResult> {
15
- const proc = Bun.spawn([CLI_PATH, ...args], {
16
- stdout: 'pipe',
17
- stderr: 'pipe',
18
- })
19
- const [stdout, stderr] = await Promise.all([new Response(proc.stdout).text(), new Response(proc.stderr).text()])
20
- const exitCode = await proc.exited
21
- return { stdout: stdout.trim(), stderr: stderr.trim(), exitCode }
22
- }
23
-
24
- // --- Help ---
25
-
26
- describe('CLI — help', () => {
27
- test('--help prints command list to stdout and exits 0', async () => {
28
- const result = await runCli('--help')
29
- expect(result.exitCode).toBe(0)
30
- expect(result.stdout).toContain('Usage: facet <command>')
31
- for (const cmd of COMMAND_NAMES) {
32
- expect(result.stdout).toContain(cmd)
33
- }
34
- expect(result.stderr).toBe('')
35
- })
36
-
37
- test('help command produces same output as --help', async () => {
38
- const helpFlag = await runCli('--help')
39
- const helpCommand = await runCli('help')
40
- expect(helpCommand.exitCode).toBe(0)
41
- expect(helpCommand.stdout).toBe(helpFlag.stdout)
42
- expect(helpCommand.stderr).toBe('')
43
- })
44
- })
45
-
46
- // --- Version ---
47
-
48
- describe('CLI — version', () => {
49
- test('--version prints version matching package.json and exits 0', async () => {
50
- const pkg = await Bun.file(resolve(import.meta.dir, '../../package.json')).json()
51
- const result = await runCli('--version')
52
- expect(result.exitCode).toBe(0)
53
- expect(result.stdout).toBe(pkg.version)
54
- expect(result.stderr).toBe('')
55
- })
56
- })
57
-
58
- // --- Bare invocation ---
59
-
60
- describe('CLI — bare invocation', () => {
61
- test('no arguments prints help and exits 0', async () => {
62
- const helpResult = await runCli('--help')
63
- const bareResult = await runCli()
64
- expect(bareResult.exitCode).toBe(0)
65
- expect(bareResult.stdout).toBe(helpResult.stdout)
66
- expect(bareResult.stderr).toBe('')
67
- })
68
- })
69
-
70
- // --- Stub commands ---
71
-
72
- describe('CLI — stub commands', () => {
73
- test.each(STUB_COMMAND_NAMES)('"%s" prints not yet implemented with command name and exits 0', async (cmd) => {
74
- const result = await runCli(cmd)
75
- expect(result.exitCode).toBe(0)
76
- expect(result.stdout).toContain(cmd)
77
- expect(result.stdout).toContain('not yet implemented')
78
- expect(result.stderr).toBe('')
79
- })
80
- })
81
-
82
- // --- Edit command dispatch ---
83
-
84
- describe('CLI — edit command', () => {
85
- test('edit with no manifest prints error and exits 1', async () => {
86
- const result = await runCli('edit', import.meta.dir)
87
- expect(result.exitCode).toBe(1)
88
- expect(result.stderr).toContain('facet.json')
89
- })
90
- })
91
-
92
- // --- Unknown commands ---
93
-
94
- describe('CLI — unknown commands', () => {
95
- test('unknown command prints error to stderr and exits 1', async () => {
96
- const result = await runCli('xyzzy')
97
- expect(result.exitCode).toBe(1)
98
- expect(result.stderr).toContain('Unknown command "xyzzy"')
99
- expect(result.stdout).toBe('')
100
- })
101
-
102
- test('unknown command with close match includes "did you mean?" suggestion', async () => {
103
- const result = await runCli('bild')
104
- expect(result.exitCode).toBe(1)
105
- expect(result.stderr).toContain('Unknown command "bild"')
106
- expect(result.stderr).toContain('Did you mean "build"')
107
- expect(result.stdout).toBe('')
108
- })
109
-
110
- test('unknown command with no close match does not include suggestion', async () => {
111
- const result = await runCli('xyzzy')
112
- expect(result.exitCode).toBe(1)
113
- expect(result.stderr).toContain('Unknown command "xyzzy"')
114
- expect(result.stderr).not.toContain('Did you mean')
115
- expect(result.stdout).toBe('')
116
- })
117
- })
118
-
119
- // --- Per-command help ---
120
-
121
- describe('CLI — per-command help', () => {
122
- test('<command> --help prints command-specific help and exits 0', async () => {
123
- const result = await runCli('build', '--help')
124
- expect(result.exitCode).toBe(0)
125
- expect(result.stdout).toContain('facet build')
126
- expect(result.stdout).toContain('Build a facet from the current directory')
127
- expect(result.stderr).toBe('')
128
- })
129
- })
130
-
131
- // --- Unexpected error ---
132
-
133
- describe('CLI — unexpected error', () => {
134
- test('unexpected error is thrown by run', async () => {
135
- const { run } = await import('../run.ts')
136
-
137
- const crashRegistry = {
138
- crash: {
139
- name: 'crash',
140
- description: 'Throws an error',
141
- run: async (_args: string[], _flags: Record<string, unknown>) => {
142
- throw new Error('boom')
143
- },
144
- },
145
- }
146
-
147
- await expect(run(['crash'], crashRegistry)).rejects.toThrow('boom')
148
- })
149
- })
150
-
151
- // --- Per-command flags ---
152
-
153
- describe('CLI — per-command flags', () => {
154
- test('create --help shows --force flag and usage', async () => {
155
- const result = await runCli('create', '--help')
156
- expect(result.exitCode).toBe(0)
157
- expect(result.stdout).toContain('[directory]')
158
- expect(result.stdout).toContain('--force')
159
- expect(result.stdout).toContain('Overwrite existing facet.json')
160
- })
161
-
162
- test('build --help shows directory usage', async () => {
163
- const result = await runCli('build', '--help')
164
- expect(result.exitCode).toBe(0)
165
- expect(result.stdout).toContain('[directory]')
166
- })
167
-
168
- test('edit --help shows directory usage', async () => {
169
- const result = await runCli('edit', '--help')
170
- expect(result.exitCode).toBe(0)
171
- expect(result.stdout).toContain('[directory]')
172
- })
173
- })
174
-
175
- // --- Directory validation ---
176
-
177
- describe('CLI — directory validation', () => {
178
- test('build with non-existent directory errors', async () => {
179
- const result = await runCli('build', `/tmp/does-not-exist-${Date.now()}`)
180
- expect(result.exitCode).toBe(1)
181
- expect(result.stderr).toContain('does not exist')
182
- })
183
-
184
- test('edit with non-existent directory errors', async () => {
185
- const result = await runCli('edit', `/tmp/does-not-exist-${Date.now()}`)
186
- expect(result.exitCode).toBe(1)
187
- expect(result.stderr).toContain('does not exist')
188
- })
189
-
190
- test('build with file instead of directory errors', async () => {
191
- const result = await runCli('build', import.meta.path)
192
- expect(result.exitCode).toBe(1)
193
- expect(result.stderr).toContain('Expected a directory')
194
- })
195
- })
@@ -1,227 +0,0 @@
1
- import { afterAll, beforeAll, describe, expect, test } from 'bun:test'
2
- import { mkdtemp, rm } from 'node:fs/promises'
3
- import { tmpdir } from 'node:os'
4
- import { join, resolve } from 'node:path'
5
- import { writeScaffold } from '../commands/create/index.ts'
6
- import { DEFAULT_VERSION } from '../commands/create-scaffold.ts'
7
-
8
- let testDir: string
9
-
10
- beforeAll(async () => {
11
- testDir = await mkdtemp(join(tmpdir(), 'cli-create-build-test-'))
12
- })
13
-
14
- afterAll(async () => {
15
- await rm(testDir, { recursive: true, force: true })
16
- })
17
-
18
- async function createFixtureDir(name: string): Promise<string> {
19
- const dir = join(testDir, name)
20
- await Bun.write(join(dir, '.keep'), '')
21
- return dir
22
- }
23
-
24
- const CLI_PATH = resolve(import.meta.dir, '../../dist/facet')
25
-
26
- async function runCli(...args: string[]) {
27
- const proc = Bun.spawn([CLI_PATH, ...args], {
28
- stdout: 'pipe',
29
- stderr: 'pipe',
30
- env: { ...process.env, NO_COLOR: '1' },
31
- })
32
- const stdout = await new Response(proc.stdout).text()
33
- const stderr = await new Response(proc.stderr).text()
34
- const exitCode = await proc.exited
35
-
36
- // Don't let build errors flood test output — capture but don't dump
37
- if (exitCode !== 0 && stderr.trim()) {
38
- const lines = stderr.trim().split('\n')
39
- const summary =
40
- lines.length > 3 ? [...lines.slice(0, 3), `... (${lines.length - 3} more lines)`].join('\n') : stderr.trim()
41
- return { stdout: stdout.trim(), stderr: summary, exitCode }
42
- }
43
-
44
- return { stdout: stdout.trim(), stderr: stderr.trim(), exitCode }
45
- }
46
-
47
- // --- Scaffold generation (unit) ---
48
-
49
- describe('writeScaffold', () => {
50
- test('scaffolds with named assets across all types', async () => {
51
- const dir = await createFixtureDir('scaffold-all')
52
- const files = await writeScaffold(
53
- {
54
- name: 'my-facet',
55
- version: DEFAULT_VERSION,
56
- description: 'A test facet',
57
- skills: ['code-review', 'testing-guide'],
58
- agents: ['reviewer'],
59
- commands: ['deploy'],
60
- },
61
- dir,
62
- )
63
-
64
- expect(files).toContain('facet.json')
65
- expect(files).toContain('skills/code-review/SKILL.md')
66
- expect(files).toContain('skills/testing-guide/SKILL.md')
67
- expect(files).toContain('agents/reviewer.md')
68
- expect(files).toContain('commands/deploy.md')
69
-
70
- // Verify manifest content (JSON)
71
- const manifestText = await Bun.file(join(dir, 'facet.json')).text()
72
- const manifest = JSON.parse(manifestText)
73
- expect(manifest.name).toBe('my-facet')
74
- expect(manifest.version).toBe(DEFAULT_VERSION)
75
- expect(manifest.description).toBe('A test facet')
76
- expect(manifest.skills).toBeDefined()
77
- expect(manifest.skills['code-review']).toBeDefined()
78
- expect(manifest.skills['testing-guide']).toBeDefined()
79
- expect(manifest.agents).toBeDefined()
80
- expect(manifest.agents.reviewer).toBeDefined()
81
- expect(manifest.commands).toBeDefined()
82
- expect(manifest.commands.deploy).toBeDefined()
83
-
84
- // Verify starter files exist and have named template content
85
- const skill = await Bun.file(join(dir, 'skills/code-review/SKILL.md')).text()
86
- expect(skill).toContain('# Code Review')
87
-
88
- const skill2 = await Bun.file(join(dir, 'skills/testing-guide/SKILL.md')).text()
89
- expect(skill2).toContain('# Testing Guide')
90
-
91
- const agent = await Bun.file(join(dir, 'agents/reviewer.md')).text()
92
- expect(agent).toContain('# Reviewer')
93
-
94
- const command = await Bun.file(join(dir, 'commands/deploy.md')).text()
95
- expect(command).toContain('# Deploy')
96
- })
97
-
98
- test('scaffolds with only one skill', async () => {
99
- const dir = await createFixtureDir('scaffold-skills-only')
100
- const files = await writeScaffold(
101
- {
102
- name: 'minimal',
103
- version: DEFAULT_VERSION,
104
- description: '',
105
- skills: ['minimal'],
106
- agents: [],
107
- commands: [],
108
- },
109
- dir,
110
- )
111
-
112
- expect(files).toContain('facet.json')
113
- expect(files).toContain('skills/minimal/SKILL.md')
114
- expect(files).not.toContain('agents/')
115
- expect(files).not.toContain('commands/')
116
-
117
- const manifestText = await Bun.file(join(dir, 'facet.json')).text()
118
- const manifest = JSON.parse(manifestText)
119
- expect(manifest.skills).toBeDefined()
120
- expect(manifest.agents).toBeUndefined()
121
- expect(manifest.commands).toBeUndefined()
122
- })
123
-
124
- test('version defaults to DEFAULT_VERSION (0.0.0)', async () => {
125
- const dir = await createFixtureDir('scaffold-default-version')
126
- await writeScaffold(
127
- {
128
- name: 'default-ver',
129
- version: DEFAULT_VERSION,
130
- description: 'Testing default version',
131
- skills: ['example'],
132
- agents: [],
133
- commands: [],
134
- },
135
- dir,
136
- )
137
-
138
- const manifestText = await Bun.file(join(dir, 'facet.json')).text()
139
- const manifest = JSON.parse(manifestText)
140
- expect(manifest.version).toBe('0.0.0')
141
- })
142
-
143
- test('scaffolded project passes build', async () => {
144
- const dir = await createFixtureDir('scaffold-buildable')
145
- await writeScaffold(
146
- {
147
- name: 'buildable',
148
- version: DEFAULT_VERSION,
149
- description: 'A buildable facet',
150
- skills: ['helper'],
151
- agents: ['assistant'],
152
- commands: [],
153
- },
154
- dir,
155
- )
156
-
157
- // Run facet build against the scaffolded project
158
- const result = await runCli('build', dir)
159
- expect(result.exitCode).toBe(0)
160
- expect(result.stdout).toContain('Built buildable')
161
-
162
- // Verify dist/ output exists — archive + build manifest
163
- const distArchive = await Bun.file(join(dir, `dist/buildable-${DEFAULT_VERSION}.facet`)).exists()
164
- expect(distArchive).toBe(true)
165
-
166
- const distManifest = await Bun.file(join(dir, 'dist/build-manifest.json')).exists()
167
- expect(distManifest).toBe(true)
168
-
169
- // No loose files
170
- const looseManifest = await Bun.file(join(dir, 'dist/facet.json')).exists()
171
- expect(looseManifest).toBe(false)
172
- })
173
- })
174
-
175
- // --- Build command (e2e) ---
176
-
177
- describe('facet build', () => {
178
- test('build succeeds on valid project', async () => {
179
- const dir = await createFixtureDir('build-valid')
180
- await Bun.write(join(dir, 'skills/review/SKILL.md'), '# Review skill content')
181
- await Bun.write(
182
- join(dir, 'facet.json'),
183
- JSON.stringify({
184
- name: 'test-facet',
185
- version: '1.0.0',
186
- skills: {
187
- review: {
188
- description: 'Code review skill',
189
- },
190
- },
191
- }),
192
- )
193
-
194
- const result = await runCli('build', dir)
195
- expect(result.exitCode).toBe(0)
196
- expect(result.stdout).toContain('Built test-facet')
197
- expect(result.stdout).toContain('sha256:')
198
- })
199
-
200
- test('build fails on missing manifest', async () => {
201
- const dir = await createFixtureDir('build-no-manifest')
202
-
203
- const result = await runCli('build', dir)
204
- expect(result.exitCode).toBe(1)
205
- expect(result.stderr).toContain('facet.json')
206
- })
207
-
208
- test('build fails on missing asset file', async () => {
209
- const dir = await createFixtureDir('build-missing-file')
210
- await Bun.write(
211
- join(dir, 'facet.json'),
212
- JSON.stringify({
213
- name: 'test-facet',
214
- version: '1.0.0',
215
- skills: {
216
- review: {
217
- description: 'Code review skill',
218
- },
219
- },
220
- }),
221
- )
222
-
223
- const result = await runCli('build', dir)
224
- expect(result.exitCode).toBe(1)
225
- expect(result.stdout).toContain('Build failed')
226
- })
227
- })
@@ -1,171 +0,0 @@
1
- import { describe, expect, test } from 'bun:test'
2
- import { mkdir } from 'node:fs/promises'
3
- import { tmpdir } from 'node:os'
4
- import { join } from 'node:path'
5
- import { runBuildPipeline } from '@agent-facets/core'
6
- import dedent from 'dedent'
7
- import { applyOperations, buildEditContext } from '../commands/edit/index.ts'
8
- import type { EditOperation } from '../tui/views/edit/edit-types.ts'
9
-
10
- async function createFixtureDir(name: string): Promise<string> {
11
- const dir = join(tmpdir(), `facets-edit-integ-${name}-${Date.now()}`)
12
- await mkdir(dir, { recursive: true })
13
- return dir
14
- }
15
-
16
- async function writeManifest(dir: string, manifest: Record<string, unknown>): Promise<void> {
17
- await Bun.write(join(dir, 'facet.json'), JSON.stringify(manifest, null, 2))
18
- }
19
-
20
- describe('edit integration', () => {
21
- test('buildEditContext detects new files on disk not in manifest', async () => {
22
- const dir = await createFixtureDir('detect-additions')
23
- await writeManifest(dir, {
24
- name: 'test',
25
- version: '1.0.0',
26
- skills: { existing: { description: 'Existing skill' } },
27
- })
28
- await mkdir(join(dir, 'skills/existing'), { recursive: true })
29
- await Bun.write(join(dir, 'skills/existing/SKILL.md'), '# Existing')
30
- // Add a file not in manifest
31
- await mkdir(join(dir, 'skills/new-one'), { recursive: true })
32
- await Bun.write(join(dir, 'skills/new-one/SKILL.md'), '# New')
33
-
34
- const result = await buildEditContext(dir)
35
- expect(result.ok).toBe(true)
36
- if (!result.ok) return
37
-
38
- const additions = result.context.reconciliationItems.filter((i) => i.kind === 'addition')
39
- expect(additions).toHaveLength(1)
40
- expect(additions[0]?.name).toBe('new-one')
41
- })
42
-
43
- test('buildEditContext detects missing files in manifest', async () => {
44
- const dir = await createFixtureDir('detect-missing')
45
- await writeManifest(dir, {
46
- name: 'test',
47
- version: '1.0.0',
48
- skills: {
49
- present: { description: 'Present' },
50
- gone: { description: 'Gone' },
51
- },
52
- })
53
- await mkdir(join(dir, 'skills/present'), { recursive: true })
54
- await Bun.write(join(dir, 'skills/present/SKILL.md'), '# Present')
55
- // 'gone' has no file on disk
56
-
57
- const result = await buildEditContext(dir)
58
- expect(result.ok).toBe(true)
59
- if (!result.ok) return
60
-
61
- const missing = result.context.reconciliationItems.filter((i) => i.kind === 'missing')
62
- expect(missing).toHaveLength(1)
63
- expect(missing[0]?.name).toBe('gone')
64
- })
65
-
66
- test('buildEditContext detects front matter in matched files', async () => {
67
- const dir = await createFixtureDir('detect-frontmatter')
68
- await writeManifest(dir, {
69
- name: 'test',
70
- version: '1.0.0',
71
- skills: { review: { description: 'Review' } },
72
- })
73
- await mkdir(join(dir, 'skills/review'), { recursive: true })
74
- await Bun.write(
75
- join(dir, 'skills/review/SKILL.md'),
76
- dedent`
77
- ---
78
- name: Review
79
- ---
80
- # Review skill
81
- `,
82
- )
83
-
84
- const result = await buildEditContext(dir)
85
- expect(result.ok).toBe(true)
86
- if (!result.ok) return
87
-
88
- const fm = result.context.reconciliationItems.filter((i) => i.kind === 'front-matter')
89
- expect(fm).toHaveLength(1)
90
- expect(fm[0]?.name).toBe('review')
91
- })
92
-
93
- test('applyOperations scaffolds new skill files', async () => {
94
- const dir = await createFixtureDir('scaffold-skill')
95
- const manifest = {
96
- name: 'test',
97
- version: '1.0.0',
98
- skills: { helper: { description: 'A helper skill' } },
99
- }
100
- const operations: EditOperation[] = [{ op: 'write-manifest' }, { op: 'scaffold', type: 'skills', name: 'helper' }]
101
-
102
- await applyOperations(manifest, operations, dir)
103
-
104
- const manifestExists = await Bun.file(join(dir, 'facet.json')).exists()
105
- expect(manifestExists).toBe(true)
106
-
107
- const skillExists = await Bun.file(join(dir, 'skills/helper/SKILL.md')).exists()
108
- expect(skillExists).toBe(true)
109
- })
110
-
111
- test('applyOperations strips front matter from files', async () => {
112
- const dir = await createFixtureDir('strip-fm')
113
- await mkdir(join(dir, 'skills/review'), { recursive: true })
114
- await Bun.write(
115
- join(dir, 'skills/review/SKILL.md'),
116
- dedent`
117
- ---
118
- name: Review
119
- description: A review skill
120
- ---
121
- # Review
122
- Review all code.
123
- `,
124
- )
125
-
126
- const manifest = {
127
- name: 'test',
128
- version: '1.0.0',
129
- skills: { review: { description: 'A review skill' } },
130
- }
131
- const operations: EditOperation[] = [
132
- { op: 'write-manifest' },
133
- { op: 'strip-front-matter', type: 'skills', name: 'review', path: 'skills/review/SKILL.md' },
134
- ]
135
-
136
- await applyOperations(manifest, operations, dir)
137
-
138
- const content = await Bun.file(join(dir, 'skills/review/SKILL.md')).text()
139
- expect(content).not.toContain('---')
140
- expect(content).toContain('# Review')
141
- })
142
-
143
- test('applyOperations deletes removed asset files', async () => {
144
- const dir = await createFixtureDir('delete-asset')
145
- await mkdir(join(dir, 'skills/old'), { recursive: true })
146
- await Bun.write(join(dir, 'skills/old/SKILL.md'), '# Old skill')
147
-
148
- const manifest = { name: 'test', version: '1.0.0', skills: { remaining: { description: 'Remaining' } } }
149
- const operations: EditOperation[] = [{ op: 'write-manifest' }, { op: 'delete-file', type: 'skills', name: 'old' }]
150
-
151
- await applyOperations(manifest, operations, dir)
152
-
153
- const deleted = await Bun.file(join(dir, 'skills/old/SKILL.md')).exists()
154
- expect(deleted).toBe(false)
155
- })
156
-
157
- test('scaffold then build succeeds end-to-end', async () => {
158
- const dir = await createFixtureDir('scaffold-then-build')
159
- const manifest = {
160
- name: 'test-facet',
161
- version: '1.0.0',
162
- skills: { example: { description: 'An example skill' } },
163
- }
164
- const operations: EditOperation[] = [{ op: 'write-manifest' }, { op: 'scaffold', type: 'skills', name: 'example' }]
165
-
166
- await applyOperations(manifest, operations, dir)
167
-
168
- const buildResult = await runBuildPipeline(dir)
169
- expect(buildResult.ok).toBe(true)
170
- })
171
- })
@@ -1,95 +0,0 @@
1
- import { describe, expect, test } from 'bun:test'
2
- import { mkdir } from 'node:fs/promises'
3
- import { tmpdir } from 'node:os'
4
- import { join } from 'node:path'
5
- import { resolveTargetDir } from '../commands/resolve-dir.ts'
6
-
7
- async function createFixtureDir(name: string): Promise<string> {
8
- const dir = join(tmpdir(), `facets-resolve-test-${name}-${Date.now()}`)
9
- await mkdir(dir, { recursive: true })
10
-
11
- return dir
12
- }
13
-
14
- describe('resolveTargetDir', () => {
15
- test('no arg defaults to cwd', async () => {
16
- const result = await resolveTargetDir(undefined, { mustExist: true })
17
- expect(result.ok).toBe(true)
18
- if (!result.ok) return
19
-
20
- expect(result.dir).toBe(process.cwd())
21
- expect(result.display).toBe('.')
22
- })
23
-
24
- test('facet.json path uses parent directory', async () => {
25
- const dir = await createFixtureDir('facet-json')
26
- await Bun.write(join(dir, 'facet.json'), '{}')
27
-
28
- const result = await resolveTargetDir(join(dir, 'facet.json'), { mustExist: true })
29
- expect(result.ok).toBe(true)
30
- if (!result.ok) return
31
-
32
- expect(result.dir).toBe(dir)
33
- })
34
-
35
- test('non-directory file returns error', async () => {
36
- const dir = await createFixtureDir('non-dir')
37
- const filePath = join(dir, 'not-a-dir.txt')
38
- await Bun.write(filePath, 'hello')
39
-
40
- const result = await resolveTargetDir(filePath, { mustExist: true })
41
- expect(result.ok).toBe(false)
42
- if (result.ok) return
43
-
44
- expect(result.message).toContain('Expected a directory')
45
- })
46
-
47
- test('non-existent dir with mustExist true returns error', async () => {
48
- const result = await resolveTargetDir(`/tmp/does-not-exist-${Date.now()}`, { mustExist: true })
49
- expect(result.ok).toBe(false)
50
- if (result.ok) return
51
-
52
- expect(result.message).toContain('does not exist')
53
- })
54
-
55
- test('non-existent dir with mustExist false auto-creates it', async () => {
56
- const dir = join(tmpdir(), `facets-resolve-autocreate-${Date.now()}`)
57
-
58
- const result = await resolveTargetDir(dir, { mustExist: false })
59
- expect(result.ok).toBe(true)
60
- if (!result.ok) return
61
-
62
- const { stat } = await import('node:fs/promises')
63
- const dirStat = await stat(result.dir)
64
- expect(dirStat.isDirectory()).toBe(true)
65
- })
66
-
67
- test('valid existing directory passes', async () => {
68
- const dir = await createFixtureDir('valid-dir')
69
-
70
- const result = await resolveTargetDir(dir, { mustExist: true })
71
- expect(result.ok).toBe(true)
72
- if (!result.ok) return
73
-
74
- expect(result.dir).toBe(dir)
75
- expect(result.display).toBe(dir)
76
- })
77
-
78
- test('facetMustExist true without facet.json returns error', async () => {
79
- const dir = await createFixtureDir('no-manifest')
80
-
81
- const result = await resolveTargetDir(dir, { mustExist: true, facetMustExist: true })
82
- expect(result.ok).toBe(false)
83
- if (result.ok) return
84
-
85
- expect(result.message).toContain('facet.json')
86
- })
87
-
88
- test('facetMustExist true with facet.json passes', async () => {
89
- const dir = await createFixtureDir('has-manifest')
90
- await Bun.write(join(dir, 'facet.json'), '{}')
91
-
92
- const result = await resolveTargetDir(dir, { mustExist: true, facetMustExist: true })
93
- expect(result.ok).toBe(true)
94
- })
95
- })