feat-forge 1.1.0 → 1.2.3

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
@@ -10,11 +10,11 @@
10
10
 
11
11
  Its goal is to make the specification of features explicit and separate the thinking/specifying phase from the coding/implementation phase, across multiple agents and repositories, while keeping everything organized and traceable.
12
12
 
13
- With **FeatForge** you will be able to :
13
+ With **FeatForge** you will be able to:
14
14
 
15
15
  - Parallelize work on multiple features:
16
16
  - across multiple repositories.
17
- - accross multiple agents.
17
+ - across multiple agents.
18
18
  - while keeping track of everything.
19
19
  - Come back to any feature after days/weeks and immediately understand its initial specifications.
20
20
  - Change agent configurations and prompts on a per-feature basis.
@@ -26,7 +26,7 @@ With **FeatForge** you will be able to :
26
26
  - Not opinionated about how you specify features, how you implement them, or how you use agents. It just provides a structure (yet customizable) and a workflow to keep everything organized and traceable.
27
27
  - Partially tested with : Copilot, Codex, Claude code (+Ollama, LM-Studio). But it should work with any agent that can be configured to read/write files in the `.active-spec` folders.
28
28
 
29
- **_This is an expirement, trying to mix between classic development and vibe-coding in large project with quality and sustainability in mind by making specification explicit and separating thinking from coding accross multiple agents and repositories._**
29
+ **_This is an experiment, trying to mix between classic development and vibe-coding in large project with quality and sustainability in mind by making specification explicit and separating thinking from coding across multiple agents and repositories._**
30
30
 
31
31
  ## Key Concepts
32
32
 
@@ -43,7 +43,7 @@ With **FeatForge** you will be able to :
43
43
  ## Installation
44
44
 
45
45
  ```bash
46
- npm install -g feat-forge-cli
46
+ npm install -g feat-forge
47
47
  forge --version
48
48
  ```
49
49
 
@@ -116,7 +116,7 @@ forge-project-root/
116
116
  ## Modes
117
117
 
118
118
  Each branch has a mode stored in `.specs/<slug>/.forge-mode`. Switching modes updates agent adapter files with the corresponding templates.
119
- Agent name are useful when calling subagent
119
+ Agent names are useful when calling subagents.
120
120
 
121
121
  Built-in modes:
122
122
 
@@ -302,6 +302,23 @@ Refresh agent adapter files for the nearest branch.
302
302
 
303
303
  Scan repositories for service declarations and generate configuration with allocated ports.
304
304
 
305
+ Service declarations are read from `.forge/services.json` in each repository. You can define an optional dedicated health endpoint per service:
306
+
307
+ ```json
308
+ {
309
+ "services": [
310
+ {
311
+ "name": "api",
312
+ "type": "http",
313
+ "path": "/api",
314
+ "healthCheckPath": "/health"
315
+ }
316
+ ]
317
+ }
318
+ ```
319
+
320
+ If `healthCheckPath` is omitted, proxy health checks fallback to `path`, then `/`.
321
+
305
322
  ---
306
323
 
307
324
  ### `forge services list [branch]`
@@ -355,6 +372,10 @@ For all commands and options:
355
372
  forge --help
356
373
  ```
357
374
 
375
+ ## Contributing
376
+
377
+ See [CONTRIBUTING.md](./CONTRIBUTING.md) for development setup and contribution workflow.
378
+
358
379
  # License
359
380
 
360
381
  [![License: AGPL-3.0](https://img.shields.io/npm/l/feat-forge)](./LICENSE)
@@ -176,18 +176,10 @@ try{
176
176
  const envCommands = data.envCmd?.subcommands.map((cmd) => cmd.name).join(' ') || '';
177
177
  const maintenanceCommands = data.maintenanceCmd?.subcommands.map((cmd) => cmd.name).join(' ') || '';
178
178
  const agentCommands = data.agentCmd?.subcommands.map((cmd) => cmd.name).join(' ') || '';
179
- const featureSlugCmds = data.featureCmd?.subcommands
180
- .filter((cmd) => data.slugSubcommands.includes(cmd.name))
181
- .map((cmd) => cmd.name) || [];
182
- const fixSlugCmds = data.fixCmd?.subcommands
183
- .filter((cmd) => data.slugSubcommands.includes(cmd.name))
184
- .map((cmd) => cmd.name) || [];
185
- const releaseSlugCmds = data.releaseCmd?.subcommands
186
- .filter((cmd) => data.slugSubcommands.includes(cmd.name))
187
- .map((cmd) => cmd.name) || [];
188
- const mainSlugCmds = data.mainCommands
189
- .filter((cmd) => data.rootSlugCommands.includes(cmd.name))
190
- .map((cmd) => cmd.name);
179
+ const featureSlugCmds = data.featureCmd?.subcommands.filter((cmd) => data.slugSubcommands.includes(cmd.name)).map((cmd) => cmd.name) || [];
180
+ const fixSlugCmds = data.fixCmd?.subcommands.filter((cmd) => data.slugSubcommands.includes(cmd.name)).map((cmd) => cmd.name) || [];
181
+ const releaseSlugCmds = data.releaseCmd?.subcommands.filter((cmd) => data.slugSubcommands.includes(cmd.name)).map((cmd) => cmd.name) || [];
182
+ const mainSlugCmds = data.mainCommands.filter((cmd) => data.rootSlugCommands.includes(cmd.name)).map((cmd) => cmd.name);
191
183
  const featureSlugCase = featureSlugCmds.length > 0
192
184
  ? ` ${featureSlugCmds.join('|')})
193
185
  if [[ \${cword} -eq 3 ]]; then
@@ -464,26 +456,22 @@ complete -F _forge_completion forge
464
456
  // ============================================================================
465
457
  generateZshCompletion() {
466
458
  const data = this.getCompletionData();
467
- const commandsArray = data.mainCommands.map((cmd) => ` '${cmd.name}:${cmd.description.replace(/'/g, "''")}'`).join('\n');
459
+ const commandsArray = data.mainCommands
460
+ .map((cmd) => ` '${cmd.name}:${cmd.description.replace(/'/g, "''")}'`)
461
+ .join('\n');
468
462
  const featureArray = data.featureCmd?.subcommands.map((cmd) => ` '${cmd.name}:${cmd.description.replace(/'/g, "''")}'`).join('\n') || '';
469
463
  const fixArray = data.fixCmd?.subcommands.map((cmd) => ` '${cmd.name}:${cmd.description.replace(/'/g, "''")}'`).join('\n') || '';
470
464
  const releaseArray = data.releaseCmd?.subcommands.map((cmd) => ` '${cmd.name}:${cmd.description.replace(/'/g, "''")}'`).join('\n') || '';
471
- const servicesArray = data.servicesCmd?.subcommands.map((cmd) => ` '${cmd.name}:${cmd.description.replace(/'/g, "''")}'`).join('\n') || '';
465
+ const servicesArray = data.servicesCmd?.subcommands.map((cmd) => ` '${cmd.name}:${cmd.description.replace(/'/g, "''")}'`).join('\n') ||
466
+ '';
472
467
  const envArray = data.envCmd?.subcommands.map((cmd) => ` '${cmd.name}:${cmd.description.replace(/'/g, "''")}'`).join('\n') || '';
473
- const maintenanceArray = data.maintenanceCmd?.subcommands.map((cmd) => ` '${cmd.name}:${cmd.description.replace(/'/g, "''")}'`).join('\n') || '';
468
+ const maintenanceArray = data.maintenanceCmd?.subcommands.map((cmd) => ` '${cmd.name}:${cmd.description.replace(/'/g, "''")}'`).join('\n') ||
469
+ '';
474
470
  const agentArray = data.agentCmd?.subcommands.map((cmd) => ` '${cmd.name}:${cmd.description.replace(/'/g, "''")}'`).join('\n') || '';
475
- const featureSlugCmds = data.featureCmd?.subcommands
476
- .filter((cmd) => data.slugSubcommands.includes(cmd.name))
477
- .map((cmd) => cmd.name) || [];
478
- const fixSlugCmds = data.fixCmd?.subcommands
479
- .filter((cmd) => data.slugSubcommands.includes(cmd.name))
480
- .map((cmd) => cmd.name) || [];
481
- const releaseSlugCmds = data.releaseCmd?.subcommands
482
- .filter((cmd) => data.slugSubcommands.includes(cmd.name))
483
- .map((cmd) => cmd.name) || [];
484
- const mainSlugCmds = data.mainCommands
485
- .filter((cmd) => data.rootSlugCommands.includes(cmd.name))
486
- .map((cmd) => cmd.name);
471
+ const featureSlugCmds = data.featureCmd?.subcommands.filter((cmd) => data.slugSubcommands.includes(cmd.name)).map((cmd) => cmd.name) || [];
472
+ const fixSlugCmds = data.fixCmd?.subcommands.filter((cmd) => data.slugSubcommands.includes(cmd.name)).map((cmd) => cmd.name) || [];
473
+ const releaseSlugCmds = data.releaseCmd?.subcommands.filter((cmd) => data.slugSubcommands.includes(cmd.name)).map((cmd) => cmd.name) || [];
474
+ const mainSlugCmds = data.mainCommands.filter((cmd) => data.rootSlugCommands.includes(cmd.name)).map((cmd) => cmd.name);
487
475
  const featureSlugCase = featureSlugCmds.length > 0
488
476
  ? ` ${featureSlugCmds.join('|')})
489
477
  local -a slugs
@@ -793,8 +781,7 @@ compdef _forge forge
793
781
  const fixSlugCompletions = genSlugCompletions('fix', data.fixCmd, '__forge_fix_slugs');
794
782
  const releaseSlugCompletions = genSlugCompletions('release', data.releaseCmd, '__forge_release_slugs');
795
783
  // Root-level slug commands
796
- const mainSlugCmds = data.mainCommands
797
- .filter((cmd) => data.rootSlugCommands.includes(cmd.name));
784
+ const mainSlugCmds = data.mainCommands.filter((cmd) => data.rootSlugCommands.includes(cmd.name));
798
785
  const mainSlugCompletions = mainSlugCmds
799
786
  .map((cmd) => `complete -c forge -n "__fish_seen_subcommand_from ${cmd.name}" -a "(__forge_all_branches)"`)
800
787
  .join('\n');
@@ -1001,18 +988,10 @@ complete -c forge -n "__fish_seen_subcommand_from alias" -a pwsh -d "Generate Po
1001
988
  const envCommandsList = toPsArray(data.envCmd?.subcommands.map((cmd) => cmd.name) || []);
1002
989
  const maintenanceCommandsList = toPsArray(data.maintenanceCmd?.subcommands.map((cmd) => cmd.name) || []);
1003
990
  const agentCommandsList = toPsArray(data.agentCmd?.subcommands.map((cmd) => cmd.name) || []);
1004
- const featureSlugCommandsList = toPsArray(data.featureCmd?.subcommands
1005
- .filter((cmd) => data.slugSubcommands.includes(cmd.name))
1006
- .map((cmd) => cmd.name) || []);
1007
- const fixSlugCommandsList = toPsArray(data.fixCmd?.subcommands
1008
- .filter((cmd) => data.slugSubcommands.includes(cmd.name))
1009
- .map((cmd) => cmd.name) || []);
1010
- const releaseSlugCommandsList = toPsArray(data.releaseCmd?.subcommands
1011
- .filter((cmd) => data.slugSubcommands.includes(cmd.name))
1012
- .map((cmd) => cmd.name) || []);
1013
- const mainSlugCommandsList = toPsArray(data.mainCommands
1014
- .filter((cmd) => data.rootSlugCommands.includes(cmd.name))
1015
- .map((cmd) => cmd.name));
991
+ const featureSlugCommandsList = toPsArray(data.featureCmd?.subcommands.filter((cmd) => data.slugSubcommands.includes(cmd.name)).map((cmd) => cmd.name) || []);
992
+ const fixSlugCommandsList = toPsArray(data.fixCmd?.subcommands.filter((cmd) => data.slugSubcommands.includes(cmd.name)).map((cmd) => cmd.name) || []);
993
+ const releaseSlugCommandsList = toPsArray(data.releaseCmd?.subcommands.filter((cmd) => data.slugSubcommands.includes(cmd.name)).map((cmd) => cmd.name) || []);
994
+ const mainSlugCommandsList = toPsArray(data.mainCommands.filter((cmd) => data.rootSlugCommands.includes(cmd.name)).map((cmd) => cmd.name));
1016
995
  const completionShellsList = toPsArray(['bash', 'zsh', 'fish', 'powershell', 'pwsh']);
1017
996
  return `# forge PowerShell completion script
1018
997
 
@@ -75,6 +75,8 @@ export class BranchPortAllocation {
75
75
  const serviceName = service.name;
76
76
  const existingService = this.getService(serviceName);
77
77
  if (existingService) {
78
+ // Keep the allocated port stable, but refresh service metadata from latest scan
79
+ Object.assign(existingService, service);
78
80
  return existingService.port;
79
81
  }
80
82
  const nextPort = this.nextAvailablePort;
@@ -28,10 +28,12 @@ export class Proxy {
28
28
  this.routingTable = await this.buildRoutingTable(branchContexts);
29
29
  this.showSummary(port);
30
30
  this.startServer(port);
31
- this.startWatching();
32
31
  console.log(`\n🚀 Proxy server running on http://localhost:${port}`);
33
32
  console.log(`📊 Dashboard: http://localhost:${port}`);
34
33
  console.log('\nPress Ctrl+C to stop.\n');
34
+ // Watchers can take noticeable time to initialize on large trees.
35
+ // Start them after startup logs so the proxy appears ready immediately.
36
+ setImmediate(() => this.startWatching());
35
37
  }
36
38
  stop() {
37
39
  this.stopWatching?.();
@@ -47,12 +49,13 @@ export class Proxy {
47
49
  try {
48
50
  const generated = await loadGeneratedServicesFile(branchContext);
49
51
  for (const service of generated.services) {
50
- const { proxyUrl, url, key, name } = getServiceOutputs(this.context, branchContext, service);
52
+ const { proxyUrl, url, key, name, healthCheckUrl } = getServiceOutputs(this.context, branchContext, service);
51
53
  table.set(key, {
52
54
  branchName: branchContext.branchName,
53
55
  serviceName: name,
54
56
  url,
55
57
  proxyUrl,
58
+ healthCheckUrl,
56
59
  });
57
60
  }
58
61
  }
@@ -16,6 +16,7 @@ export class ServiceDefinition {
16
16
  name;
17
17
  type = 'http'; // Phase 1: HTTP only, but extensible
18
18
  path; // HTTP only, e.g., "/api"
19
+ healthCheckPath; // HTTP only, e.g., "/health"
19
20
  }
20
21
  __decorate([
21
22
  IsString(),
@@ -31,6 +32,11 @@ __decorate([
31
32
  IsString(),
32
33
  __metadata("design:type", String)
33
34
  ], ServiceDefinition.prototype, "path", void 0);
35
+ __decorate([
36
+ IsOptional(),
37
+ IsString(),
38
+ __metadata("design:type", String)
39
+ ], ServiceDefinition.prototype, "healthCheckPath", void 0);
34
40
  /**
35
41
  * Services declaration from .forge/services.json
36
42
  */
@@ -1,24 +1,9 @@
1
- import http from 'http';
2
- async function checkHealth(target) {
3
- return new Promise((resolve) => {
4
- const req = http.get(target, { timeout: 2000 }, (res) => {
5
- res.resume();
6
- resolve(true);
7
- });
8
- req.on('error', () => resolve(false));
9
- req.on('timeout', () => {
10
- req.destroy();
11
- resolve(false);
12
- });
13
- });
14
- }
15
1
  export async function handleDashboardRequest(req, res, routingTable, proxyPort) {
16
2
  const statuses = [];
17
- const checks = Array.from(routingTable.entries()).map(async ([key, route]) => {
18
- const healthy = await checkHealth(route.url);
19
- statuses.push({ key, route, healthy });
3
+ Array.from(routingTable.entries()).forEach(([, route], index) => {
4
+ const rowId = `${route.branchName}::${route.serviceName}::${index}`;
5
+ statuses.push({ id: encodeURIComponent(rowId), route, status: 'PENDING' });
20
6
  });
21
- await Promise.all(checks);
22
7
  statuses.sort((a, b) => a.route.branchName.localeCompare(b.route.branchName) || a.route.serviceName.localeCompare(b.route.serviceName));
23
8
  const grouped = new Map();
24
9
  for (const s of statuses) {
@@ -28,27 +13,28 @@ export async function handleDashboardRequest(req, res, routingTable, proxyPort)
28
13
  }
29
14
  const rows = Array.from(grouped.entries())
30
15
  .map(([branchName, services]) => {
31
- const header = `<tr class="branch-header"><td colspan="4"><strong>${branchName}</strong></td></tr>`;
16
+ const header = `<tr class="branch-header"><td colspan="5"><strong>${branchName}</strong></td></tr>`;
32
17
  const serviceRows = services
33
18
  .map((s) => {
34
- const statusBadge = s.healthy
35
- ? '<span style="color:#22c55e;font-weight:bold">● UP</span>'
36
- : '<span style="color:#ef4444;font-weight:bold">● DOWN</span>';
37
- return `<tr>
19
+ return `<tr data-health-row="${s.id}">
38
20
  <td>${s.route.serviceName}</td>
39
21
  <td><a href="${s.route.proxyUrl}" target="_blank">${s.route.proxyUrl}</a></td>
40
22
  <td><a href="${s.route.url}" target="_blank">${s.route.url}</a></td>
41
- <td>${statusBadge}</td>
23
+ <td><a href="${s.route.healthCheckUrl}" target="_blank">${s.route.healthCheckUrl}</a></td>
24
+ <td data-health-status="${s.id}"><span style="color:#f59e0b;font-weight:bold">● ${s.status}</span></td>
42
25
  </tr>`;
43
26
  })
44
27
  .join('\n');
45
28
  return header + '\n' + serviceRows;
46
29
  })
47
30
  .join('\n');
31
+ const healthTargets = statuses.map((s) => ({
32
+ id: s.id,
33
+ url: s.route.healthCheckUrl,
34
+ }));
48
35
  const html = `<!DOCTYPE html>
49
36
  <html><head>
50
37
  <meta charset="utf-8">
51
- <meta http-equiv="refresh" content="30">
52
38
  <title>Feat-Forge Proxy Dashboard</title>
53
39
  <style>
54
40
  body { font-family: system-ui, sans-serif; margin: 2rem; background: #0f172a; color: #e2e8f0; }
@@ -66,9 +52,47 @@ export async function handleDashboardRequest(req, res, routingTable, proxyPort)
66
52
  <h1>Feat-Forge Proxy Dashboard</h1>
67
53
  <p class="meta">${statuses.length} routes &middot; auto-refresh 30s &middot; proxy port ${proxyPort}</p>
68
54
  <table>
69
- <thead><tr><th>Service</th><th>Proxy URL</th><th>Target</th><th>Status</th></tr></thead>
70
- <tbody>${rows || '<tr><td colspan="4" style="text-align:center;color:#64748b">No routes configured. Run <code>forge services scan</code> on active branches.</td></tr>'}</tbody>
55
+ <thead><tr><th>Service</th><th>Proxy URL</th><th>Target</th><th>Health Check</th><th>Status</th></tr></thead>
56
+ <tbody>${rows || '<tr><td colspan="5" style="text-align:center;color:#64748b">No routes configured. Run <code>forge services scan</code> on active branches.</td></tr>'}</tbody>
71
57
  </table>
58
+ <script>
59
+ const healthTargets = ${JSON.stringify(healthTargets)};
60
+ const HEALTH_TIMEOUT_MS = 1200;
61
+ const REFRESH_EVERY_MS = 30000;
62
+
63
+ function setStatus(id, label, color) {
64
+ const el = document.querySelector('[data-health-status="' + id + '"]');
65
+ if (!el) return;
66
+ el.innerHTML = '<span style="color:' + color + ';font-weight:bold">● ' + label + '</span>';
67
+ }
68
+
69
+ async function checkHealth(url) {
70
+ const controller = new AbortController();
71
+ const timer = setTimeout(() => controller.abort(), HEALTH_TIMEOUT_MS);
72
+ try {
73
+ // no-cors allows probing reachability from the browser without requiring CORS headers.
74
+ await fetch(url, { mode: 'no-cors', cache: 'no-store', signal: controller.signal });
75
+ return true;
76
+ } catch {
77
+ return false;
78
+ } finally {
79
+ clearTimeout(timer);
80
+ }
81
+ }
82
+
83
+ async function refreshStatuses() {
84
+ await Promise.all(healthTargets.map(async ({ id, url }) => {
85
+ setStatus(id, 'PENDING', '#f59e0b');
86
+ const healthy = await checkHealth(url);
87
+ setStatus(id, healthy ? 'UP' : 'DOWN', healthy ? '#22c55e' : '#ef4444');
88
+ }));
89
+ }
90
+
91
+ if (healthTargets.length > 0) {
92
+ refreshStatuses();
93
+ setInterval(refreshStatuses, REFRESH_EVERY_MS);
94
+ }
95
+ </script>
72
96
  </body></html>`;
73
97
  res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
74
98
  res.end(html);
@@ -119,6 +119,7 @@ export function getServiceOutputs(forgeContext, branchContext, service) {
119
119
  const urlSlug = slugify(service.name, false);
120
120
  const branchSlug = branchContext.branchNameSlug;
121
121
  const proxyPort = forgeContext.options.proxy.port;
122
+ const healthCheckPath = service.healthCheckPath || service.path || '/';
122
123
  return {
123
124
  ...service,
124
125
  branchSlug,
@@ -128,5 +129,6 @@ export function getServiceOutputs(forgeContext, branchContext, service) {
128
129
  urlSlug: urlSlug,
129
130
  url: `${service.type}://localhost:${service.port}${service.path || ''}`,
130
131
  proxyUrl: `${service.type}://${branchSlug}.${urlSlug}.localhost:${proxyPort}${service.path || ''}`,
132
+ healthCheckUrl: `${service.type}://localhost:${service.port}${healthCheckPath}`,
131
133
  };
132
134
  }
@@ -4,8 +4,8 @@ This project is **process-first**: changes must be deliberate, reviewable, and v
4
4
 
5
5
  ## Golden Rules (must follow)
6
6
 
7
- 1. **Read the feature spec files first (in this order), they are in the .active-feature folder:**
8
- - `../FEATURE.md`
7
+ 1. **Read the feature spec files first (in this order), they are in the .active-spec folder:**
8
+ - `../SPEC.md`
9
9
  - `../TODO.md`
10
10
 
11
11
  2. **Do not implement code blindly.**
@@ -26,7 +26,7 @@ This project is **process-first**: changes must be deliberate, reviewable, and v
26
26
 
27
27
  5. **Always keep the spec in sync.**
28
28
  - If you implement something, update `TODO.md` (checklist / status).
29
- - If you make a design choice, record it in a sub section in `FEATURE.md`.
29
+ - If you make a design choice, record it in a sub section in `SPEC.md`.
30
30
 
31
31
  6. **No secrets / no destructive actions.**
32
32
  - Don’t touch credentials, `.env`, keys, or user-specific configs.
@@ -39,7 +39,7 @@ Protect your context window, use subagent to protecte it
39
39
  1. **Read specifications**
40
40
 
41
41
  - Use a subagent **TODO Reader** to read the specifications files and extract actionable tasks, clarifying and prioritizing them as needed :
42
- - `../FEATURE.md`
42
+ - `../SPEC.md`
43
43
  - `../TODO.md`
44
44
  - Use it to define clear independant code tasks
45
45
 
@@ -62,11 +62,11 @@ Protect your context window, use subagent to protecte it
62
62
 
63
63
  Its a TODO Reader agent for the current feature.
64
64
 
65
- His job is to extract, clarify, and prioritize actionable tasks from `TODO.md`, based on concepts in `FEATURE.md`. Specs files are here : - `../FEATURE.md` - `../TODO.md`
65
+ His job is to extract, clarify, and prioritize actionable tasks from `TODO.md`, based on concepts in `SPEC.md`. Specs files are here : - `../SPEC.md` - `../TODO.md`
66
66
 
67
67
  #### Responsibilities
68
68
 
69
- - Read and understand the feature specification in `FEATURE.md`.
69
+ - Read and understand the feature specification in `SPEC.md`.
70
70
  - Read and understand every item in `TODO.md`.
71
71
  - Clarify ambiguities by asking questions if needed.
72
72
  - Output a clear, actionable list of tasks for implementation by other agents, ensuring each task is specific, measurable, and feasible.
@@ -138,7 +138,7 @@ A step is “done” only if:
138
138
  - The changes are implemented
139
139
  - `TODO.md` is updated accordingly
140
140
  - Verification steps are provided (and ideally runnable)
141
- - Any new major design choice is recorded in `FEATURE.md`
141
+ - Any new major design choice is recorded in `SPEC.md`
142
142
 
143
143
  ## If something is missing
144
144
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  This project is **process-first**: clarity before code, all changes must go through the feature specification files:
4
4
 
5
- - `../FEATURE.md`
5
+ - `../SPEC.md`
6
6
  - `../TODO.md`
7
7
 
8
8
  You are currently in **SPEC MODE** / **PLAN MODE**.
@@ -14,13 +14,13 @@ You must only modify the specification files and not touch application code.
14
14
 
15
15
  ## Golden Rules
16
16
 
17
- 1. **Your scope is limited to writing and clarifying the specification files in `.active-feature`:**
18
- - `../FEATURE.md`
17
+ 1. **Your scope is limited to writing and clarifying the specification files in `.active-spec`:**
18
+ - `../SPEC.md`
19
19
  - `../TODO.md`
20
20
  You can update them as much as needed.
21
21
 
22
22
  2. **Do not modify application code.**
23
- - Only files in `.active-feature` should be modified
23
+ - Only files in `.active-spec` should be modified
24
24
  - No modification of source files, tests, or build scripts elsewhere in the project
25
25
  - If code changes are needed, express them as tasks or points to clarify in the specs.
26
26
 
@@ -28,15 +28,15 @@ You must only modify the specification files and not touch application code.
28
28
  - If something is unclear, ask the user in the chat.
29
29
  - Prefer questions, options, and trade-offs over premature conclusions.
30
30
  - Ask questions one by one or in small batches (max 4 at a time).
31
- - Use the answers to enrich and clarify `FEATURE.md`.
31
+ - Use the answers to enrich and clarify `SPEC.md`.
32
32
 
33
33
  4. **All proposals must be traceable.**
34
34
  - Avoid vague suggestions like "we should consider X" without writing it in the specification.
35
- - Any idea or question must be asked to the user and the answer added to `FEATURE.md` or `TODO.md`.
35
+ - Any idea or question must be asked to the user and the answer added to `SPEC.md` or `TODO.md`.
36
36
 
37
37
  5. **Use user answers to enrich the specification.**
38
38
  - The user can edit the files, reread them each time to see if decisions have been made.
39
- - Move answers to the appropriate section of `FEATURE.md` or add tasks in `TODO.md`.
39
+ - Move answers to the appropriate section of `SPEC.md` or add tasks in `TODO.md`.
40
40
 
41
41
  6. **No irreversible or destructive actions.**
42
42
  - Do not touch credentials, `.env`, keys, or user configs.
@@ -72,7 +72,7 @@ You are not optimizing for speed, but for **shared understanding**.
72
72
  - risks and assumptions
73
73
  - alternative designs
74
74
  - good architecture practices
75
- - Improve the structure and wording of `FEATURE.md` and `TODO.md`
75
+ - Improve the structure and wording of `SPEC.md` and `TODO.md`
76
76
  - Actively modify these two files
77
77
 
78
78
  ---
@@ -82,7 +82,7 @@ You are not optimizing for speed, but for **shared understanding**.
82
82
  - **Think in layers**: intent → constraints → decisions → tasks
83
83
  - Prefer explicit over implicit
84
84
  - Prefer writing things down over remembering them
85
- - All important decisions must be made explicit in `FEATURE.md` or as tasks in `TODO.md`
85
+ - All important decisions must be made explicit in `SPEC.md` or as tasks in `TODO.md`
86
86
  - No part of the implementation relies on “we’ll figure it out later”
87
87
  - If you cannot confidently improve the spec: ask questions
88
88
 
@@ -23,7 +23,7 @@ handoffs:
23
23
 
24
24
  You are a SPECIFICATION AGENT, NOT an implementation agent.
25
25
 
26
- You are pairing with the user to create a clear, detailed, and actionable specification for the given feature and any user feedback. Your iterative <workflow> loops through gathering context, asking questions and updating the specification files (`FEATURE.md` and `TODO.md`), then back to gathering more context based on user feedback.
26
+ You are pairing with the user to create a clear, detailed, and actionable specification for the given feature and any user feedback. Your iterative <workflow> loops through gathering context, asking questions and updating the specification files (`SPEC.md` and `TODO.md`), then back to gathering more context based on user feedback.
27
27
 
28
28
  The path to the specification files are :
29
29
  %%--COPILOT_SPEC_FILES--%%
@@ -31,7 +31,7 @@ The path to the specification files are :
31
31
  Your SOLE responsibility is to clarify, structure, and complete the feature specification. NEVER start implementation or modify application code.
32
32
 
33
33
  <stopping_rules>
34
- STOP IMMEDIATELY if you consider starting implementation, switching to implementation mode, or editing any file outside of `FEATURE.md` and `TODO.md` in `.active-feature`.
34
+ STOP IMMEDIATELY if you consider starting implementation, switching to implementation mode, or editing any file outside of `SPEC.md` and `TODO.md` in `.active-spec`.
35
35
 
36
36
  If you catch yourself planning implementation steps for YOU to execute, STOP. Your job is to update the specification files for the USER or another agent to implement later.
37
37
  </stopping_rules>
@@ -49,7 +49,7 @@ If #tool:agent/runSubagent tool is NOT available, run <spec_research> via tools
49
49
 
50
50
  ## 2. Update the specification files:
51
51
 
52
- 1. Update `FEATURE.md` and `TODO.md` directly with all clarifications, responses, and structure improvements.
52
+ 1. Update `SPEC.md` and `TODO.md` directly with all clarifications, responses, and structure improvements.
53
53
  2. Pause for user feedback, framing this as a draft for review.
54
54
 
55
55
  ## 3. Handle user feedback:
@@ -13,7 +13,7 @@ handoffs:
13
13
  showContinueOn: false
14
14
  ---
15
15
 
16
- You are a SPECIFICATION COMMIT MESSAGE AGENT. Your role is to prepare a commit message for the specification changes made in the `FEATURE.md` and `TODO.md` files :
16
+ You are a SPECIFICATION COMMIT MESSAGE AGENT. Your role is to prepare a commit message for the specification changes made in the `SPEC.md` and `TODO.md` files :
17
17
  %%--COPILOT_SPEC_FILES--%%
18
18
 
19
19
  Your commit message should follow the repository conventions and clearly indicate that the commit contains specification updates.
@@ -6,12 +6,12 @@ tools: ['search', 'read', 'todo']
6
6
 
7
7
  You are a TODO Reader agent for the current feature.
8
8
 
9
- Your job is to extract, clarify, and prioritize actionable tasks from `TODO.md`, based on concepts in `FEATURE.md`. Specs files are here :
9
+ Your job is to extract, clarify, and prioritize actionable tasks from `TODO.md`, based on concepts in `SPEC.md`. Specs files are here :
10
10
  %%--COPILOT_SPEC_FILES--%%
11
11
 
12
12
  ## Responsibilities
13
13
 
14
- - Read and understand the feature specification in `FEATURE.md`.
14
+ - Read and understand the feature specification in `SPEC.md`.
15
15
  - Read and understand every item in `TODO.md`.
16
16
  - Clarify ambiguities by asking questions if needed.
17
17
  - Output a clear, actionable list of tasks for implementation by other agents, ensuring each task is specific, measurable, and feasible.
package/package.json CHANGED
@@ -1,9 +1,10 @@
1
1
  {
2
2
  "name": "feat-forge",
3
- "version": "1.1.0",
3
+ "version": "1.2.3",
4
4
  "description": "Feature-first CLI workflow tool for building software at scale with AI agents. Organize specs, parallelize work across repos and agents, and switch contexts without losing track.",
5
5
  "license": "AGPL-3.0-only",
6
6
  "type": "module",
7
+ "packageManager": "pnpm@10.29.3",
7
8
  "author": {
8
9
  "name": "Emmanuel Gauthier",
9
10
  "email": "dev@anorake.net"
@@ -40,6 +41,21 @@
40
41
  "files": [
41
42
  "dist"
42
43
  ],
44
+ "scripts": {
45
+ "build": "pnpm run build:clean && pnpm run build:transpile && pnpm run build:copy && pnpm run build:permissions",
46
+ "build:clean": "rm -rf dist",
47
+ "build:transpile": "tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json",
48
+ "build:copy": "cp -r src/templates dist/",
49
+ "build:permissions": "chmod +x dist/cli.js",
50
+ "format": "prettier --write .",
51
+ "format:check": "prettier --check .",
52
+ "lint": "tsc --noEmit -p tsconfig.json",
53
+ "lint:fix": "pnpm lint",
54
+ "test": "vitest run",
55
+ "test:watch": "vitest watch",
56
+ "test:coverage": "vitest run --coverage",
57
+ "generate:errors": "tsx scripts/generate-errors.ts"
58
+ },
43
59
  "dependencies": {
44
60
  "@inquirer/prompts": "^8.2.0",
45
61
  "class-transformer": "^0.5.1",
@@ -59,18 +75,5 @@
59
75
  "typescript": "^5.9.3",
60
76
  "vite-tsconfig-paths": "^6.1.0",
61
77
  "vitest": "^4.0.18"
62
- },
63
- "scripts": {
64
- "build": "pnpm run build:clean && pnpm run build:transpile && pnpm run build:copy && pnpm run build:permissions",
65
- "build:clean": "rm -rf dist",
66
- "build:transpile": "tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json",
67
- "build:copy": "cp -r src/templates dist/",
68
- "build:permissions": "chmod +x dist/cli.js",
69
- "format": "prettier --write .",
70
- "format:check": "prettier --check .",
71
- "test": "vitest run",
72
- "test:watch": "vitest watch",
73
- "test:coverage": "vitest run --coverage",
74
- "generate:errors": "tsx scripts/generate-errors.ts"
75
78
  }
76
- }
79
+ }