@ionivetech/mugiwara 0.2.0 → 0.4.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.
Files changed (99) hide show
  1. package/.opencode/commands/mugiwara-execute.md +11 -0
  2. package/.opencode/commands/mugiwara-heal.md +11 -0
  3. package/.opencode/commands/mugiwara-mode.md +6 -0
  4. package/.opencode/commands/mugiwara-plan.md +15 -0
  5. package/.opencode/commands/mugiwara-review.md +11 -0
  6. package/.opencode/commands/mugiwara-security.md +11 -0
  7. package/.opencode/commands/mugiwara-ship.md +11 -0
  8. package/.opencode/commands/mugiwara.md +11 -0
  9. package/.opencode/plugins/mugiwara.mjs +126 -7
  10. package/README.md +338 -355
  11. package/content/agents/brook-healing.md +2 -2
  12. package/content/agents/luffy-orchestrator.md +3 -2
  13. package/content/agents/robin-reviewer.md +1 -1
  14. package/content/agents/skeptic-verifier.md +1 -1
  15. package/content/agents/using-mugiwara.md +5 -1
  16. package/content/agents/usopp-brainstorm.md +1 -1
  17. package/content/agents/zoro-execution.md +1 -1
  18. package/content/skills/mugiwara-agent-security/SKILL.md +5 -0
  19. package/content/skills/mugiwara-api-and-interface-design/SKILL.md +92 -0
  20. package/content/skills/mugiwara-backend/SKILL.md +5 -0
  21. package/content/skills/mugiwara-brainstorm/SKILL.md +5 -0
  22. package/content/skills/mugiwara-checkpoint/SKILL.md +5 -0
  23. package/content/skills/mugiwara-context-engineering/SKILL.md +64 -0
  24. package/content/skills/mugiwara-deprecation/SKILL.md +5 -0
  25. package/content/skills/mugiwara-doubt-driven-development/SKILL.md +70 -0
  26. package/content/skills/mugiwara-dynamic-workflow/SKILL.md +5 -0
  27. package/content/skills/mugiwara-eval/SKILL.md +5 -0
  28. package/content/skills/mugiwara-execution/SKILL.md +9 -0
  29. package/content/skills/mugiwara-frontend/SKILL.md +61 -59
  30. package/content/skills/mugiwara-frontend/references/checklist.md +37 -0
  31. package/content/skills/mugiwara-gates/SKILL.md +9 -0
  32. package/content/skills/mugiwara-git/SKILL.md +5 -0
  33. package/content/skills/mugiwara-git-worktrees/SKILL.md +67 -0
  34. package/content/skills/mugiwara-healing/SKILL.md +17 -0
  35. package/content/skills/mugiwara-lessons/SKILL.md +5 -0
  36. package/content/skills/mugiwara-mode/SKILL.md +18 -4
  37. package/content/skills/mugiwara-observability/SKILL.md +5 -0
  38. package/content/skills/mugiwara-orchestration/SKILL.md +32 -1
  39. package/content/skills/mugiwara-planning/SKILL.md +25 -26
  40. package/content/skills/mugiwara-pr/SKILL.md +22 -6
  41. package/content/skills/mugiwara-quality/SKILL.md +15 -0
  42. package/content/skills/mugiwara-resume/SKILL.md +5 -0
  43. package/content/skills/mugiwara-review/SKILL.md +5 -0
  44. package/content/skills/mugiwara-security/SKILL.md +51 -14
  45. package/content/skills/mugiwara-ship/SKILL.md +29 -1
  46. package/content/skills/mugiwara-systematic-debugging/SKILL.md +82 -0
  47. package/content/skills/mugiwara-test-driven-development/SKILL.md +89 -0
  48. package/content/skills/mugiwara-testcases/SKILL.md +5 -0
  49. package/content/skills/mugiwara-workflow/SKILL.md +14 -3
  50. package/content/skills/mugiwara-writing-skills/SKILL.md +65 -0
  51. package/dist/mugiwara.js +175 -51
  52. package/docs/adoption-guide.md +3 -3
  53. package/docs/agents.md +2 -2
  54. package/docs/claude-setup.md +9 -4
  55. package/docs/codex-setup.md +3 -1
  56. package/docs/comparison.md +166 -44
  57. package/docs/config.md +53 -0
  58. package/docs/copilot-setup.md +3 -1
  59. package/docs/cursor-setup.md +3 -1
  60. package/docs/developer-onboarding.md +17 -13
  61. package/docs/enforcement.md +38 -0
  62. package/docs/execution-model.md +33 -0
  63. package/docs/gemini-setup.md +4 -1
  64. package/docs/getting-started.md +16 -4
  65. package/docs/index.md +14 -3
  66. package/docs/lanes.md +40 -0
  67. package/docs/modes.md +22 -12
  68. package/docs/opencode-setup.md +9 -2
  69. package/docs/pr-summary.md +54 -0
  70. package/docs/skill-anatomy.md +16 -6
  71. package/docs/skills.md +17 -5
  72. package/docs/troubleshooting.md +91 -0
  73. package/docs/windsurf-setup.md +3 -1
  74. package/docs/workflow.md +15 -6
  75. package/evals/cases/adversarial-pressure-fake-pass.json +12 -0
  76. package/evals/cases/adversarial-pressure-skip-review.json +13 -0
  77. package/evals/cases/lane-exploratory-vague.json +12 -0
  78. package/evals/cases/lane-sensitivity-payment.json +12 -0
  79. package/evals/cases/negative-secrets-typo.json +12 -0
  80. package/evals/cases/negative-security-docs-change.json +12 -0
  81. package/evals/cases/positive-refactor-existing-tests.json +11 -0
  82. package/evals/cases/positive-resume-mid-mission.json +11 -0
  83. package/evals/cases/routing-auth-feature.json +13 -0
  84. package/evals/cases/routing-bug-one-file.json +13 -0
  85. package/evals/cases/routing-typo.json +13 -0
  86. package/hooks/hooks.json +15 -0
  87. package/hooks/session-start.ts +8 -0
  88. package/package.json +3 -1
  89. package/src/args.ts +2 -1
  90. package/src/cli.ts +16 -20
  91. package/src/installer.ts +31 -3
  92. package/src/mission.ts +25 -0
  93. package/src/targets/claude.ts +22 -1
  94. package/src/targets/codex.ts +1 -1
  95. package/src/targets/copilot.ts +4 -0
  96. package/src/targets/gemini.ts +1 -1
  97. package/src/targets/generic.ts +33 -1
  98. package/src/targets/kilo.ts +1 -0
  99. package/src/targets/opencode.ts +4 -0
package/src/cli.ts CHANGED
@@ -1,15 +1,15 @@
1
1
  #!/usr/bin/env node
2
2
  // src/cli.ts
3
- import { existsSync, readFileSync, readdirSync, realpathSync, rmSync } from 'node:fs';
3
+ import { existsSync, readFileSync, realpathSync, rmSync } from 'node:fs';
4
4
  import { homedir } from 'node:os';
5
- import { join, resolve } from 'node:path';
5
+ import { resolve } from 'node:path';
6
6
  import { pathToFileURL } from 'node:url';
7
7
  import { parseArgs, type FlagValue, type Args } from './args.ts';
8
8
  import { createRl, choose, multiChoose, confirm } from './prompt.ts';
9
9
  import { targets, TARGET_IDS } from './targets/index.ts';
10
- import { installTo, removeInstalled, VERSION, CONTENT_DIR } from './installer.ts';
10
+ import { installTo, removeInstalled, VERSION } from './installer.ts';
11
11
  import { manifestPath, readManifest, writeManifest, type Scope } from './manifest.ts';
12
- import { parseFrontmatter } from './frontmatter.ts';
12
+ import { resetMission } from './mission.ts';
13
13
 
14
14
  const str = (v: FlagValue): string | undefined => (typeof v === 'string' ? v : undefined);
15
15
  const flag = (v: FlagValue): boolean => v === true;
@@ -23,11 +23,19 @@ export async function run(argv: string[]): Promise<void> {
23
23
  case 'update': return install({ ...flags, force: true });
24
24
  case 'uninstall': return uninstall(flags);
25
25
  case 'list': return list(flags);
26
- case 'skills': return skills();
26
+ case 'reset': return resetCmd(flags);
27
27
  default: throw new Error(`Unknown command: ${command}`);
28
28
  }
29
29
  }
30
30
 
31
+ function resetCmd(flags: Args['flags']): void {
32
+ const projectDir = resolve(str(flags.project) ?? process.cwd());
33
+ const { removed, kept } = resetMission(projectDir, flag(flags.keepLogs));
34
+ if (removed.length) console.log(`removed: ${removed.join(', ')}`);
35
+ else console.log('nothing to remove.');
36
+ if (kept.length) console.log(`kept: ${kept.join(', ')}`);
37
+ }
38
+
31
39
  async function resolveOptions(flags: Args['flags']): Promise<{ scope: Scope; projectDir: string; targetIds: string[] }> {
32
40
  const interactive = !flag(flags.yes);
33
41
  const rl = interactive ? createRl() : null;
@@ -126,19 +134,6 @@ function list(flags: Args['flags']): void {
126
134
  if (!found) console.log('No mugiwara installation found.');
127
135
  }
128
136
 
129
- function skills(): void {
130
- const dir = join(CONTENT_DIR, 'skills');
131
- const names = readdirSync(dir, { withFileTypes: true }).filter(e => e.isDirectory()).map(e => e.name).sort();
132
- const rows = names.map(name => {
133
- const { data } = parseFrontmatter(readFileSync(join(dir, name, 'SKILL.md'), 'utf8'));
134
- return [name, data.description ?? ''] as const;
135
- });
136
- const w = Math.max(...rows.map(r => r[0].length)) + 2;
137
- console.log(`mugiwara ${VERSION} — ${rows.length} skills (agentskills.io format):\n`);
138
- for (const [name, description] of rows) console.log(` ${name.padEnd(w)}${description}`);
139
- console.log(`\nInstall skills into any agent via skills.sh:\n npx skills add ionivetech/mugiwara`);
140
- }
141
-
142
137
  function help(): void {
143
138
  console.log(`mugiwara ${VERSION} — the Straw Hat crew for AI agents
144
139
 
@@ -147,7 +142,7 @@ Usage:
147
142
  mugiwara update replace existing files (backs up differences first)
148
143
  mugiwara uninstall remove installed files via manifest
149
144
  mugiwara list show installations
150
- mugiwara skills list installable skills (agentskills.io)
145
+ mugiwara reset wipe mission state (spec/plans/results/review/issues[/logs])
151
146
  mugiwara --help this help
152
147
  mugiwara --version print version
153
148
 
@@ -157,7 +152,8 @@ Flags:
157
152
  --target <ids|all> comma-separated: ${TARGET_IDS.join(', ')}
158
153
  --yes, -y non-interactive (needs --global/--project, --target)
159
154
  --force overwrite differing files (with backup)
160
- --dry-run print actions without writing`);
155
+ --dry-run print actions without writing
156
+ --keep-logs with reset: keep .mugiwara/logs (lessons ledger survives)`);
161
157
  }
162
158
 
163
159
  let entry = process.argv[1] !== undefined ? resolve(process.argv[1]) : undefined;
package/src/installer.ts CHANGED
@@ -5,7 +5,12 @@ import { homedir } from 'node:os';
5
5
  import { parseFrontmatter, type FrontmatterData } from './frontmatter.ts';
6
6
  import type { Scope } from './manifest.ts';
7
7
 
8
- export type ContentItem = { name: string; data: FrontmatterData; body: string };
8
+ export type ContentItem = {
9
+ name: string;
10
+ data: FrontmatterData;
11
+ body: string;
12
+ refs: { relPath: string; text: string }[];
13
+ };
9
14
 
10
15
  export type InstallOptions = {
11
16
  scope: Scope;
@@ -28,9 +33,13 @@ export interface Target {
28
33
  id: string;
29
34
  label: string;
30
35
  native: boolean;
36
+ tier?: 1 | 2 | 3;
31
37
  paths(opts: { scope: Scope; projectDir: string; home: string }): { skillsDir: string; agentsDir: string };
32
38
  transformSkill(data: FrontmatterData, body: string): TransformOut;
33
39
  transformAgent(data: FrontmatterData, body: string): TransformOut;
40
+ refsDir?(opts: { scope: Scope; projectDir: string; home: string }, skillName: string): string;
41
+ transformSkillFull?(data: FrontmatterData, body: string): TransformOut | null;
42
+ transformAgentFull?(data: FrontmatterData, body: string): TransformOut | null;
34
43
  postInstall?(opts: { scope: Scope; projectDir: string; home: string; dryRun: boolean; files: string[] }): { written: string[]; notes: string[] };
35
44
  }
36
45
 
@@ -43,17 +52,24 @@ export function collectContent(): { skills: ContentItem[]; agents: ContentItem[]
43
52
  .filter(e => e.isDirectory()).map(e => e.name);
44
53
  const skills = skillNames.map(name => {
45
54
  const { data, body } = parseFrontmatter(readFileSync(join(CONTENT_DIR, 'skills', name, 'SKILL.md'), 'utf8'));
46
- return { name, data, body };
55
+ return { name, data, body, refs: collectRefs(join(CONTENT_DIR, 'skills', name)) };
47
56
  });
48
57
  const agents = readdirSync(join(CONTENT_DIR, 'agents'))
49
58
  .filter(f => f.endsWith('.md'))
50
59
  .map(f => {
51
60
  const { data, body } = parseFrontmatter(readFileSync(join(CONTENT_DIR, 'agents', f), 'utf8'));
52
- return { name: f.replace(/\.md$/, ''), data, body };
61
+ return { name: f.replace(/\.md$/, ''), data, body, refs: [] as { relPath: string; text: string }[] };
53
62
  });
54
63
  return { skills, agents };
55
64
  }
56
65
 
66
+ function collectRefs(skillDir: string): { relPath: string; text: string }[] {
67
+ const refsDir = join(skillDir, 'references');
68
+ if (!existsSync(refsDir)) return [];
69
+ return readdirSync(refsDir, { recursive: true }).map(f => String(f)).filter(f => f.endsWith('.md'))
70
+ .map(rel => ({ relPath: rel, text: readFileSync(join(refsDir, rel), 'utf8') }));
71
+ }
72
+
57
73
  export function installTo(target: Target, opts: InstallOptions): InstallResult {
58
74
  const { scope, projectDir, dryRun = false, force = false } = opts;
59
75
  const home = opts.home ?? homedir();
@@ -82,10 +98,22 @@ export function installTo(target: Target, opts: InstallOptions): InstallResult {
82
98
  for (const s of skills) {
83
99
  const out = target.transformSkill(s.data, s.body);
84
100
  if (out) writeOne(join(dirs.skillsDir, out.relPath), out.text);
101
+ if (target.transformSkillFull) {
102
+ const full = target.transformSkillFull(s.data, s.body);
103
+ if (full && target.refsDir) writeOne(join(target.refsDir({ scope, projectDir, home }, s.name), full.relPath), full.text);
104
+ }
105
+ if (s.refs.length && target.refsDir) {
106
+ const refsRoot = target.refsDir({ scope, projectDir, home }, s.name);
107
+ for (const r of s.refs) writeOne(join(refsRoot, r.relPath), r.text);
108
+ }
85
109
  }
86
110
  for (const a of agents) {
87
111
  const out = target.transformAgent(a.data, a.body);
88
112
  if (out) writeOne(join(dirs.agentsDir, out.relPath), out.text);
113
+ if (target.transformAgentFull) {
114
+ const full = target.transformAgentFull(a.data, a.body);
115
+ if (full && target.refsDir) writeOne(join(target.refsDir({ scope, projectDir, home }, a.name), full.relPath), full.text);
116
+ }
89
117
  }
90
118
 
91
119
  if (target.postInstall) {
package/src/mission.ts ADDED
@@ -0,0 +1,25 @@
1
+ // src/mission.ts
2
+ // Mission-state helpers for the mugiwara CLI (installer + reset only).
3
+ import { existsSync, rmSync } from 'node:fs';
4
+ import { join } from 'node:path';
5
+
6
+ export function resetMission(projectDir: string, keepLogs: boolean): { removed: string[]; kept: string[] } {
7
+ const root = join(projectDir, '.mugiwara');
8
+ if (!existsSync(root)) return { removed: [], kept: [] };
9
+ const removed: string[] = [];
10
+ const kept: string[] = [];
11
+ for (const dir of ['spec', 'plans', 'results', 'review', 'issues']) {
12
+ const p = join(root, dir);
13
+ if (existsSync(p)) { rmSync(p, { recursive: true, force: true }); removed.push(dir); }
14
+ }
15
+ if (!keepLogs) {
16
+ const p = join(root, 'logs');
17
+ if (existsSync(p)) { rmSync(p, { recursive: true, force: true }); removed.push('logs'); }
18
+ } else if (existsSync(join(root, 'logs'))) {
19
+ kept.push('logs');
20
+ }
21
+ for (const f of ['config', 'manifest.json', 'backup']) {
22
+ if (existsSync(join(root, f))) kept.push(f);
23
+ }
24
+ return { removed, kept };
25
+ }
@@ -1,8 +1,13 @@
1
1
  // src/targets/claude.ts
2
- import { join } from 'node:path';
2
+ import { existsSync, readFileSync, mkdirSync, copyFileSync } from 'node:fs';
3
+ import { dirname, join } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
3
5
  import { stringifyFrontmatter, type FrontmatterData } from '../frontmatter.ts';
4
6
  import type { Target } from '../installer.ts';
5
7
 
8
+ const here = dirname(fileURLToPath(import.meta.url));
9
+ const HOOK_SRC = join(here, '..', '..', 'hooks', 'session-start.ts');
10
+
6
11
  export const target: Target = {
7
12
  id: 'claude',
8
13
  label: 'Claude Code',
@@ -22,4 +27,20 @@ export const target: Target = {
22
27
  if (data.tools) fm.tools = data.tools;
23
28
  return { relPath: `${data.name}.md`, text: stringifyFrontmatter(fm, body) };
24
29
  },
30
+ refsDir({ scope, projectDir, home }, skillName: string) {
31
+ const root = scope === 'global' ? join(home, '.claude') : join(projectDir, '.claude');
32
+ return join(root, 'skills', skillName, 'references');
33
+ },
34
+ postInstall({ scope, projectDir, home, dryRun }) {
35
+ // Wire the SessionStart hook (inline doctrine) into the installed .claude dir.
36
+ const root = scope === 'global' ? join(home, '.claude') : join(projectDir, '.claude');
37
+ const hookFile = join(root, 'hooks', 'session-start.ts');
38
+ if (dryRun) return { written: [], notes: [] };
39
+ if (existsSync(HOOK_SRC) && !existsSync(hookFile)) {
40
+ mkdirSync(dirname(hookFile), { recursive: true });
41
+ copyFileSync(HOOK_SRC, hookFile);
42
+ return { written: [hookFile], notes: [] };
43
+ }
44
+ return { written: [], notes: [] };
45
+ },
25
46
  };
@@ -6,5 +6,5 @@ export const target = makeGeneric({
6
6
  label: 'Codex',
7
7
  rulesDir: '.codex/mugiwara',
8
8
  bootstrapFile: 'AGENTS.md',
9
- bootstrapPointer: 'Mugiwara crew installed in .codex/mugiwara/ — read .codex/mugiwara/mugiwara-workflow.md to run the pipeline.',
9
+ bootstrapPointer: 'Mugiwara crew installed in .codex/mugiwara/ — read .codex/mugiwara/mugiwara-workflow.md to run the pipeline inline in the main conversation.',
10
10
  });
@@ -1,6 +1,7 @@
1
1
  // src/targets/copilot.ts
2
2
  import { join } from 'node:path';
3
3
  import { stringifyFrontmatter, type FrontmatterData } from '../frontmatter.ts';
4
+ import type { Scope } from '../manifest.ts';
4
5
  import type { Target } from '../installer.ts';
5
6
 
6
7
  export const target: Target = {
@@ -23,4 +24,7 @@ export const target: Target = {
23
24
  text: stringifyFrontmatter({ name: data.name, description: data.description }, body),
24
25
  };
25
26
  },
27
+ refsDir(_opts: { scope: Scope; projectDir: string; home: string }, skillName: string) {
28
+ return join(_opts.projectDir, '.mugiwara', 'refs', skillName);
29
+ },
26
30
  };
@@ -6,5 +6,5 @@ export const target = makeGeneric({
6
6
  label: 'Gemini',
7
7
  rulesDir: '.gemini/mugiwara',
8
8
  bootstrapFile: 'GEMINI.md',
9
- bootstrapPointer: 'Mugiwara crew installed in .gemini/mugiwara/ — read .gemini/mugiwara/mugiwara-workflow.md to run the pipeline.',
9
+ bootstrapPointer: 'Mugiwara crew installed in .gemini/mugiwara/ — read .gemini/mugiwara/mugiwara-workflow.md to run the pipeline inline in the main conversation.',
10
10
  });
@@ -1,6 +1,7 @@
1
1
  // src/targets/generic.ts
2
2
  import { existsSync, writeFileSync } from 'node:fs';
3
3
  import { join } from 'node:path';
4
+ import type { Scope } from '../manifest.ts';
4
5
  import type { Target } from '../installer.ts';
5
6
 
6
7
  export function makeGeneric(opts: {
@@ -9,23 +10,54 @@ export function makeGeneric(opts: {
9
10
  rulesDir: string;
10
11
  bootstrapFile: string | null;
11
12
  bootstrapPointer: string | null;
13
+ tier?: 2 | 3;
12
14
  }): Target {
13
- const { id, label, rulesDir, bootstrapFile, bootstrapPointer } = opts;
15
+ const { id, label, rulesDir, bootstrapFile, bootstrapPointer, tier = bootstrapPointer ? 2 : 3 } = opts;
16
+ const stubOnly = tier === 3;
14
17
  return {
15
18
  id,
16
19
  label,
17
20
  native: false,
21
+ tier,
18
22
  paths({ scope, projectDir }: { scope: 'global' | 'project'; projectDir: string; home: string }) {
19
23
  if (scope === 'global') throw new Error(`${label} supports project scope only`);
20
24
  const dir = join(projectDir, rulesDir);
21
25
  return { skillsDir: dir, agentsDir: dir };
22
26
  },
23
27
  transformSkill(data, body) {
28
+ if (stubOnly) {
29
+ // Stub: routing + pointer only. Full body lands in .mugiwara/refs/
30
+ // (outside the rules glob) so glob-loading harnesses stop eating ~40k
31
+ // tokens at session start. The agent reads the full file on demand.
32
+ return {
33
+ relPath: `${data.name}.md`,
34
+ text: `# ${data.name}\n\n> ${data.description}\n\n## Skip when\n\nFull skill: \`${data.name}\` — read \`.mugiwara/refs/${data.name}.md\` when the crew invokes this role.`,
35
+ };
36
+ }
37
+ return { relPath: `${data.name}.md`, text: `# ${data.name}\n\n> ${data.description}\n\n${body}` };
38
+ },
39
+ transformSkillFull(data, body) {
40
+ if (!stubOnly) return null;
24
41
  return { relPath: `${data.name}.md`, text: `# ${data.name}\n\n> ${data.description}\n\n${body}` };
25
42
  },
26
43
  transformAgent(data, body) {
44
+ if (stubOnly) {
45
+ return {
46
+ relPath: `agent-${data.name}.md`,
47
+ text: `# Agent: ${data.name}\n\n> ${data.description}\n\nSkills: ${data.skills ?? ''}. Read \`.mugiwara/refs/${data.name}.md\` when embodying this role.`,
48
+ };
49
+ }
27
50
  return { relPath: `agent-${data.name}.md`, text: `# Agent: ${data.name}\n\n> ${data.description}\n\nSkills used: ${data.skills ?? ''}\n\n${body}` };
28
51
  },
52
+ transformAgentFull(data, body) {
53
+ if (!stubOnly) return null;
54
+ return { relPath: `${data.name}.md`, text: `# Agent: ${data.name}\n\n> ${data.description}\n\nSkills used: ${data.skills ?? ''}\n\n${body}` };
55
+ },
56
+ refsDir({ projectDir }: { scope: Scope; projectDir: string; home: string }) {
57
+ // Keep reference detail outside the rules dir so the harness never
58
+ // glob-loads it; the skill names the path and the agent reads on demand.
59
+ return join(projectDir, '.mugiwara', 'refs');
60
+ },
29
61
  postInstall({ projectDir, dryRun }) {
30
62
  if (!bootstrapFile) return { written: [], notes: [] };
31
63
  const notes: string[] = [];
@@ -7,4 +7,5 @@ export const target = makeGeneric({
7
7
  rulesDir: '.kilo/rules',
8
8
  bootstrapFile: 'kilo.jsonc',
9
9
  bootstrapPointer: '{\n "instructions": [\n ".kilo/rules/*.md"\n ]\n}',
10
+ tier: 3,
10
11
  });
@@ -22,4 +22,8 @@ export const target: Target = {
22
22
  if (data.tools) fm.tools = data.tools;
23
23
  return { relPath: `${data.name}.md`, text: stringifyFrontmatter(fm, body) };
24
24
  },
25
+ refsDir({ scope, projectDir, home }, skillName: string) {
26
+ const root = scope === 'global' ? join(home, '.config', 'opencode') : join(projectDir, '.opencode');
27
+ return join(root, 'skills', skillName, 'references');
28
+ },
25
29
  };