@firatcand/forge 0.1.0 → 0.2.1

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
@@ -1,3 +1,4 @@
1
+ ![banner](forge-cover-v1--horizon-banner-3x1@2x.png)
1
2
  # 🔨 Forge
2
3
 
3
4
  > A lightweight Claude Code framework that takes you from idea to production with structure, not friction.
@@ -8,7 +9,7 @@ Forge is for solo founders and small teams who want to ship real products with C
8
9
 
9
10
  Forge ships:
10
11
 
11
- - **21 slash commands** covering the full product lifecycle from raw idea to production
12
+ - **21 skills** covering the full product lifecycle from raw idea to production (typed as slash commands in Claude Code; invoked by description in Codex CLI — see [Cross-tool support](#cross-tool-support))
12
13
  - **12 specialist subagents** — frontend, backend, db, qa, security, devops, design, plus orchestrators
13
14
  - **13 templates** for PRD, SPEC, DESIGN, phases.yaml, GitHub workflows, and more
14
15
  - **8 best practices baked in** — Boil the Lake, Iron Law of Investigation, Compound Learning, Test-or-die, Multi-model Second Opinion, and more
@@ -44,6 +45,8 @@ PROD ← /phase-gate phase-3 ← (manual PR dev → main)
44
45
 
45
46
  ~90-120 minutes from raw idea to first task ready to implement.
46
47
 
48
+ > The `/`-prefixed names above are the Claude Code experience — typed slash commands. In other hosts (Codex CLI, Cursor, Gemini CLI), the same skills are available but invoked differently. See [Cross-tool support](#cross-tool-support) below.
49
+
47
50
  ## Install
48
51
 
49
52
  One command. No git clone, no setup script.
@@ -69,9 +72,9 @@ npx @firatcand/forge
69
72
  mkdir my-product && cd my-product
70
73
  npx @firatcand/forge init
71
74
 
72
- # Open your AI coding tool and run /forge
73
- claude # or: codex, cursor, gemini
74
- > /forge # Socratic Q&A → spec/BRIEF.md
75
+ # Open your AI coding tool see Cross-tool support below for per-host invocation
76
+ claude
77
+ > /forge # discovery interview → spec/BRIEF.md
75
78
  > /draft-prd # → spec/PRD.md
76
79
  > /draft-spec # → spec/SPEC.md
77
80
  > /decompose # → plans/phases.yaml
@@ -80,31 +83,41 @@ claude # or: codex, cursor, gemini
80
83
  > /pickup-task # claim first task, worktree created
81
84
  ```
82
85
 
86
+ In Codex CLI, the same skills are installed but Codex doesn't expose user-defined slash commands — invoke them by description instead, e.g. `Run forge's discovery interview for my project idea: ...`. Codex's model picks the skill up from `~/.codex/skills/forge/SKILL.md`.
87
+
83
88
  [Full quick start →](docs/QUICKSTART.md)
84
89
 
85
90
  ## Other commands
86
91
 
87
92
  ```bash
88
93
  npx @firatcand/forge install # Install/reinstall forge skills + agents only
94
+ npx @firatcand/forge doctor # Audit installed skills + agents per detected tool
89
95
  npx @firatcand/forge init [name] # Initialize a project in current directory
90
96
  npx @firatcand/forge companions # Install founder-skills companions only
91
97
  npx @firatcand/forge --help # Show all commands
92
98
  npx @firatcand/forge --version # Show version
93
99
  ```
94
100
 
101
+ If a slash command stops triggering in one of your AI tools (e.g. you installed Codex CLI after running forge for the first time), run `npx @firatcand/forge doctor` to see exactly what's installed where, then `npx @firatcand/forge install` to sync.
102
+
95
103
  ## Cross-tool support
96
104
 
97
- Forge works with:
98
- - ✅ Claude Code (`~/.claude/`)
99
- - Codex CLI (`~/.codex/`)
100
- - ✅ Cursor (`~/.cursor/`)
101
- - Gemini CLI (`~/.gemini/`)
105
+ The installer detects which tools you have and installs forge skills + subagents into each. **The skills land in every host, but how you invoke them differs by host.**
106
+
107
+ | Host | Install path | How to invoke a forge skill | Status |
108
+ |------------|---------------------------------------|--------------------------------------------------------------|-----------------------|
109
+ | Claude Code | `~/.claude/skills/`, `~/.claude/agents/` | Typed slash commands: `/forge`, `/draft-prd`, … | ✅ Verified |
110
+ | Codex CLI | `~/.codex/skills/`, `~/.codex/subagents/` | Natural language — Codex doesn't expose user slash commands. Ask the model: "Run forge's discovery interview for …" | ✅ Skills load; no `/forge` syntax |
111
+ | Cursor | `~/.cursor/skills/`, `~/.cursor/agents/` | Unverified — invocation mechanism not yet tested by maintainer | ⚠️ Unverified |
112
+ | Gemini CLI | `~/.gemini/extensions/` | Unverified — uses Gemini's `extensions` format, may need manual config | ⚠️ Unverified |
113
+
114
+ If you're on Codex/Cursor/Gemini and `/forge` doesn't trigger, that's expected — try the natural-language form. Run `npx @firatcand/forge doctor` to confirm the skills are physically present at the expected paths.
102
115
 
103
- The installer detects which tools you have and installs to all of them by default. You can choose specific tools during setup.
116
+ If you discover the right invocation pattern for Cursor or Gemini and it differs from natural language, please open an issue or PR the maintainer is actively looking for confirmation on those two.
104
117
 
105
118
  ## Why "forge"?
106
119
 
107
- Forge is what you do when you have raw material (an idea) and want a finished tool (a product). The process is heat, pressure, shape, repeat. The framework's namesake skill `/forge` applies Socratic pressure to your raw idea until structure emerges.
120
+ Forge is what you do when you have raw material (an idea) and want a finished tool (a product). The process is heat, pressure, shape, repeat. The framework's namesake skill `/forge` is a discovery interview that applies pressure to your raw idea until structure emerges.
108
121
 
109
122
  ## Inspiration
110
123
 
@@ -123,7 +136,7 @@ What forge adds:
123
136
 
124
137
  ## Status
125
138
 
126
- Forge is **v1.0** — used in production by the maintainer for solo founder workflows. Stable enough to depend on, raw enough that you'll find sharp edges. Issues and PRs welcome.
139
+ Forge is **v0.2.1** — used in production by the maintainer for solo founder workflows. Stable enough to depend on, raw enough that you'll find sharp edges. Issues and PRs welcome.
127
140
 
128
141
  ## License
129
142
 
package/bin/forge.js CHANGED
@@ -4,12 +4,28 @@ import { checkbox, input, confirm } from '@inquirer/prompts';
4
4
  import chalk from 'chalk';
5
5
  import fs from 'fs-extra';
6
6
  import path from 'path';
7
+ import os from 'os';
7
8
  import { execSync } from 'child_process';
8
9
 
9
- import { detectTools, installToTool } from '../lib/tools.js';
10
+ import { detectTools, installToTool, auditTool } from '../lib/tools.js';
10
11
  import { COMPANIONS, installCompanion } from '../lib/companions.js';
11
12
  import { FORGE_ROOT, getPackageVersion } from '../lib/paths.js';
12
13
 
14
+ function tildify(p) {
15
+ if (!p) return p;
16
+ const home = os.homedir();
17
+ return p.startsWith(home) ? '~' + p.slice(home.length) : p;
18
+ }
19
+
20
+ function formatInstallSummary(tool, result) {
21
+ const parts = [`${result.skillsCount} skills → ${tildify(result.skillsTarget)}`];
22
+ if (result.agentsTarget) {
23
+ const agentLabel = tool.key === 'codex' ? 'subagents' : 'agents';
24
+ parts.push(`${result.agentsCount} ${agentLabel} → ${tildify(result.agentsTarget)}`);
25
+ }
26
+ return `${tool.name} — ${parts.join(', ')}`;
27
+ }
28
+
13
29
  function showHelp() {
14
30
  console.log(`
15
31
  ${chalk.bold('🔨 forge')} — A lightweight framework for shipping software products with AI coding agents
@@ -17,13 +33,15 @@ ${chalk.bold('🔨 forge')} — A lightweight framework for shipping software pr
17
33
  ${chalk.bold('Usage:')}
18
34
  npx @firatcand/forge Run interactive setup (default)
19
35
  npx @firatcand/forge install Install forge skills + agents only
36
+ npx @firatcand/forge doctor Audit installed skills + agents per tool
20
37
  npx @firatcand/forge init [name] Initialize a forge project in current directory
21
38
  npx @firatcand/forge companions Install founder-skills companions
22
39
  npx @firatcand/forge --version Show version
23
40
  npx @firatcand/forge --help Show this help
24
41
 
25
42
  ${chalk.bold('Skills (use inside your AI coding tool):')}
26
- /forge Socratic ideation → BRIEF.md
43
+ ${chalk.dim(` Claude Code: type the / name. Codex CLI: ask in natural language ("run forge's discovery interview").`)}
44
+ /forge Discovery interview → BRIEF.md
27
45
  /draft-prd Generate PRD.md from BRIEF.md
28
46
  /draft-spec Generate SPEC.md from PRD.md
29
47
  /draft-design Generate DESIGN.md from PRD.md (optional)
@@ -84,8 +102,8 @@ async function runInteractiveSetup() {
84
102
  console.log(chalk.bold('\n📦 Installing forge skills (21) and agents (12)...\n'));
85
103
  for (const toolKey of selectedTools) {
86
104
  const tool = detected.find(t => t.key === toolKey);
87
- await installToTool(tool, FORGE_ROOT);
88
- console.log(chalk.green(' ✓') + ` Installed to ${tool.name}`);
105
+ const result = await installToTool(tool, FORGE_ROOT);
106
+ console.log(chalk.green(' ✓') + ' ' + formatInstallSummary(tool, result));
89
107
  }
90
108
 
91
109
  console.log();
@@ -145,13 +163,82 @@ async function commandInstall() {
145
163
 
146
164
  for (const toolKey of selectedTools) {
147
165
  const tool = detected.find(t => t.key === toolKey);
148
- await installToTool(tool, FORGE_ROOT);
149
- console.log(chalk.green(' ✓') + ` Installed to ${tool.name}`);
166
+ const result = await installToTool(tool, FORGE_ROOT);
167
+ console.log(chalk.green(' ✓') + ' ' + formatInstallSummary(tool, result));
150
168
  }
151
169
 
152
170
  console.log(chalk.bold.green('\n✅ Done\n'));
153
171
  }
154
172
 
173
+ async function commandDoctor() {
174
+ console.log(chalk.bold('\n🔨 forge doctor\n'));
175
+
176
+ const detected = detectTools();
177
+ if (detected.length === 0) {
178
+ console.log(chalk.red(' ⚠️ No supported AI coding tools detected.'));
179
+ console.log(chalk.dim(' Install one of: Claude Code, Codex CLI, Cursor, Gemini CLI'));
180
+ console.log(chalk.dim(' Then re-run: npx @firatcand/forge\n'));
181
+ process.exit(1);
182
+ }
183
+
184
+ let anyDrift = false;
185
+ for (const tool of detected) {
186
+ const audit = await auditTool(tool, FORGE_ROOT);
187
+ const agentLabelWord = tool.key === 'codex' ? 'subagents' : 'agents';
188
+
189
+ const skillsStaleSuffix = audit.staleSkills.length > 0
190
+ ? chalk.yellow(` (${audit.staleSkills.length} stale)`)
191
+ : '';
192
+ const skillsLabel = `${audit.installedSkills.length}/${audit.expectedSkills.length} skills${skillsStaleSuffix}`;
193
+
194
+ let agentsLabel = null;
195
+ if (audit.agentsTarget) {
196
+ const agentsStaleSuffix = audit.staleAgents.length > 0
197
+ ? chalk.yellow(` (${audit.staleAgents.length} stale)`)
198
+ : '';
199
+ agentsLabel = `${audit.installedAgents.length}/${audit.expectedAgents.length} ${agentLabelWord}${agentsStaleSuffix}`;
200
+ }
201
+
202
+ const toolDrift =
203
+ audit.missingSkills.length > 0 ||
204
+ audit.missingAgents.length > 0 ||
205
+ audit.staleSkills.length > 0 ||
206
+ audit.staleAgents.length > 0;
207
+
208
+ const statusIcon = toolDrift ? chalk.yellow(' ⚠') : chalk.green(' ✓');
209
+ const counts = agentsLabel ? `${skillsLabel}, ${agentsLabel}` : skillsLabel;
210
+ console.log(`${statusIcon} ${tool.name} — ${counts}`);
211
+ console.log(chalk.dim(` skills: ${tildify(audit.skillsTarget)}`));
212
+ if (audit.agentsTarget) {
213
+ console.log(chalk.dim(` ${agentLabelWord}: ${tildify(audit.agentsTarget)}`));
214
+ }
215
+
216
+ if (audit.missingSkills.length > 0) {
217
+ console.log(chalk.yellow(` missing skills: ${audit.missingSkills.join(', ')}`));
218
+ }
219
+ if (audit.staleSkills.length > 0) {
220
+ console.log(chalk.yellow(` stale skills: ${audit.staleSkills.join(', ')}`));
221
+ }
222
+ if (audit.missingAgents.length > 0) {
223
+ console.log(chalk.yellow(` missing ${agentLabelWord}: ${audit.missingAgents.join(', ')}`));
224
+ }
225
+ if (audit.staleAgents.length > 0) {
226
+ console.log(chalk.yellow(` stale ${agentLabelWord}: ${audit.staleAgents.join(', ')}`));
227
+ }
228
+
229
+ if (toolDrift) anyDrift = true;
230
+ }
231
+
232
+ console.log();
233
+ if (anyDrift) {
234
+ console.log(chalk.yellow('Some tools have missing or stale forge skills/agents.'));
235
+ console.log(chalk.dim('Run: ') + chalk.cyan('npx @firatcand/forge install') + chalk.dim(' to sync.\n'));
236
+ process.exit(1);
237
+ }
238
+
239
+ console.log(chalk.bold.green('✅ All detected tools are in sync\n'));
240
+ }
241
+
155
242
  async function commandCompanions() {
156
243
  console.log(chalk.bold('\n🔨 forge companions\n'));
157
244
 
@@ -319,7 +406,7 @@ async function commandInit(projectNameArg) {
319
406
  console.log(chalk.bold.green('\n✅ Project initialized\n'));
320
407
  console.log(chalk.bold('Next:'));
321
408
  console.log(' Open your AI coding tool: ' + chalk.cyan('claude') + ' or ' + chalk.cyan('codex'));
322
- console.log(' Run: ' + chalk.cyan('/forge') + ' (Socratic ideation → BRIEF.md)\n');
409
+ console.log(' Run: ' + chalk.cyan('/forge') + ' (discovery interview → BRIEF.md)\n');
323
410
  }
324
411
 
325
412
  async function main() {
@@ -331,6 +418,9 @@ async function main() {
331
418
  case 'install':
332
419
  await commandInstall();
333
420
  break;
421
+ case 'doctor':
422
+ await commandDoctor();
423
+ break;
334
424
  case 'companions':
335
425
  await commandCompanions();
336
426
  break;
package/lib/tools.js CHANGED
@@ -1,7 +1,38 @@
1
1
  import path from 'path';
2
2
  import os from 'os';
3
+ import crypto from 'crypto';
3
4
  import fs from 'fs-extra';
4
5
 
6
+ const HASH_IGNORE = new Set(['.DS_Store', 'Thumbs.db', '.git']);
7
+
8
+ async function hashEntry(entryPath) {
9
+ let stat;
10
+ try {
11
+ stat = await fs.stat(entryPath);
12
+ } catch {
13
+ return null;
14
+ }
15
+ if (stat.isFile()) {
16
+ const buf = await fs.readFile(entryPath);
17
+ return crypto.createHash('sha256').update(buf).digest('hex');
18
+ }
19
+ if (stat.isDirectory()) {
20
+ const entries = (await fs.readdir(entryPath))
21
+ .filter(name => !HASH_IGNORE.has(name))
22
+ .sort();
23
+ const hash = crypto.createHash('sha256');
24
+ for (const name of entries) {
25
+ const childHash = await hashEntry(path.join(entryPath, name));
26
+ if (childHash === null) continue;
27
+ hash.update(name);
28
+ hash.update('\0');
29
+ hash.update(childHash);
30
+ }
31
+ return hash.digest('hex');
32
+ }
33
+ return null;
34
+ }
35
+
5
36
  const TOOL_DEFINITIONS = [
6
37
  {
7
38
  key: 'claude',
@@ -53,6 +84,7 @@ export async function installToTool(tool, forgeRoot) {
53
84
  await fs.copy(src, dst, { overwrite: true, errorOnExist: false });
54
85
  }
55
86
 
87
+ let agentsCount = 0;
56
88
  if (agentsTarget) {
57
89
  const agentsSource = path.join(forgeRoot, 'agents');
58
90
  if (await fs.pathExists(agentsSource)) {
@@ -63,6 +95,76 @@ export async function installToTool(tool, forgeRoot) {
63
95
  await fs.remove(dst);
64
96
  await fs.copy(src, dst, { overwrite: true, errorOnExist: false });
65
97
  }
98
+ agentsCount = agents.length;
66
99
  }
67
100
  }
101
+
102
+ return {
103
+ skillsCount: skills.length,
104
+ agentsCount,
105
+ skillsTarget,
106
+ agentsTarget,
107
+ };
108
+ }
109
+
110
+ export async function listExpectedSkills(forgeRoot) {
111
+ return fs.readdir(path.join(forgeRoot, 'skills'));
112
+ }
113
+
114
+ export async function listExpectedAgents(forgeRoot) {
115
+ const agentsSource = path.join(forgeRoot, 'agents');
116
+ if (!(await fs.pathExists(agentsSource))) return [];
117
+ return fs.readdir(agentsSource);
118
+ }
119
+
120
+ async function diffByContent(names, sourceDir, targetDir) {
121
+ const stale = [];
122
+ for (const name of names) {
123
+ const sourceHash = await hashEntry(path.join(sourceDir, name));
124
+ const targetHash = await hashEntry(path.join(targetDir, name));
125
+ if (sourceHash !== targetHash) stale.push(name);
126
+ }
127
+ return stale;
128
+ }
129
+
130
+ export async function auditTool(tool, forgeRoot) {
131
+ const expectedSkills = await listExpectedSkills(forgeRoot);
132
+ const skillsTarget = path.join(tool.dir, tool.skillsDir);
133
+ const skillsSource = path.join(forgeRoot, 'skills');
134
+ const skillsInstalled = (await fs.pathExists(skillsTarget))
135
+ ? new Set(await fs.readdir(skillsTarget))
136
+ : new Set();
137
+ const installedSkills = expectedSkills.filter(s => skillsInstalled.has(s));
138
+ const missingSkills = expectedSkills.filter(s => !skillsInstalled.has(s));
139
+ const staleSkills = await diffByContent(installedSkills, skillsSource, skillsTarget);
140
+
141
+ let agentsTarget = null;
142
+ let expectedAgents = [];
143
+ let installedAgents = [];
144
+ let missingAgents = [];
145
+ let staleAgents = [];
146
+ if (tool.agentsDir) {
147
+ agentsTarget = path.join(tool.dir, tool.agentsDir);
148
+ const agentsSource = path.join(forgeRoot, 'agents');
149
+ expectedAgents = await listExpectedAgents(forgeRoot);
150
+ const agentsInstalled = (await fs.pathExists(agentsTarget))
151
+ ? new Set(await fs.readdir(agentsTarget))
152
+ : new Set();
153
+ installedAgents = expectedAgents.filter(a => agentsInstalled.has(a));
154
+ missingAgents = expectedAgents.filter(a => !agentsInstalled.has(a));
155
+ staleAgents = await diffByContent(installedAgents, agentsSource, agentsTarget);
156
+ }
157
+
158
+ return {
159
+ skillsTarget,
160
+ expectedSkills,
161
+ installedSkills,
162
+ missingSkills,
163
+ staleSkills,
164
+ agentsTarget,
165
+ expectedAgents,
166
+ installedAgents,
167
+ missingAgents,
168
+ staleAgents,
169
+ };
68
170
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@firatcand/forge",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "description": "A lightweight framework for shipping software products from idea to production with AI coding agents (Claude Code, Codex CLI, Cursor, Gemini)",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: forge
3
- description: Apply Socratic pressure to a raw idea and produce a validated project BRIEF. Heavy ceremony — 6 forcing questions. The required first command for any new forge project.
3
+ description: Run forge's discovery interview over a raw idea and produce a validated project BRIEF. Heavy ceremony — 6 forcing questions. The required first command for any new forge project.
4
4
  tools: Read, Write, Edit, Bash(git*)
5
5
  ---
6
6
 
@@ -71,7 +71,7 @@ BRIEF written to spec/BRIEF.md
71
71
  Gate 1 — review the brief. To proceed:
72
72
  • /draft-prd to generate the PRD
73
73
  • Edit spec/BRIEF.md directly for fixes
74
- • /forge --refine [section] to re-Socratic a weak section
74
+ • /forge --refine [section] to re-interview a weak section
75
75
  ```
76
76
 
77
77
  ## --refine mode
@@ -22,7 +22,7 @@
22
22
  - New API routes must have input validation
23
23
  - See `spec/` for full conventions
24
24
 
25
- ## Forge principles (auto-applied — see ~/.forge/ETHOS.md)
25
+ ## Forge principles (auto-applied — see [Forge ETHOS.md](https://github.com/firatcand/forge/blob/main/ETHOS.md))
26
26
  1. Boil the Lake — refuse weak inputs
27
27
  2. Iron Law of Investigation — no fixes without RCA
28
28
  3. Confusion Protocol — clarify, don't guess