abrez ‎ commited on
Commit
67978ff
·
1 Parent(s): 281e1e1

feat(cli): standardize on .agents/skills/ and root AGENTS.md for universal agent discovery

Browse files
packages/cli/src/commands/skills.ts CHANGED
@@ -112,7 +112,7 @@ export async function handleSkillsShow(skillId: string, options: OutputOptions =
112
  }
113
 
114
  export interface SkillInstallOptions extends OutputOptions {
115
- target?: 'gemini' | 'cursor' | 'claude' | 'auto';
116
  global?: boolean;
117
  yes?: boolean;
118
  }
@@ -132,16 +132,17 @@ export async function handleSkillsInstall(skillId: string, options: SkillInstall
132
  name: 'target',
133
  message: `Select target agent environment for skill '${skillId}':`,
134
  choices: [
135
- { title: 'Antigravity / Gemini (~/.gemini/skills/ or .gemini/skills/)', value: 'gemini' },
136
  { title: 'Cursor (.cursor/rules/)', value: 'cursor' },
137
  { title: 'Claude Code (.claude/skills/)', value: 'claude' },
 
138
  ],
139
  });
140
  target = response.target;
141
  }
142
 
143
  const result = materializeService.materialize(workspaceRoot, skillId, {
144
- target: target || 'gemini',
145
  global: options.global,
146
  });
147
 
 
112
  }
113
 
114
  export interface SkillInstallOptions extends OutputOptions {
115
+ target?: 'agents' | 'gemini' | 'cursor' | 'claude' | 'auto';
116
  global?: boolean;
117
  yes?: boolean;
118
  }
 
132
  name: 'target',
133
  message: `Select target agent environment for skill '${skillId}':`,
134
  choices: [
135
+ { title: 'Universal Agent Standard (.agents/skills/ — Recommended)', value: 'agents' },
136
  { title: 'Cursor (.cursor/rules/)', value: 'cursor' },
137
  { title: 'Claude Code (.claude/skills/)', value: 'claude' },
138
+ { title: 'Gemini (~/.gemini/skills/)', value: 'gemini' },
139
  ],
140
  });
141
  target = response.target;
142
  }
143
 
144
  const result = materializeService.materialize(workspaceRoot, skillId, {
145
+ target: target || 'agents',
146
  global: options.global,
147
  });
148
 
packages/cli/src/services/materialize-service.ts CHANGED
@@ -15,11 +15,11 @@ export class MaterializeService {
15
  workspaceRoot: string,
16
  skillId: string,
17
  options?: {
18
- target?: 'gemini' | 'cursor' | 'claude' | 'auto';
19
  global?: boolean;
20
  }
21
  ): MaterializeResult {
22
- const targetAgent = options?.target && options.target !== 'auto' ? options.target : 'gemini';
23
  const isGlobal = !!options?.global;
24
 
25
  // Locate skill in cache
@@ -36,7 +36,11 @@ export class MaterializeService {
36
  let destDir = '';
37
  let primaryFile = '';
38
 
39
- if (targetAgent === 'gemini') {
 
 
 
 
40
  const base = isGlobal ? path.join(os.homedir(), '.gemini', 'skills') : path.join(workspaceRoot, '.gemini', 'skills');
41
  destDir = path.join(base, skillId);
42
  primaryFile = path.join(destDir, 'SKILL.md');
@@ -65,7 +69,7 @@ export class MaterializeService {
65
  filesCopied++;
66
  }
67
  } else {
68
- // For Gemini & Claude, copy directory structure
69
  const subPath = file.startsWith(`${skillDirRel}/`)
70
  ? file.slice(`${skillDirRel}/`.length)
71
  : 'SKILL.md';
 
15
  workspaceRoot: string,
16
  skillId: string,
17
  options?: {
18
+ target?: 'agents' | 'gemini' | 'cursor' | 'claude' | 'auto';
19
  global?: boolean;
20
  }
21
  ): MaterializeResult {
22
+ const targetAgent = options?.target && options.target !== 'auto' ? options.target : 'agents';
23
  const isGlobal = !!options?.global;
24
 
25
  // Locate skill in cache
 
36
  let destDir = '';
37
  let primaryFile = '';
38
 
39
+ if (targetAgent === 'agents') {
40
+ const base = isGlobal ? path.join(os.homedir(), '.agents', 'skills') : path.join(workspaceRoot, '.agents', 'skills');
41
+ destDir = path.join(base, skillId);
42
+ primaryFile = path.join(destDir, 'SKILL.md');
43
+ } else if (targetAgent === 'gemini') {
44
  const base = isGlobal ? path.join(os.homedir(), '.gemini', 'skills') : path.join(workspaceRoot, '.gemini', 'skills');
45
  destDir = path.join(base, skillId);
46
  primaryFile = path.join(destDir, 'SKILL.md');
 
69
  filesCopied++;
70
  }
71
  } else {
72
+ // For universal .agents, Gemini & Claude, copy directory structure
73
  const subPath = file.startsWith(`${skillDirRel}/`)
74
  ? file.slice(`${skillDirRel}/`.length)
75
  : 'SKILL.md';
packages/cli/src/services/meta-skill-service.ts CHANGED
@@ -2,16 +2,8 @@ import fs from 'node:fs';
2
  import path from 'node:path';
3
 
4
  export const META_SKILL_CONTENT = `---
5
- name: knowiki-operator
6
- description: Query, inspect, modify, and publish shared project knowledge, runbooks, and conventions using the Knowiki CLI.
7
- whenToUse: Use when the developer asks about project architecture, conventions, workflows, database rules, or asks to save, update, or propose runbooks and skills.
8
- triggers:
9
- - "what is our convention for"
10
- - "how do we deploy/migrate/test"
11
- - "update the runbook"
12
- - "save this knowledge/skill"
13
- - "propose a rule change"
14
- - "knowiki"
15
  ---
16
 
17
  # Knowiki Agent Operator Guide
@@ -35,44 +27,40 @@ When the user asks to save, update, or create a skill or knowledge doc:
35
  4. Confirm the resulting commit SHA or Pull Request URL to the user.
36
  `;
37
 
38
- export const CURSOR_RULE_CONTENT = `---
39
- description: Knowiki project intelligence operator rule
40
- globs: .knowiki/**
41
- ---
42
 
43
- # Knowiki Project Intelligence Guide
44
 
45
- Use the Knowiki CLI to read, inspect, and update shared project intelligence:
46
- - Query status: \`knowiki status --json\`
47
- - View knowledge: \`knowiki knowledge list\` / \`knowiki knowledge show <path>\`
48
- - View skills: \`knowiki skills list\` / \`knowiki skills show <skill-id>\`
49
- - Modify files in \`.knowiki/cache/\`, check \`knowiki diff\`, and publish via \`knowiki push -m "..."\` or \`knowiki propose\`.
50
  `;
51
 
52
  export class MetaSkillService {
53
  bootstrapMetaSkill(workspaceRoot: string): { installedLocations: string[] } {
54
  const installedLocations: string[] = [];
55
 
56
- // 1. Antigravity / Gemini: .gemini/skills/knowiki/SKILL.md
57
- const geminiSkillDir = path.join(workspaceRoot, '.gemini', 'skills', 'knowiki');
58
- fs.mkdirSync(geminiSkillDir, { recursive: true });
59
- const geminiSkillFile = path.join(geminiSkillDir, 'SKILL.md');
60
- fs.writeFileSync(geminiSkillFile, META_SKILL_CONTENT, 'utf8');
61
- installedLocations.push('.gemini/skills/knowiki/SKILL.md');
62
-
63
- // 2. Cursor: .cursor/rules/knowiki.mdc
64
- const cursorDir = path.join(workspaceRoot, '.cursor', 'rules');
65
- fs.mkdirSync(cursorDir, { recursive: true });
66
- const cursorFile = path.join(cursorDir, 'knowiki.mdc');
67
- fs.writeFileSync(cursorFile, CURSOR_RULE_CONTENT, 'utf8');
68
- installedLocations.push('.cursor/rules/knowiki.mdc');
69
 
70
- // 3. Claude: .claude/skills/knowiki/SKILL.md
71
- const claudeDir = path.join(workspaceRoot, '.claude', 'skills', 'knowiki');
72
- fs.mkdirSync(claudeDir, { recursive: true });
73
- const claudeFile = path.join(claudeDir, 'SKILL.md');
74
- fs.writeFileSync(claudeFile, META_SKILL_CONTENT, 'utf8');
75
- installedLocations.push('.claude/skills/knowiki/SKILL.md');
 
 
 
 
 
 
76
 
77
  return { installedLocations };
78
  }
 
2
  import path from 'node:path';
3
 
4
  export const META_SKILL_CONTENT = `---
5
+ name: knowiki
6
+ description: Query, inspect, modify, and publish shared project knowledge, runbooks, and conventions using the Knowiki CLI. Use whenever asking about project conventions, architecture, runbooks, or saving shared knowledge.
 
 
 
 
 
 
 
 
7
  ---
8
 
9
  # Knowiki Agent Operator Guide
 
27
  4. Confirm the resulting commit SHA or Pull Request URL to the user.
28
  `;
29
 
30
+ export const AGENTS_MD_SECTION = `
31
+ # Project Intelligence (Knowiki)
 
 
32
 
33
+ This repository uses **Knowiki** for shared project knowledge, conventions, and operational runbooks.
34
 
35
+ - **Local Knowledge Cache**: \`.knowiki/cache/\`
36
+ - **Query CLI**: \`knowiki status --json\`, \`knowiki knowledge show <path>\`, \`knowiki skills list\`
37
+ - **Skill Runbooks**: \`.agents/skills/\` (Activate the \`knowiki\` skill for runbook operations)
38
+ - **Modifications**: When asked to update, save, or propose rules/skills, edit the file in \`.knowiki/cache/\`, inspect \`knowiki diff\`, and publish via \`knowiki push -m "..."\` (maintainers) or \`knowiki propose\` (PR review).
 
39
  `;
40
 
41
  export class MetaSkillService {
42
  bootstrapMetaSkill(workspaceRoot: string): { installedLocations: string[] } {
43
  const installedLocations: string[] = [];
44
 
45
+ // 1. Universal Agent Standard: .agents/skills/knowiki/SKILL.md
46
+ const agentsSkillDir = path.join(workspaceRoot, '.agents', 'skills', 'knowiki');
47
+ fs.mkdirSync(agentsSkillDir, { recursive: true });
48
+ const agentsSkillFile = path.join(agentsSkillDir, 'SKILL.md');
49
+ fs.writeFileSync(agentsSkillFile, META_SKILL_CONTENT, 'utf8');
50
+ installedLocations.push('.agents/skills/knowiki/SKILL.md');
 
 
 
 
 
 
 
51
 
52
+ // 2. Project Root Context: AGENTS.md
53
+ const agentsMdPath = path.join(workspaceRoot, 'AGENTS.md');
54
+ if (fs.existsSync(agentsMdPath)) {
55
+ const existing = fs.readFileSync(agentsMdPath, 'utf8');
56
+ if (!existing.includes('Project Intelligence (Knowiki)')) {
57
+ fs.appendFileSync(agentsMdPath, `\n${AGENTS_MD_SECTION.trim()}\n`, 'utf8');
58
+ installedLocations.push('AGENTS.md (updated)');
59
+ }
60
+ } else {
61
+ fs.writeFileSync(agentsMdPath, AGENTS_MD_SECTION.trim() + '\n', 'utf8');
62
+ installedLocations.push('AGENTS.md');
63
+ }
64
 
65
  return { installedLocations };
66
  }
packages/cli/test/materialize-service.test.ts CHANGED
@@ -16,23 +16,23 @@ describe('MaterializeService', () => {
16
  fs.rmSync(testDir, { recursive: true, force: true });
17
  });
18
 
19
- it('materializes skill into Gemini environment', () => {
20
  cacheService.writeCachedFile(
21
  testDir,
22
  'skills/db-migrate/SKILL.md',
23
  '---\nname: db-migrate\n---\n# DB Migration Runbook'
24
  );
25
 
26
- const result = materializeService.materialize(testDir, 'db-migrate', { target: 'gemini' });
27
- expect(result.targetAgent).toBe('gemini');
28
  expect(result.filesCopied).toBe(1);
29
 
30
- const destFile = path.join(testDir, '.gemini', 'skills', 'db-migrate', 'SKILL.md');
31
  expect(fs.existsSync(destFile)).toBe(true);
32
  expect(fs.readFileSync(destFile, 'utf8')).toContain('DB Migration Runbook');
33
  });
34
 
35
- it('materializes skill into Cursor environment as .mdc rule', () => {
36
  cacheService.writeCachedFile(
37
  testDir,
38
  'skills/testing/SKILL.md',
 
16
  fs.rmSync(testDir, { recursive: true, force: true });
17
  });
18
 
19
+ it('materializes skill into universal .agents environment by default', () => {
20
  cacheService.writeCachedFile(
21
  testDir,
22
  'skills/db-migrate/SKILL.md',
23
  '---\nname: db-migrate\n---\n# DB Migration Runbook'
24
  );
25
 
26
+ const result = materializeService.materialize(testDir, 'db-migrate');
27
+ expect(result.targetAgent).toBe('agents');
28
  expect(result.filesCopied).toBe(1);
29
 
30
+ const destFile = path.join(testDir, '.agents', 'skills', 'db-migrate', 'SKILL.md');
31
  expect(fs.existsSync(destFile)).toBe(true);
32
  expect(fs.readFileSync(destFile, 'utf8')).toContain('DB Migration Runbook');
33
  });
34
 
35
+ it('materializes skill into Cursor environment as .mdc rule when requested', () => {
36
  cacheService.writeCachedFile(
37
  testDir,
38
  'skills/testing/SKILL.md',
packages/cli/test/meta-skill-service.test.ts CHANGED
@@ -15,17 +15,21 @@ describe('MetaSkillService', () => {
15
  fs.rmSync(testDir, { recursive: true, force: true });
16
  });
17
 
18
- it('bootstraps meta-skill files across Gemini, Cursor, and Claude target paths', () => {
19
  const result = metaSkillService.bootstrapMetaSkill(testDir);
20
 
21
- expect(result.installedLocations).toContain('.gemini/skills/knowiki/SKILL.md');
22
- expect(result.installedLocations).toContain('.cursor/rules/knowiki.mdc');
23
- expect(result.installedLocations).toContain('.claude/skills/knowiki/SKILL.md');
24
 
25
- const geminiSkillPath = path.join(testDir, '.gemini', 'skills', 'knowiki', 'SKILL.md');
26
- expect(fs.existsSync(geminiSkillPath)).toBe(true);
27
- const content = fs.readFileSync(geminiSkillPath, 'utf8');
28
- expect(content).toContain('name: knowiki-operator');
29
- expect(content).toContain('knowiki status');
 
 
 
 
 
30
  });
31
  });
 
15
  fs.rmSync(testDir, { recursive: true, force: true });
16
  });
17
 
18
+ it('bootstraps universal .agents/skills/knowiki/SKILL.md and project AGENTS.md', () => {
19
  const result = metaSkillService.bootstrapMetaSkill(testDir);
20
 
21
+ expect(result.installedLocations).toContain('.agents/skills/knowiki/SKILL.md');
22
+ expect(result.installedLocations).toContain('AGENTS.md');
 
23
 
24
+ const agentsSkillPath = path.join(testDir, '.agents', 'skills', 'knowiki', 'SKILL.md');
25
+ expect(fs.existsSync(agentsSkillPath)).toBe(true);
26
+ const skillContent = fs.readFileSync(agentsSkillPath, 'utf8');
27
+ expect(skillContent).toContain('name: knowiki');
28
+ expect(skillContent).toContain('knowiki status');
29
+
30
+ const agentsMdPath = path.join(testDir, 'AGENTS.md');
31
+ expect(fs.existsSync(agentsMdPath)).toBe(true);
32
+ const mdContent = fs.readFileSync(agentsMdPath, 'utf8');
33
+ expect(mdContent).toContain('Project Intelligence (Knowiki)');
34
  });
35
  });