@johanesimm/fundamentum 0.1.0 → 0.2.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/README.md CHANGED
@@ -13,11 +13,12 @@
13
13
  npx @johanesimm/fundamentum
14
14
  ```
15
15
 
16
- That's it — the installer asks whether to install **globally** (every
17
- project) or into the **current project**, copies the skills into the
18
- right `.claude/skills/` folder, and you're done. Restart Claude Code,
19
- then say *"Initialize this project documentation-first"* to kick off
20
- `blueprint`. Full walkthrough in [Installation](#installation) below.
16
+ The installer asks **which coding agent you use** (Claude Code, Codex,
17
+ Cursor, Gemini CLI, Copilot, …), then installs the skills the way that
18
+ agent expects and preconfigures them for it — no repeated "which tool?"
19
+ questions later. Restart your agent, then say *"Initialize this project
20
+ documentation-first"* to kick off `blueprint`. Full walkthrough in
21
+ [Installation](#installation) below.
21
22
 
22
23
  ------------------------------------------------------------------------
23
24
 
@@ -110,7 +111,7 @@ jump straight to one phase, provided its inputs already exist.
110
111
  ### The `npx` installer (recommended)
111
112
 
112
113
  **Prerequisites:** [Node.js](https://nodejs.org) 18+ (gives you `npx`)
113
- and [Claude Code](https://claude.com/claude-code).
114
+ and a supported coding agent.
114
115
 
115
116
  **Step 1 — run the installer.** No global install needed; `npx` fetches
116
117
  and runs it on the fly:
@@ -119,33 +120,49 @@ and runs it on the fly:
119
120
  npx @johanesimm/fundamentum
120
121
  ```
121
122
 
122
- **Step 2 — choose where the skills go.** You'll be asked:
123
+ **Step 2 — pick your agent (asked first).** Your choice decides where
124
+ the skills go *and* preconfigures `primer` to write the right
125
+ agent-context file — so nothing re-asks "which tool?" later:
123
126
 
124
127
  ```
125
128
  Fundamentum — installing: blueprint, excavate, primer
126
129
 
127
- Where should these skills go?
128
- 1) Global ~/.claude/skills (available in every project)
129
- 2) Project ./.claude/skills (this repo only)
130
- Choose [1/2]:
130
+ Which coding agent do you use?
131
+ 1) Claude Code (native skills)
132
+ 2) Codex (wires into AGENTS.md)
133
+ 3) Cursor (wires into AGENTS.md)
134
+ 4) Gemini CLI (wires into GEMINI.md)
135
+ 5) GitHub Copilot (wires into .github/copilot-instructions.md)
136
+ 6) Other / generic (wires into AGENTS.md)
137
+ Choose [1-6]:
131
138
  ```
132
139
 
133
- - **Global** the skills work in *every* project on your machine.
134
- - **Project** → installed only into the repo you're currently in
135
- (`./.claude/skills/`); commit them to share with your team.
140
+ How each agent is installed:
136
141
 
137
- **Step 3 done.** It copies the folders and confirms:
142
+ | Agent | Skills land in | How it's used |
143
+ |---|---|---|
144
+ | **Claude Code** | `.claude/skills/` (asks **global** vs **project**) | invoked natively as skills |
145
+ | **Codex / Cursor / Gemini / Copilot / Other** | `.fundamentum/skills/` (this project) | a pointer block is added to that agent's instructions file (`AGENTS.md` / `GEMINI.md` / `copilot-instructions.md`) telling it to follow the workflows |
146
+
147
+ For Claude Code you then choose **global** (every project) or
148
+ **project** (this repo only). Non-Claude agents are always wired into
149
+ the current project.
150
+
151
+ **Step 3 — done.** It copies the folders, wires things up, and confirms
152
+ — e.g. for Codex:
138
153
 
139
154
  ```
140
- ✓ blueprint → ~/.claude/skills/blueprint
141
- ✓ excavate → ~/.claude/skills/excavate
142
- ✓ primer → ~/.claude/skills/primer
155
+ ✓ blueprint → ./.fundamentum/skills/blueprint
156
+ ✓ excavate → ./.fundamentum/skills/excavate
157
+ ✓ primer → ./.fundamentum/skills/primer
143
158
 
144
- Done. Installed 3 skill(s) into the global directory.
145
- Restart Claude Code (or start a new session) to pick them up.
159
+ Done. Installed 3 skill(s) for Codex.
160
+ Wired into: ./AGENTS.md
161
+ primer is preconfigured to write AGENTS.md — it won't ask which agent.
146
162
  ```
147
163
 
148
- **Step 4 — use them.** Restart Claude Code, then just ask:
164
+ **Step 4 — use them.** Restart your agent (or start a new session),
165
+ then just ask:
149
166
 
150
167
  | Say this | Runs |
151
168
  |---|---|
@@ -156,12 +173,14 @@ Restart Claude Code (or start a new session) to pick them up.
156
173
  ### More installer options
157
174
 
158
175
  ```bash
159
- npx @johanesimm/fundamentum add primer # install just one skill (space-separate for several)
160
- npx @johanesimm/fundamentum --global # skip the prompt: install for every project
161
- npx @johanesimm/fundamentum --project # skip the prompt: install into ./.claude/skills
162
- npx @johanesimm/fundamentum --force # overwrite existing skills without asking
163
- npx @johanesimm/fundamentum list # show which skills are bundled
164
- npx @johanesimm/fundamentum --help # full usage
176
+ npx @johanesimm/fundamentum --agent claude # skip the agent prompt
177
+ npx @johanesimm/fundamentum --agent codex # wire into ./AGENTS.md
178
+ npx @johanesimm/fundamentum add primer # install just one skill (space-separate for several)
179
+ npx @johanesimm/fundamentum --agent claude --global # Claude: install for every project
180
+ npx @johanesimm/fundamentum --agent claude --project # Claude: install into ./.claude/skills
181
+ npx @johanesimm/fundamentum --force # overwrite existing skills without asking
182
+ npx @johanesimm/fundamentum list # show which skills are bundled
183
+ npx @johanesimm/fundamentum --help # full usage
165
184
  ```
166
185
 
167
186
  ### Run without npm (from source)
package/bin/cli.mjs CHANGED
@@ -1,9 +1,20 @@
1
1
  #!/usr/bin/env node
2
- // Fundamentum skill installer — copies the bundled skills into a Claude Code
3
- // skills directory (global ~/.claude/skills or project ./.claude/skills).
4
- // Zero dependencies: uses only the Node standard library.
2
+ // Fundamentum skill installer.
3
+ //
4
+ // Asks which coding agent you use, then installs the skills the way that
5
+ // agent expects:
6
+ // - Claude Code -> copies invokable skills into ~/.claude/skills or
7
+ // ./.claude/skills (native SKILL.md skills).
8
+ // - Other agents -> copies the skill folders into ./.fundamentum/skills and
9
+ // wires a pointer block into that agent's instructions
10
+ // file (AGENTS.md / GEMINI.md / copilot-instructions.md),
11
+ // since those tools have no skill runtime.
12
+ // The chosen agent is baked into the installed `primer` skill so it never
13
+ // re-asks which agent-context file to generate.
14
+ //
15
+ // Zero dependencies: Node standard library only.
5
16
 
6
- import { readdir, cp, mkdir } from 'node:fs/promises';
17
+ import { readdir, cp, mkdir, readFile, writeFile } from 'node:fs/promises';
7
18
  import { existsSync } from 'node:fs';
8
19
  import { fileURLToPath } from 'node:url';
9
20
  import { dirname, join, resolve } from 'node:path';
@@ -14,27 +25,41 @@ import process from 'node:process';
14
25
  const HERE = dirname(fileURLToPath(import.meta.url));
15
26
  const ROOT = resolve(HERE, '..'); // package root — where the skill folders live
16
27
 
17
- const HELP = `fundamentum install documentation-first Claude Code skills
28
+ // agent key -> how to install + which context file `primer` should write.
29
+ const AGENTS = {
30
+ claude: { label: 'Claude Code', file: 'CLAUDE.md', native: true },
31
+ codex: { label: 'Codex', file: 'AGENTS.md', native: false },
32
+ cursor: { label: 'Cursor', file: 'AGENTS.md', native: false },
33
+ gemini: { label: 'Gemini CLI', file: 'GEMINI.md', native: false },
34
+ copilot: { label: 'GitHub Copilot', file: '.github/copilot-instructions.md', native: false },
35
+ other: { label: 'Other / generic', file: 'AGENTS.md', native: false },
36
+ };
37
+ const AGENT_ORDER = ['claude', 'codex', 'cursor', 'gemini', 'copilot', 'other'];
38
+ const NONCLAUDE_SKILLS_DIR = '.fundamentum/skills'; // relative to the project root
39
+
40
+ const HELP = `fundamentum — install documentation-first coding-agent skills
18
41
 
19
42
  Usage:
20
- npx fundamentum [add] [skills...] [options]
21
- npx fundamentum list
43
+ npx @johanesimm/fundamentum [add] [skills...] [options]
44
+ npx @johanesimm/fundamentum list
22
45
 
23
46
  Arguments:
24
- skills... Names of skills to install (default: all bundled skills)
47
+ skills... Skills to install (default: all bundled skills)
25
48
 
26
49
  Options:
27
- -g, --global Install into ~/.claude/skills (every project)
28
- -p, --project Install into ./.claude/skills (this repo only)
29
- -f, --force Overwrite an existing skill without asking
30
- -h, --help Show this help
50
+ --agent <name> claude | codex | cursor | gemini | copilot | other
51
+ -g, --global Claude Code only: install into ~/.claude/skills
52
+ -p, --project Install into the current project
53
+ -f, --force Overwrite existing skills without asking
54
+ -h, --help Show this help
31
55
 
32
- With no --global/--project flag, you'll be asked which one.
56
+ With no --agent, you'll be asked which agent you use (asked first).
57
+ Non-Claude agents are always installed into the current project.
33
58
 
34
59
  Examples:
35
- npx fundamentum # install all skills, ask scope
36
- npx fundamentum add primer -g # install just 'primer' globally
37
- npx fundamentum --project # install all into this repo
60
+ npx @johanesimm/fundamentum # ask everything
61
+ npx @johanesimm/fundamentum --agent claude -g # Claude, global, no prompts
62
+ npx @johanesimm/fundamentum --agent codex # wire into ./AGENTS.md
38
63
  `;
39
64
 
40
65
  /** Discover bundled skills = top-level dirs that contain a SKILL.md. */
@@ -51,35 +76,102 @@ async function discoverSkills() {
51
76
 
52
77
  function parseArgs(argv) {
53
78
  const args = [...argv];
54
- // optional leading verb
55
79
  if (['add', 'install', 'i'].includes(args[0])) args.shift();
56
80
  const flags = new Set();
57
81
  const names = [];
58
- for (const a of args) {
82
+ let agent = null;
83
+ for (let i = 0; i < args.length; i++) {
84
+ const a = args[i];
59
85
  if (a === '-h' || a === '--help') flags.add('help');
60
86
  else if (a === '-g' || a === '--global') flags.add('global');
61
87
  else if (a === '-p' || a === '--project') flags.add('project');
62
88
  else if (a === '-f' || a === '--force') flags.add('force');
89
+ else if (a === '--agent') agent = (args[++i] || '').toLowerCase();
90
+ else if (a.startsWith('--agent=')) agent = a.slice('--agent='.length).toLowerCase();
63
91
  else if (a.startsWith('-')) throw new Error(`Unknown option: ${a}`);
64
92
  else names.push(a);
65
93
  }
66
- return { flags, names, verb: argv[0] };
94
+ return { flags, names, agent, verb: argv[0] };
67
95
  }
68
96
 
69
- async function main() {
70
- const { flags, names, verb } = parseArgs(process.argv.slice(2));
97
+ /** The note the installer injects into the installed primer/SKILL.md. */
98
+ function preconfiguredNote(agentKey) {
99
+ const { label, file } = AGENTS[agentKey];
100
+ return (
101
+ `> **Installer-preconfigured target: \`${file}\` (${label}).** The agent ` +
102
+ `was chosen at install time — do NOT ask "which tool?"; write ` +
103
+ `\`${file}\` directly. Only generate additional tools' files if the ` +
104
+ `user explicitly asks.`
105
+ );
106
+ }
107
+
108
+ /** Rewrite the placeholder in an installed primer so it skips its interview. */
109
+ async function preconfigurePrimer(primerDir, agentKey) {
110
+ const file = join(primerDir, 'SKILL.md');
111
+ if (!existsSync(file)) return;
112
+ const src = await readFile(file, 'utf8');
113
+ const marker = '<!-- FUNDAMENTUM_PRECONFIGURED_TARGET -->';
114
+ if (!src.includes(marker)) return;
115
+ await writeFile(file, src.replace(marker, preconfiguredNote(agentKey)), 'utf8');
116
+ }
117
+
118
+ /** Build the pointer block wired into a non-Claude agent's instructions file. */
119
+ function pointerBlock(skills, agentKey) {
120
+ const { file } = AGENTS[agentKey];
121
+ const lines = [
122
+ '<!-- fundamentum:start -->',
123
+ '## Fundamentum documentation workflows',
124
+ '',
125
+ `Installed at \`${NONCLAUDE_SKILLS_DIR}/\` (paths below are relative to the`,
126
+ 'project root). When asked to scaffold or write project documentation,',
127
+ 'follow the matching workflow file to completion:',
128
+ '',
129
+ ];
130
+ const desc = {
131
+ blueprint: 'New project, documentation-first',
132
+ excavate: 'Existing codebase — document what is built + plan the gap',
133
+ primer: `Generate this project's agent-context file (writes \`${file}\`)`,
134
+ };
135
+ for (const s of skills) {
136
+ lines.push(`- ${desc[s] || s} → \`${NONCLAUDE_SKILLS_DIR}/${s}/SKILL.md\``);
137
+ }
138
+ lines.push('<!-- fundamentum:end -->');
139
+ return lines.join('\n');
140
+ }
71
141
 
72
- if (flags.has('help')) {
73
- process.stdout.write(HELP);
74
- return;
142
+ /** Create/replace the fundamentum pointer block in an instructions file. */
143
+ async function wirePointer(projectRoot, agentKey, skills) {
144
+ const rel = AGENTS[agentKey].file;
145
+ const dest = join(projectRoot, rel);
146
+ await mkdir(dirname(dest), { recursive: true });
147
+ const block = pointerBlock(skills, agentKey);
148
+ let body = existsSync(dest) ? await readFile(dest, 'utf8') : '';
149
+ const start = '<!-- fundamentum:start -->';
150
+ const end = '<!-- fundamentum:end -->';
151
+ const si = body.indexOf(start);
152
+ const ei = body.indexOf(end);
153
+ if (si !== -1 && ei !== -1) {
154
+ body = body.slice(0, si) + block + body.slice(ei + end.length); // replace in place
155
+ } else {
156
+ if (body && !body.endsWith('\n')) body += '\n';
157
+ body += (body ? '\n' : '') + block + '\n';
75
158
  }
159
+ await writeFile(dest, body, 'utf8');
160
+ return dest;
161
+ }
162
+
163
+ async function main() {
164
+ const { flags, names, agent: agentFlag, verb } = parseArgs(process.argv.slice(2));
165
+
166
+ if (flags.has('help')) return void process.stdout.write(HELP);
76
167
 
77
168
  const available = await discoverSkills();
78
169
  if (available.length === 0) throw new Error(`No skills found in ${ROOT}`);
79
170
 
80
171
  if (verb === 'list') {
81
- process.stdout.write(`Bundled skills:\n${available.map((s) => ` - ${s}`).join('\n')}\n`);
82
- return;
172
+ return void process.stdout.write(
173
+ `Bundled skills:\n${available.map((s) => ` - ${s}`).join('\n')}\n`,
174
+ );
83
175
  }
84
176
 
85
177
  // Which skills?
@@ -87,49 +179,66 @@ async function main() {
87
179
  if (names.length) {
88
180
  const unknown = names.filter((n) => !available.includes(n));
89
181
  if (unknown.length) {
90
- throw new Error(
91
- `Unknown skill(s): ${unknown.join(', ')}\nAvailable: ${available.join(', ')}`,
92
- );
182
+ throw new Error(`Unknown skill(s): ${unknown.join(', ')}\nAvailable: ${available.join(', ')}`);
93
183
  }
94
184
  selected = names;
95
185
  }
96
186
 
97
- // Interactive prompts share one readline instance.
98
187
  const interactive = process.stdin.isTTY && process.stdout.isTTY;
99
188
  const rl = interactive ? createInterface({ input: process.stdin, output: process.stdout }) : null;
100
189
 
101
190
  try {
102
- // Scope: global vs project.
191
+ // 1) Agent asked FIRST.
192
+ let agent = agentFlag;
193
+ if (agent && !AGENTS[agent]) {
194
+ throw new Error(`Unknown agent: ${agent}\nChoose one of: ${AGENT_ORDER.join(', ')}`);
195
+ }
196
+ if (!agent) {
197
+ if (!rl) throw new Error('Non-interactive shell: pass --agent <name>.');
198
+ process.stdout.write(`\nFundamentum — installing: ${selected.join(', ')}\n\nWhich coding agent do you use?\n`);
199
+ AGENT_ORDER.forEach((k, i) => {
200
+ const tag = AGENTS[k].native ? 'native skills' : `wires into ${AGENTS[k].file}`;
201
+ process.stdout.write(` ${i + 1}) ${AGENTS[k].label.padEnd(15)} (${tag})\n`);
202
+ });
203
+ const ans = (await rl.question('Choose [1-6]: ')).trim().toLowerCase();
204
+ agent = AGENT_ORDER[Number(ans) - 1] || AGENT_ORDER.find((k) => k.startsWith(ans) || AGENTS[k].label.toLowerCase().startsWith(ans));
205
+ if (!agent) throw new Error('No agent chosen. Aborting.');
206
+ }
207
+ const meta = AGENTS[agent];
208
+
209
+ // 2) Location.
103
210
  let scope;
104
- if (flags.has('global')) scope = 'global';
105
- else if (flags.has('project')) scope = 'project';
106
- else if (rl) {
107
- process.stdout.write(
108
- `\nFundamentum installing: ${selected.join(', ')}\n\n` +
109
- `Where should these skills go?\n` +
110
- ` 1) Global ${join(homedir(), '.claude', 'skills')} (available in every project)\n` +
111
- ` 2) Project ${join(process.cwd(), '.claude', 'skills')} (this repo only)\n`,
112
- );
113
- const ans = (await rl.question('Choose [1/2]: ')).trim();
114
- scope = ans === '2' || ans.toLowerCase().startsWith('p') ? 'project' : ans === '1' || ans.toLowerCase().startsWith('g') ? 'global' : null;
115
- if (!scope) throw new Error('No scope chosen. Aborting.');
211
+ let targetBase;
212
+ if (meta.native) {
213
+ if (flags.has('global')) scope = 'global';
214
+ else if (flags.has('project')) scope = 'project';
215
+ else if (rl) {
216
+ process.stdout.write(
217
+ `\n${meta.label} skills where to?\n` +
218
+ ` 1) Global ${join(homedir(), '.claude', 'skills')} (every project)\n` +
219
+ ` 2) Project ${join(process.cwd(), '.claude', 'skills')} (this repo only)\n`,
220
+ );
221
+ const ans = (await rl.question('Choose [1/2]: ')).trim().toLowerCase();
222
+ scope = ans === '2' || ans.startsWith('p') ? 'project' : ans === '1' || ans.startsWith('g') ? 'global' : null;
223
+ if (!scope) throw new Error('No location chosen. Aborting.');
224
+ } else {
225
+ throw new Error('Non-interactive shell: pass --global or --project.');
226
+ }
227
+ targetBase = scope === 'global' ? join(homedir(), '.claude', 'skills') : join(process.cwd(), '.claude', 'skills');
116
228
  } else {
117
- throw new Error('Non-interactive shell: pass --global or --project.');
229
+ // Non-Claude agents wire into a project instructions file — always project.
230
+ scope = 'project';
231
+ targetBase = join(process.cwd(), ...NONCLAUDE_SKILLS_DIR.split('/'));
118
232
  }
119
233
 
120
- const targetBase =
121
- scope === 'global'
122
- ? join(homedir(), '.claude', 'skills')
123
- : join(process.cwd(), '.claude', 'skills');
124
-
125
234
  await mkdir(targetBase, { recursive: true });
126
235
 
236
+ // 3) Copy skills.
127
237
  const force = flags.has('force');
128
238
  const installed = [];
129
239
  for (const name of selected) {
130
240
  const src = join(ROOT, name);
131
241
  const dest = join(targetBase, name);
132
-
133
242
  if (existsSync(dest) && !force) {
134
243
  if (rl) {
135
244
  const ans = (await rl.question(` ${dest} exists. Overwrite? [y/N]: `)).trim().toLowerCase();
@@ -142,19 +251,30 @@ async function main() {
142
251
  continue;
143
252
  }
144
253
  }
145
-
146
254
  await cp(src, dest, { recursive: true, force: true });
255
+ if (name === 'primer') await preconfigurePrimer(dest, agent);
147
256
  installed.push(name);
148
257
  process.stdout.write(` ✓ ${name} → ${dest}\n`);
149
258
  }
150
259
 
151
- if (installed.length) {
260
+ if (!installed.length) return void process.stdout.write('\nNothing installed.\n');
261
+
262
+ // 4) Wire the pointer for non-Claude agents.
263
+ let wired = null;
264
+ if (!meta.native) wired = await wirePointer(process.cwd(), agent, installed);
265
+
266
+ // 5) Summary.
267
+ process.stdout.write(`\nDone. Installed ${installed.length} skill(s) for ${meta.label}.\n`);
268
+ if (meta.native) {
269
+ process.stdout.write(`Location: ${targetBase} (${scope}).\nRestart Claude Code (or start a new session) to pick them up.\n`);
270
+ } else {
152
271
  process.stdout.write(
153
- `\nDone. Installed ${installed.length} skill(s) into the ${scope} directory.\n` +
154
- `Restart Claude Code (or start a new session) to pick them up.\n`,
272
+ `Skills: ${targetBase}\nWired into: ${wired}\n` +
273
+ `${meta.label} reads that file, so it will follow the workflows from there.\n`,
155
274
  );
156
- } else {
157
- process.stdout.write(`\nNothing installed.\n`);
275
+ }
276
+ if (installed.includes('primer')) {
277
+ process.stdout.write(`\nprimer is preconfigured to write ${meta.file} — it won't ask which agent.\n`);
158
278
  }
159
279
  } finally {
160
280
  rl?.close();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@johanesimm/fundamentum",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Documentation-first Claude Code skill suite (blueprint / excavate / primer) with a one-command installer.",
5
5
  "type": "module",
6
6
  "author": "Johanes <johanesimm95@gmail.com>",
package/primer/SKILL.md CHANGED
@@ -25,6 +25,15 @@ generated file, do not guess.
25
25
 
26
26
  ## Phase 0 — Pick the target file(s)
27
27
 
28
+ <!-- FUNDAMENTUM_PRECONFIGURED_TARGET -->
29
+ <!-- The installer rewrites the line above when you choose your agent at
30
+ install time. If it still shows as a bare comment, this copy was
31
+ installed manually — run the interview below as normal. -->
32
+
33
+ If the line above was replaced with a "preconfigured target" note, obey
34
+ it and **skip the question** — the agent was already chosen at install
35
+ time; just write that file. Otherwise:
36
+
28
37
  Ask (AskUserQuestion) before anything else: **"Do you use an agentic
29
38
  coding assistant, and which one(s)?"** Map the answer to filename(s):
30
39