openspec-playwright 0.1.62 → 0.1.64

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 (55) hide show
  1. package/.claude/commands/CLAUDE.md +12 -0
  2. package/.claude/commands/opsx/CLAUDE.md +12 -0
  3. package/.claude/commands/opsx/e2e-body.md +5 -0
  4. package/.claude/commands/opsx/e2e.md +5 -1
  5. package/.claude/skills/CLAUDE.md +12 -0
  6. package/.claude/skills/openspec-e2e/CLAUDE.md +14 -0
  7. package/.claude/skills/openspec-e2e/SKILL.md +101 -77
  8. package/LICENSE +21 -0
  9. package/README.md +4 -3
  10. package/README.zh-CN.md +4 -3
  11. package/bin/CLAUDE.md +11 -0
  12. package/dist/CLAUDE.md +17 -0
  13. package/dist/commands/doctor.d.ts +4 -1
  14. package/dist/commands/doctor.js +110 -73
  15. package/dist/commands/doctor.js.map +1 -1
  16. package/dist/commands/editors.js +149 -95
  17. package/dist/commands/editors.js.map +1 -1
  18. package/dist/commands/init.js +105 -97
  19. package/dist/commands/init.js.map +1 -1
  20. package/dist/commands/mcpSync.js +46 -31
  21. package/dist/commands/mcpSync.js.map +1 -1
  22. package/dist/commands/run.d.ts +13 -0
  23. package/dist/commands/run.js +74 -51
  24. package/dist/commands/run.js.map +1 -1
  25. package/dist/commands/uninstall.d.ts +1 -0
  26. package/dist/commands/uninstall.js +133 -0
  27. package/dist/commands/uninstall.js.map +1 -0
  28. package/dist/commands/update.js +79 -68
  29. package/dist/commands/update.js.map +1 -1
  30. package/dist/index.js +33 -26
  31. package/dist/index.js.map +1 -1
  32. package/employee-standards.md +3 -3
  33. package/package.json +21 -1
  34. package/schemas/playwright-e2e/templates/playwright.config.ts +22 -22
  35. package/templates/CLAUDE.md +15 -0
  36. package/templates/seed.spec.ts +5 -3
  37. package/.github/workflows/release.yml +0 -81
  38. package/docs/plans/2026-03-26-openspec-playwright-design.md +0 -180
  39. package/openspec/schemas/playwright-e2e/schema.yaml +0 -56
  40. package/openspec/schemas/playwright-e2e/templates/e2e-test.ts +0 -55
  41. package/openspec/schemas/playwright-e2e/templates/playwright.config.ts +0 -52
  42. package/openspec/schemas/playwright-e2e/templates/report.md +0 -27
  43. package/openspec/schemas/playwright-e2e/templates/test-plan.md +0 -24
  44. package/openspec-playwright-0.1.62.tgz +0 -0
  45. package/release-notes.md +0 -5
  46. package/src/commands/doctor.ts +0 -115
  47. package/src/commands/editors.ts +0 -606
  48. package/src/commands/init.ts +0 -252
  49. package/src/commands/mcpSync.ts +0 -160
  50. package/src/commands/run.ts +0 -172
  51. package/src/commands/update.ts +0 -192
  52. package/src/index.ts +0 -47
  53. package/tests/editors.test.ts +0 -180
  54. package/tsconfig.json +0 -18
  55. package/vitest.config.ts +0 -9
@@ -1,606 +0,0 @@
1
- import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'fs';
2
- import { homedir } from 'os';
3
- import { join, dirname, resolve as pathResolve } from 'path';
4
- import chalk from 'chalk';
5
-
6
- /** Shared YAML escape — matches OpenSpec's escape logic */
7
- export function escapeYamlValue(value: string): string {
8
- const needsQuoting = /[:\n\r#{}[\],&*!|>'"%@`]|^\s|\s$/.test(value);
9
- if (needsQuoting) {
10
- const escaped = value.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n');
11
- return `"${escaped}"`;
12
- }
13
- return value;
14
- }
15
-
16
- /** Format tags as YAML inline array (escaped) */
17
- export function formatTagsArray(tags: string[]): string {
18
- return `[${tags.map(t => escapeYamlValue(t)).join(', ')}]`;
19
- }
20
-
21
- /** Format tags as YAML inline array (plain, no escaping) */
22
- function formatTagsPlain(tags: string[]): string {
23
- return `[${tags.join(', ')}]`;
24
- }
25
-
26
- /** Transform /opsx: to /opsx- for OpenCode */
27
- function transformToHyphenCommands(text: string): string {
28
- return text.replace(/\/opsx:/g, '/opsx-');
29
- }
30
-
31
- /** Command metadata shared across editors */
32
- export interface CommandMeta {
33
- id: string;
34
- name: string;
35
- description: string;
36
- category: string;
37
- tags: string[];
38
- body: string;
39
- }
40
-
41
- /** Editor adapter — Strategy Pattern */
42
- export interface EditorAdapter {
43
- toolId: string;
44
- hasSkill: boolean;
45
- getCommandPath(commandId: string): string;
46
- formatCommand(meta: CommandMeta): string;
47
- }
48
-
49
- // ─── Claude Code ──────────────────────────────────────────────────────────────
50
-
51
- /** Claude Code: .claude/commands/opsx/<id>.md + SKILL.md */
52
- const claudeAdapter: EditorAdapter = {
53
- toolId: 'claude',
54
- hasSkill: true,
55
- getCommandPath(id) { return join('.claude', 'commands', 'opsx', `${id}.md`); },
56
- formatCommand(meta) {
57
- return `---
58
- name: ${escapeYamlValue(meta.name)}
59
- description: ${escapeYamlValue(meta.description)}
60
- category: ${escapeYamlValue(meta.category)}
61
- tags: ${formatTagsArray(meta.tags)}
62
- ---
63
-
64
- ${meta.body}
65
- `;
66
- },
67
- };
68
-
69
- // ─── Cursor ─────────────────────────────────────────────────────────────────
70
-
71
- /** Cursor: .cursor/commands/opsx-<id>.md */
72
- const cursorAdapter: EditorAdapter = {
73
- toolId: 'cursor',
74
- hasSkill: false,
75
- getCommandPath(id) { return join('.cursor', 'commands', `opsx-${id}.md`); },
76
- formatCommand(meta) {
77
- return `---
78
- name: /opsx-${meta.id}
79
- id: opsx-${meta.id}
80
- category: ${escapeYamlValue(meta.category)}
81
- description: ${escapeYamlValue(meta.description)}
82
- ---
83
-
84
- ${meta.body}
85
- `;
86
- },
87
- };
88
-
89
- // ─── Windsurf ────────────────────────────────────────────────────────────────
90
-
91
- /** Windsurf: .windsurf/workflows/opsx-<id>.md */
92
- const windsurfAdapter: EditorAdapter = {
93
- toolId: 'windsurf',
94
- hasSkill: false,
95
- getCommandPath(id) { return join('.windsurf', 'workflows', `opsx-${id}.md`); },
96
- formatCommand(meta) {
97
- return `---
98
- name: ${escapeYamlValue(meta.name)}
99
- description: ${escapeYamlValue(meta.description)}
100
- category: ${escapeYamlValue(meta.category)}
101
- tags: ${formatTagsArray(meta.tags)}
102
- ---
103
-
104
- ${meta.body}
105
- `;
106
- },
107
- };
108
-
109
- // ─── Cline ──────────────────────────────────────────────────────────────────
110
-
111
- /** Cline: .clinerules/workflows/opsx-<id>.md — markdown header only */
112
- const clineAdapter: EditorAdapter = {
113
- toolId: 'cline',
114
- hasSkill: false,
115
- getCommandPath(id) { return join('.clinerules', 'workflows', `opsx-${id}.md`); },
116
- formatCommand(meta) {
117
- return `# ${meta.name}
118
-
119
- ${meta.description}
120
-
121
- ${meta.body}
122
- `;
123
- },
124
- };
125
-
126
- // ─── Continue ────────────────────────────────────────────────────────────────
127
-
128
- /** Continue: .continue/prompts/opsx-<id>.prompt */
129
- const continueAdapter: EditorAdapter = {
130
- toolId: 'continue',
131
- hasSkill: false,
132
- getCommandPath(id) { return join('.continue', 'prompts', `opsx-${id}.prompt`); },
133
- formatCommand(meta) {
134
- return `---
135
- name: opsx-${meta.id}
136
- description: ${meta.description}
137
- invokable: true
138
- ---
139
-
140
- ${meta.body}
141
- `;
142
- },
143
- };
144
-
145
- // ─── amazon-q ─────────────────────────────────────────────────────────────
146
-
147
- /** Amazon Q: .amazonq/prompts/opsx-<id>.md */
148
- const amazonqAdapter: EditorAdapter = {
149
- toolId: 'amazon-q',
150
- hasSkill: false,
151
- getCommandPath(id) { return join('.amazonq', 'prompts', `opsx-${id}.md`); },
152
- formatCommand(meta) {
153
- return `---
154
- description: ${meta.description}
155
- ---
156
-
157
- ${meta.body}
158
- `;
159
- },
160
- };
161
-
162
- // ─── antigravity ──────────────────────────────────────────────────────────
163
-
164
- /** Antigravity: .agent/workflows/opsx-<id>.md */
165
- const antigravityAdapter: EditorAdapter = {
166
- toolId: 'antigravity',
167
- hasSkill: false,
168
- getCommandPath(id) { return join('.agent', 'workflows', `opsx-${id}.md`); },
169
- formatCommand(meta) {
170
- return `---
171
- description: ${meta.description}
172
- ---
173
-
174
- ${meta.body}
175
- `;
176
- },
177
- };
178
-
179
- // ─── auggie ────────────────────────────────────────────────────────────────
180
-
181
- /** Auggie: .augment/commands/opsx-<id>.md */
182
- const auggieAdapter: EditorAdapter = {
183
- toolId: 'auggie',
184
- hasSkill: false,
185
- getCommandPath(id) { return join('.augment', 'commands', `opsx-${id}.md`); },
186
- formatCommand(meta) {
187
- return `---
188
- description: ${meta.description}
189
- argument-hint: command arguments
190
- ---
191
-
192
- ${meta.body}
193
- `;
194
- },
195
- };
196
-
197
- // ─── codebuddy ─────────────────────────────────────────────────────────────
198
-
199
- /** CodeBuddy: .codebuddy/commands/opsx/<id>.md */
200
- const codebuddyAdapter: EditorAdapter = {
201
- toolId: 'codebuddy',
202
- hasSkill: false,
203
- getCommandPath(id) { return join('.codebuddy', 'commands', 'opsx', `${id}.md`); },
204
- formatCommand(meta) {
205
- return `---
206
- name: ${meta.name}
207
- description: "${meta.description}"
208
- argument-hint: "[command arguments]"
209
- ---
210
-
211
- ${meta.body}
212
- `;
213
- },
214
- };
215
-
216
- // ─── codex ────────────────────────────────────────────────────────────────
217
-
218
- /** Codex: <CODEX_HOME>/prompts/opsx-<id>.md — global scope */
219
- const codexAdapter: EditorAdapter = {
220
- toolId: 'codex',
221
- hasSkill: false,
222
- getCommandPath(id) {
223
- const codexHome = process.env.CODEX_HOME?.trim() || join(homedir(), '.codex');
224
- // pathResolve: if codexHome is absolute (C:\...), it returns codexHome directly
225
- // if relative, it resolves against projectRoot
226
- return pathResolve(codexHome, 'prompts', `opsx-${id}.md`);
227
- },
228
- formatCommand(meta) {
229
- return `---
230
- description: ${meta.description}
231
- argument-hint: command arguments
232
- ---
233
-
234
- ${meta.body}
235
- `;
236
- },
237
- };
238
-
239
- // ─── costrict ─────────────────────────────────────────────────────────────
240
-
241
- /** CoStrict: .cospec/openspec/commands/opsx-<id>.md */
242
- const costrictAdapter: EditorAdapter = {
243
- toolId: 'costrict',
244
- hasSkill: false,
245
- getCommandPath(id) { return join('.cospec', 'openspec', 'commands', `opsx-${id}.md`); },
246
- formatCommand(meta) {
247
- return `---
248
- description: "${meta.description}"
249
- argument-hint: command arguments
250
- ---
251
-
252
- ${meta.body}
253
- `;
254
- },
255
- };
256
-
257
- // ─── crush ────────────────────────────────────────────────────────────────
258
-
259
- /** Crush: .crush/commands/opsx/<id>.md — raw values, no escaping */
260
- const crushAdapter: EditorAdapter = {
261
- toolId: 'crush',
262
- hasSkill: false,
263
- getCommandPath(id) { return join('.crush', 'commands', 'opsx', `${id}.md`); },
264
- formatCommand(meta) {
265
- return `---
266
- name: ${meta.name}
267
- description: ${meta.description}
268
- category: ${meta.category}
269
- tags: ${formatTagsPlain(meta.tags)}
270
- ---
271
-
272
- ${meta.body}
273
- `;
274
- },
275
- };
276
-
277
- // ─── factory ───────────────────────────────────────────────────────────────
278
-
279
- /** Factory Droid: .factory/commands/opsx-<id>.md */
280
- const factoryAdapter: EditorAdapter = {
281
- toolId: 'factory',
282
- hasSkill: false,
283
- getCommandPath(id) { return join('.factory', 'commands', `opsx-${id}.md`); },
284
- formatCommand(meta) {
285
- return `---
286
- description: ${meta.description}
287
- argument-hint: command arguments
288
- ---
289
-
290
- ${meta.body}
291
- `;
292
- },
293
- };
294
-
295
- // ─── gemini ────────────────────────────────────────────────────────────────
296
-
297
- /** Gemini CLI: .gemini/commands/opsx/<id>.toml */
298
- const geminiAdapter: EditorAdapter = {
299
- toolId: 'gemini',
300
- hasSkill: false,
301
- getCommandPath(id) { return join('.gemini', 'commands', 'opsx', `${id}.toml`); },
302
- formatCommand(meta) {
303
- return `description = "${meta.description}"
304
-
305
- prompt = """
306
- ${meta.body}
307
- """
308
- `;
309
- },
310
- };
311
-
312
- // ─── github-copilot ────────────────────────────────────────────────────────
313
-
314
- /** GitHub Copilot: .github/prompts/opsx-<id>.prompt.md */
315
- const githubcopilotAdapter: EditorAdapter = {
316
- toolId: 'github-copilot',
317
- hasSkill: false,
318
- getCommandPath(id) { return join('.github', 'prompts', `opsx-${id}.prompt.md`); },
319
- formatCommand(meta) {
320
- return `---
321
- description: ${meta.description}
322
- ---
323
-
324
- ${meta.body}
325
- `;
326
- },
327
- };
328
-
329
- // ─── iflow ────────────────────────────────────────────────────────────────
330
-
331
- /** iFlow: .iflow/commands/opsx-<id>.md */
332
- const iflowAdapter: EditorAdapter = {
333
- toolId: 'iflow',
334
- hasSkill: false,
335
- getCommandPath(id) { return join('.iflow', 'commands', `opsx-${id}.md`); },
336
- formatCommand(meta) {
337
- return `---
338
- name: /opsx-${meta.id}
339
- id: opsx-${meta.id}
340
- category: ${meta.category}
341
- description: ${meta.description}
342
- ---
343
-
344
- ${meta.body}
345
- `;
346
- },
347
- };
348
-
349
- // ─── kilocode ────────────────────────────────────────────────────────────
350
-
351
- /** Kilo Code: .kilocode/workflows/opsx-<id>.md — body only */
352
- const kilocodeAdapter: EditorAdapter = {
353
- toolId: 'kilocode',
354
- hasSkill: false,
355
- getCommandPath(id) { return join('.kilocode', 'workflows', `opsx-${id}.md`); },
356
- formatCommand(meta) {
357
- return `${meta.body}
358
- `;
359
- },
360
- };
361
-
362
- // ─── kiro ─────────────────────────────────────────────────────────────────
363
-
364
- /** Kiro: .kiro/prompts/opsx-<id>.prompt.md */
365
- const kiroAdapter: EditorAdapter = {
366
- toolId: 'kiro',
367
- hasSkill: false,
368
- getCommandPath(id) { return join('.kiro', 'prompts', `opsx-${id}.prompt.md`); },
369
- formatCommand(meta) {
370
- return `---
371
- description: ${meta.description}
372
- ---
373
-
374
- ${meta.body}
375
- `;
376
- },
377
- };
378
-
379
- // ─── opencode ─────────────────────────────────────────────────────────────
380
-
381
- /** OpenCode: .opencode/commands/opsx-<id>.md — transforms /opsx: to /opsx- */
382
- const opencodeAdapter: EditorAdapter = {
383
- toolId: 'opencode',
384
- hasSkill: false,
385
- getCommandPath(id) { return join('.opencode', 'commands', `opsx-${id}.md`); },
386
- formatCommand(meta) {
387
- const transformed = transformToHyphenCommands(meta.body);
388
- return `---
389
- description: ${meta.description}
390
- ---
391
-
392
- ${transformed}
393
- `;
394
- },
395
- };
396
-
397
- // ─── pi ──────────────────────────────────────────────────────────────────
398
-
399
- /** Pi: .pi/prompts/opsx-<id>.md */
400
- const piAdapter: EditorAdapter = {
401
- toolId: 'pi',
402
- hasSkill: false,
403
- getCommandPath(id) { return join('.pi', 'prompts', `opsx-${id}.md`); },
404
- formatCommand(meta) {
405
- return `---
406
- description: ${escapeYamlValue(meta.description)}
407
- ---
408
-
409
- ${meta.body}
410
- `;
411
- },
412
- };
413
-
414
- // ─── qoder ────────────────────────────────────────────────────────────────
415
-
416
- /** Qoder: .qoder/commands/opsx/<id>.md — raw values, no escaping */
417
- const qoderAdapter: EditorAdapter = {
418
- toolId: 'qoder',
419
- hasSkill: false,
420
- getCommandPath(id) { return join('.qoder', 'commands', 'opsx', `${id}.md`); },
421
- formatCommand(meta) {
422
- return `---
423
- name: ${meta.name}
424
- description: ${meta.description}
425
- category: ${meta.category}
426
- tags: ${formatTagsPlain(meta.tags)}
427
- ---
428
-
429
- ${meta.body}
430
- `;
431
- },
432
- };
433
-
434
- // ─── qwen ────────────────────────────────────────────────────────────────
435
-
436
- /** Qwen Code: .qwen/commands/opsx-<id>.toml */
437
- const qwenAdapter: EditorAdapter = {
438
- toolId: 'qwen',
439
- hasSkill: false,
440
- getCommandPath(id) { return join('.qwen', 'commands', `opsx-${id}.toml`); },
441
- formatCommand(meta) {
442
- return `description = "${meta.description}"
443
-
444
- prompt = """
445
- ${meta.body}
446
- """
447
- `;
448
- },
449
- };
450
-
451
- // ─── roocode ─────────────────────────────────────────────────────────────
452
-
453
- /** RooCode: .roo/commands/opsx-<id>.md — markdown header */
454
- const roocodeAdapter: EditorAdapter = {
455
- toolId: 'roocode',
456
- hasSkill: false,
457
- getCommandPath(id) { return join('.roo', 'commands', `opsx-${id}.md`); },
458
- formatCommand(meta) {
459
- return `# ${meta.name}
460
-
461
- ${meta.description}
462
-
463
- ${meta.body}
464
- `;
465
- },
466
- };
467
-
468
- // ─── Detection map ───────────────────────────────────────────────────────
469
-
470
- const ALL_ADAPTERS: EditorAdapter[] = [
471
- claudeAdapter,
472
- cursorAdapter,
473
- windsurfAdapter,
474
- clineAdapter,
475
- continueAdapter,
476
- amazonqAdapter,
477
- antigravityAdapter,
478
- auggieAdapter,
479
- codebuddyAdapter,
480
- codexAdapter,
481
- costrictAdapter,
482
- crushAdapter,
483
- factoryAdapter,
484
- geminiAdapter,
485
- githubcopilotAdapter,
486
- iflowAdapter,
487
- kilocodeAdapter,
488
- kiroAdapter,
489
- opencodeAdapter,
490
- piAdapter,
491
- qoderAdapter,
492
- qwenAdapter,
493
- roocodeAdapter,
494
- ];
495
-
496
- /** Detect which editors are installed by checking their config directories */
497
- export function detectEditors(projectRoot: string): EditorAdapter[] {
498
- const checks: Array<[string, EditorAdapter]> = [
499
- ['.claude', claudeAdapter],
500
- ['.cursor', cursorAdapter],
501
- ['.windsurf', windsurfAdapter],
502
- ['.clinerules', clineAdapter],
503
- ['.continue', continueAdapter],
504
- ['.amazonq', amazonqAdapter],
505
- ['.agent', antigravityAdapter],
506
- ['.augment', auggieAdapter],
507
- ['.codebuddy', codebuddyAdapter],
508
- ['.cospec', costrictAdapter],
509
- ['.crush', crushAdapter],
510
- ['.factory', factoryAdapter],
511
- ['.gemini', geminiAdapter],
512
- ['.github', githubcopilotAdapter],
513
- ['.iflow', iflowAdapter],
514
- ['.kilocode', kilocodeAdapter],
515
- ['.kiro', kiroAdapter],
516
- ['.opencode', opencodeAdapter],
517
- ['.pi', piAdapter],
518
- ['.qoder', qoderAdapter],
519
- ['.qwen', qwenAdapter],
520
- ['.roo', roocodeAdapter],
521
- ];
522
-
523
- return checks
524
- .filter(([dir]) => existsSync(join(projectRoot, dir)))
525
- .map(([, adapter]) => adapter);
526
- }
527
-
528
- // ─── Codex is global — detect separately ─────────────────────────────────
529
-
530
- /** Detect Codex by checking if CODEX_HOME or ~/.codex exists */
531
- export function detectCodex(): EditorAdapter | null {
532
- const codexHome = process.env.CODEX_HOME?.trim() || join(homedir(), '.codex');
533
- return existsSync(codexHome) ? codexAdapter : null;
534
- }
535
-
536
- // ─── Install helpers ───────────────────────────────────────────────────────
537
-
538
- /** Build the shared command metadata */
539
- export function buildCommandMeta(body: string): CommandMeta {
540
- return {
541
- id: 'e2e',
542
- name: 'OPSX: E2E',
543
- description: 'Run Playwright E2E verification for an OpenSpec change',
544
- category: 'OpenSpec',
545
- tags: ['openspec', 'playwright', 'e2e', 'testing'],
546
- body,
547
- };
548
- }
549
-
550
- /** Install command files for all detected editors */
551
- export function installForAllEditors(
552
- body: string,
553
- adapters: EditorAdapter[],
554
- projectRoot: string
555
- ): void {
556
- const meta = buildCommandMeta(body);
557
-
558
- for (const adapter of adapters) {
559
- const relPath = adapter.getCommandPath(meta.id);
560
- const absPath = pathResolve(projectRoot, relPath);
561
- mkdirSync(dirname(absPath), { recursive: true });
562
- writeFileSync(absPath, adapter.formatCommand(meta));
563
- console.log(chalk.green(` ✓ ${adapter.toolId}: ${relPath}`));
564
- }
565
- }
566
-
567
- /** Install SKILL.md only for Claude Code */
568
- export function installSkill(projectRoot: string, skillContent: string): void {
569
- const skillDir = join(projectRoot, '.claude', 'skills', 'openspec-e2e');
570
- mkdirSync(skillDir, { recursive: true });
571
- writeFileSync(join(skillDir, 'SKILL.md'), skillContent);
572
- console.log(chalk.green(` ✓ claude: .claude/skills/openspec-e2e/SKILL.md`));
573
- }
574
-
575
- /** Install project-level CLAUDE.md with employee-grade standards + OpenSpec context */
576
- export function installProjectClaudeMd(projectRoot: string, standardsContent: string): void {
577
- const dest = join(projectRoot, 'CLAUDE.md');
578
- const exists = existsSync(dest);
579
- if (exists) {
580
- // Append standards inside OPENSPEC:START/END markers
581
- const existing = readFileSync(dest, 'utf-8');
582
- const markerStart = '<!-- OPENSPEC:START -->';
583
- const markerEnd = '<!-- OPENSPEC:END -->';
584
-
585
- if (existing.includes(markerStart) && existing.includes(markerEnd)) {
586
- // Already has markers, skip
587
- console.log(chalk.gray(' - CLAUDE.md already has standards markers, skipping'));
588
- } else {
589
- const updated = existing.trim() + '\n\n' + markerStart + '\n\n' + standardsContent + '\n\n' + markerEnd + '\n';
590
- writeFileSync(dest, updated);
591
- console.log(chalk.green(' ✓ CLAUDE.md: appended employee-grade standards'));
592
- }
593
- } else {
594
- // No existing CLAUDE.md, create from template
595
- const content = `# ${projectRoot.split('/').pop()}\n\n` + standardsContent;
596
- writeFileSync(dest, content);
597
- console.log(chalk.green(' ✓ CLAUDE.md: created with employee-grade standards'));
598
- }
599
- }
600
-
601
- /** Read the employee-grade standards from a source file */
602
- export function readEmployeeStandards(srcPath: string): string {
603
- return existsSync(srcPath) ? readFileSync(srcPath, 'utf-8') : '';
604
- }
605
-
606
- export { claudeAdapter, ALL_ADAPTERS };