@entelligentsia/forgecli 0.8.4 → 0.9.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.
Files changed (170) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/README.md +165 -2
  3. package/dist/bin/argv.d.ts +2 -2
  4. package/dist/bin/argv.js +17 -0
  5. package/dist/bin/argv.js.map +1 -1
  6. package/dist/bin/config.d.ts +69 -0
  7. package/dist/bin/config.js +315 -0
  8. package/dist/bin/config.js.map +1 -0
  9. package/dist/bin/doctor.d.ts +1 -0
  10. package/dist/bin/doctor.js +12 -0
  11. package/dist/bin/doctor.js.map +1 -1
  12. package/dist/bin/forge.js +7 -0
  13. package/dist/bin/forge.js.map +1 -1
  14. package/dist/extensions/forgecli/config-command.d.ts +8 -0
  15. package/dist/extensions/forgecli/config-command.js +66 -0
  16. package/dist/extensions/forgecli/config-command.js.map +1 -0
  17. package/dist/extensions/forgecli/config-layer.d.ts +38 -0
  18. package/dist/extensions/forgecli/config-layer.js +68 -0
  19. package/dist/extensions/forgecli/config-layer.js.map +1 -0
  20. package/dist/extensions/forgecli/config-tui/component.d.ts +35 -0
  21. package/dist/extensions/forgecli/config-tui/component.js +236 -0
  22. package/dist/extensions/forgecli/config-tui/component.js.map +1 -0
  23. package/dist/extensions/forgecli/config-tui/handler.d.ts +40 -0
  24. package/dist/extensions/forgecli/config-tui/handler.js +240 -0
  25. package/dist/extensions/forgecli/config-tui/handler.js.map +1 -0
  26. package/dist/extensions/forgecli/config-tui/index.d.ts +5 -0
  27. package/dist/extensions/forgecli/config-tui/index.js +5 -0
  28. package/dist/extensions/forgecli/config-tui/index.js.map +1 -0
  29. package/dist/extensions/forgecli/config-tui/keys.d.ts +26 -0
  30. package/dist/extensions/forgecli/config-tui/keys.js +33 -0
  31. package/dist/extensions/forgecli/config-tui/keys.js.map +1 -0
  32. package/dist/extensions/forgecli/config-tui/plugin-config-reader.d.ts +23 -0
  33. package/dist/extensions/forgecli/config-tui/plugin-config-reader.js +58 -0
  34. package/dist/extensions/forgecli/config-tui/plugin-config-reader.js.map +1 -0
  35. package/dist/extensions/forgecli/config-tui/screens/advanced-menu.d.ts +7 -0
  36. package/dist/extensions/forgecli/config-tui/screens/advanced-menu.js +83 -0
  37. package/dist/extensions/forgecli/config-tui/screens/advanced-menu.js.map +1 -0
  38. package/dist/extensions/forgecli/config-tui/screens/confirm-quit.d.ts +11 -0
  39. package/dist/extensions/forgecli/config-tui/screens/confirm-quit.js +54 -0
  40. package/dist/extensions/forgecli/config-tui/screens/confirm-quit.js.map +1 -0
  41. package/dist/extensions/forgecli/config-tui/screens/override-editor.d.ts +11 -0
  42. package/dist/extensions/forgecli/config-tui/screens/override-editor.js +233 -0
  43. package/dist/extensions/forgecli/config-tui/screens/override-editor.js.map +1 -0
  44. package/dist/extensions/forgecli/config-tui/screens/overrides-list-phases.d.ts +7 -0
  45. package/dist/extensions/forgecli/config-tui/screens/overrides-list-phases.js +91 -0
  46. package/dist/extensions/forgecli/config-tui/screens/overrides-list-phases.js.map +1 -0
  47. package/dist/extensions/forgecli/config-tui/screens/overrides-list.d.ts +7 -0
  48. package/dist/extensions/forgecli/config-tui/screens/overrides-list.js +71 -0
  49. package/dist/extensions/forgecli/config-tui/screens/overrides-list.js.map +1 -0
  50. package/dist/extensions/forgecli/config-tui/screens/persona-editor.d.ts +10 -0
  51. package/dist/extensions/forgecli/config-tui/screens/persona-editor.js +182 -0
  52. package/dist/extensions/forgecli/config-tui/screens/persona-editor.js.map +1 -0
  53. package/dist/extensions/forgecli/config-tui/screens/persona-picker.d.ts +7 -0
  54. package/dist/extensions/forgecli/config-tui/screens/persona-picker.js +76 -0
  55. package/dist/extensions/forgecli/config-tui/screens/persona-picker.js.map +1 -0
  56. package/dist/extensions/forgecli/config-tui/screens/personas-list.d.ts +7 -0
  57. package/dist/extensions/forgecli/config-tui/screens/personas-list.js +98 -0
  58. package/dist/extensions/forgecli/config-tui/screens/personas-list.js.map +1 -0
  59. package/dist/extensions/forgecli/config-tui/screens/shared.d.ts +29 -0
  60. package/dist/extensions/forgecli/config-tui/screens/shared.js +100 -0
  61. package/dist/extensions/forgecli/config-tui/screens/shared.js.map +1 -0
  62. package/dist/extensions/forgecli/config-tui/screens/show-resolved.d.ts +23 -0
  63. package/dist/extensions/forgecli/config-tui/screens/show-resolved.js +128 -0
  64. package/dist/extensions/forgecli/config-tui/screens/show-resolved.js.map +1 -0
  65. package/dist/extensions/forgecli/config-tui/screens/tier-menu.d.ts +7 -0
  66. package/dist/extensions/forgecli/config-tui/screens/tier-menu.js +135 -0
  67. package/dist/extensions/forgecli/config-tui/screens/tier-menu.js.map +1 -0
  68. package/dist/extensions/forgecli/config-tui/screens/tier-picker.d.ts +9 -0
  69. package/dist/extensions/forgecli/config-tui/screens/tier-picker.js +122 -0
  70. package/dist/extensions/forgecli/config-tui/screens/tier-picker.js.map +1 -0
  71. package/dist/extensions/forgecli/config-tui/screens/types.d.ts +24 -0
  72. package/dist/extensions/forgecli/config-tui/screens/types.js +5 -0
  73. package/dist/extensions/forgecli/config-tui/screens/types.js.map +1 -0
  74. package/dist/extensions/forgecli/config-tui/screens.d.ts +24 -0
  75. package/dist/extensions/forgecli/config-tui/screens.js +78 -0
  76. package/dist/extensions/forgecli/config-tui/screens.js.map +1 -0
  77. package/dist/extensions/forgecli/config-tui/state/buffer.d.ts +11 -0
  78. package/dist/extensions/forgecli/config-tui/state/buffer.js +91 -0
  79. package/dist/extensions/forgecli/config-tui/state/buffer.js.map +1 -0
  80. package/dist/extensions/forgecli/config-tui/state/constants.d.ts +4 -0
  81. package/dist/extensions/forgecli/config-tui/state/constants.js +14 -0
  82. package/dist/extensions/forgecli/config-tui/state/constants.js.map +1 -0
  83. package/dist/extensions/forgecli/config-tui/state/index.d.ts +6 -0
  84. package/dist/extensions/forgecli/config-tui/state/index.js +9 -0
  85. package/dist/extensions/forgecli/config-tui/state/index.js.map +1 -0
  86. package/dist/extensions/forgecli/config-tui/state/init.d.ts +2 -0
  87. package/dist/extensions/forgecli/config-tui/state/init.js +30 -0
  88. package/dist/extensions/forgecli/config-tui/state/init.js.map +1 -0
  89. package/dist/extensions/forgecli/config-tui/state/model.d.ts +192 -0
  90. package/dist/extensions/forgecli/config-tui/state/model.js +4 -0
  91. package/dist/extensions/forgecli/config-tui/state/model.js.map +1 -0
  92. package/dist/extensions/forgecli/config-tui/state/reducer.d.ts +2 -0
  93. package/dist/extensions/forgecli/config-tui/state/reducer.js +212 -0
  94. package/dist/extensions/forgecli/config-tui/state/reducer.js.map +1 -0
  95. package/dist/extensions/forgecli/config-tui/state/selectors.d.ts +91 -0
  96. package/dist/extensions/forgecli/config-tui/state/selectors.js +231 -0
  97. package/dist/extensions/forgecli/config-tui/state/selectors.js.map +1 -0
  98. package/dist/extensions/forgecli/config-tui/state.d.ts +6 -0
  99. package/dist/extensions/forgecli/config-tui/state.js +11 -0
  100. package/dist/extensions/forgecli/config-tui/state.js.map +1 -0
  101. package/dist/extensions/forgecli/config-tui/theme.d.ts +37 -0
  102. package/dist/extensions/forgecli/config-tui/theme.js +88 -0
  103. package/dist/extensions/forgecli/config-tui/theme.js.map +1 -0
  104. package/dist/extensions/forgecli/config-tui/tier-meta.d.ts +28 -0
  105. package/dist/extensions/forgecli/config-tui/tier-meta.js +69 -0
  106. package/dist/extensions/forgecli/config-tui/tier-meta.js.map +1 -0
  107. package/dist/extensions/forgecli/config-writer.d.ts +16 -0
  108. package/dist/extensions/forgecli/config-writer.js +63 -0
  109. package/dist/extensions/forgecli/config-writer.js.map +1 -0
  110. package/dist/extensions/forgecli/fix-bug.js +85 -1
  111. package/dist/extensions/forgecli/fix-bug.js.map +1 -1
  112. package/dist/extensions/forgecli/forge-cli-schema.json +54 -0
  113. package/dist/extensions/forgecli/forge-commands.js +3 -8
  114. package/dist/extensions/forgecli/forge-commands.js.map +1 -1
  115. package/dist/extensions/forgecli/forge-subagent.d.ts +13 -0
  116. package/dist/extensions/forgecli/forge-subagent.js +19 -0
  117. package/dist/extensions/forgecli/forge-subagent.js.map +1 -1
  118. package/dist/extensions/forgecli/index.js +16 -0
  119. package/dist/extensions/forgecli/index.js.map +1 -1
  120. package/dist/extensions/forgecli/input-router.d.ts +33 -0
  121. package/dist/extensions/forgecli/input-router.js +133 -0
  122. package/dist/extensions/forgecli/input-router.js.map +1 -0
  123. package/dist/extensions/forgecli/model-resolver.d.ts +32 -0
  124. package/dist/extensions/forgecli/model-resolver.js +65 -0
  125. package/dist/extensions/forgecli/model-resolver.js.map +1 -0
  126. package/dist/extensions/forgecli/model-validator.d.ts +29 -0
  127. package/dist/extensions/forgecli/model-validator.js +107 -0
  128. package/dist/extensions/forgecli/model-validator.js.map +1 -0
  129. package/dist/extensions/forgecli/run-sprint.js +59 -0
  130. package/dist/extensions/forgecli/run-sprint.js.map +1 -1
  131. package/dist/extensions/forgecli/run-task.js +93 -1
  132. package/dist/extensions/forgecli/run-task.js.map +1 -1
  133. package/dist/extensions/forgecli/thread-switcher.js +5 -2
  134. package/dist/extensions/forgecli/thread-switcher.js.map +1 -1
  135. package/dist/extensions/forgecli/whats-new-widget.js +5 -2
  136. package/dist/extensions/forgecli/whats-new-widget.js.map +1 -1
  137. package/package.json +11 -3
  138. package/dist/extensions/forgecli/review-command.d.ts +0 -2
  139. package/dist/extensions/forgecli/review-command.js +0 -184
  140. package/dist/extensions/forgecli/review-command.js.map +0 -1
  141. package/dist/forge-payload/.tools/banners.cjs +0 -435
  142. package/dist/forge-payload/.tools/build-context-pack.cjs +0 -290
  143. package/dist/forge-payload/.tools/build-init-context.cjs +0 -322
  144. package/dist/forge-payload/.tools/build-overlay.cjs +0 -326
  145. package/dist/forge-payload/.tools/build-persona-pack.cjs +0 -226
  146. package/dist/forge-payload/.tools/collate.cjs +0 -1041
  147. package/dist/forge-payload/.tools/generation-manifest.cjs +0 -311
  148. package/dist/forge-payload/.tools/lib/forge-root.cjs +0 -59
  149. package/dist/forge-payload/.tools/lib/paths.cjs +0 -29
  150. package/dist/forge-payload/.tools/lib/pricing.cjs +0 -165
  151. package/dist/forge-payload/.tools/lib/project-root.cjs +0 -32
  152. package/dist/forge-payload/.tools/lib/result.js +0 -40
  153. package/dist/forge-payload/.tools/lib/store-facade.cjs +0 -162
  154. package/dist/forge-payload/.tools/lib/store-nlp.cjs +0 -250
  155. package/dist/forge-payload/.tools/lib/store-query-exec.cjs +0 -272
  156. package/dist/forge-payload/.tools/lib/validate.js +0 -141
  157. package/dist/forge-payload/.tools/manage-config.cjs +0 -340
  158. package/dist/forge-payload/.tools/manage-versions.cjs +0 -365
  159. package/dist/forge-payload/.tools/package.json +0 -3
  160. package/dist/forge-payload/.tools/parse-gates.cjs +0 -151
  161. package/dist/forge-payload/.tools/parse-verdict.cjs +0 -67
  162. package/dist/forge-payload/.tools/preflight-gate.cjs +0 -350
  163. package/dist/forge-payload/.tools/prompts/sprint-plan-prompt.md +0 -70
  164. package/dist/forge-payload/.tools/schemas/task-list.schema.json +0 -53
  165. package/dist/forge-payload/.tools/seed-store.cjs +0 -237
  166. package/dist/forge-payload/.tools/store-cli.cjs +0 -1226
  167. package/dist/forge-payload/.tools/store-query.cjs +0 -319
  168. package/dist/forge-payload/.tools/store.cjs +0 -315
  169. package/dist/forge-payload/.tools/substitute-placeholders.cjs +0 -625
  170. package/dist/forge-payload/.tools/validate-store.cjs +0 -593
@@ -1,311 +0,0 @@
1
- #!/usr/bin/env node
2
- 'use strict';
3
-
4
- // Forge tool: generation-manifest
5
- // Track and verify generated file integrity via content hashes.
6
- // Usage: generation-manifest record <path>
7
- // generation-manifest record-all
8
- // generation-manifest check <path>
9
- // generation-manifest list [--modified]
10
- // generation-manifest status
11
- // generation-manifest remove <path>
12
- // generation-manifest clear-namespace <prefix> Remove all entries whose path starts with <prefix>.
13
- // prefix must start with .forge/ or .claude/ and end with /
14
-
15
- const fs = require('fs');
16
- const path = require('path');
17
- const crypto = require('crypto');
18
-
19
- const MANIFEST_PATH = path.join(process.cwd(), '.forge', 'generation-manifest.json');
20
-
21
- function normalize(content) {
22
- return content
23
- .replace(/\r\n/g, '\n')
24
- .split('\n')
25
- .map(line => line.trimEnd())
26
- .join('\n');
27
- }
28
-
29
- function hashContent(content) {
30
- return 'sha256:' + crypto.createHash('sha256').update(normalize(content), 'utf8').digest('hex');
31
- }
32
-
33
- // ── exports (for testing) ────────────────────────────────────────────────────
34
-
35
- module.exports = { normalize, hashContent, MANIFEST_PATH };
36
-
37
- // ── CLI guard ────────────────────────────────────────────────────────────────
38
-
39
- if (require.main === module) {
40
- process.on('uncaughtException', (e) => {
41
- process.stderr.write(`× ${e.message}\n`);
42
- process.exit(1);
43
- });
44
-
45
- function readManifest() {
46
- if (!fs.existsSync(MANIFEST_PATH)) return { files: {} };
47
- try {
48
- return JSON.parse(fs.readFileSync(MANIFEST_PATH, 'utf8'));
49
- } catch (e) {
50
- process.stderr.write(`× reading manifest: ${e.message}\n`);
51
- process.exit(1);
52
- }
53
- }
54
-
55
- function writeManifest(manifest) {
56
- const dir = path.dirname(MANIFEST_PATH);
57
- if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
58
- const tmp = MANIFEST_PATH + '.tmp.' + process.pid;
59
- try {
60
- fs.writeFileSync(tmp, JSON.stringify(manifest, null, 2) + '\n', 'utf8');
61
- fs.renameSync(tmp, MANIFEST_PATH);
62
- } catch (e) {
63
- try { fs.unlinkSync(tmp); } catch {}
64
- process.stderr.write(`× writing manifest: ${e.message}\n`);
65
- process.exit(1);
66
- }
67
- }
68
-
69
- function getForgeVersion() {
70
- try {
71
- const configPath = path.join(process.cwd(), '.forge', 'config.json');
72
- if (fs.existsSync(configPath)) {
73
- const cfg = JSON.parse(fs.readFileSync(configPath, 'utf8'));
74
- if (cfg.version) return cfg.version;
75
- }
76
- } catch {}
77
- return 'unknown';
78
- }
79
-
80
- function toRelPath(filePath) {
81
- return path.relative(process.cwd(), path.resolve(filePath));
82
- }
83
-
84
- function fileStatus(relPath, entry) {
85
- const absPath = path.resolve(relPath);
86
- if (!fs.existsSync(absPath)) return 'missing';
87
- const current = hashContent(fs.readFileSync(absPath, 'utf8'));
88
- return current === entry.hash ? 'pristine' : 'modified';
89
- }
90
-
91
- const STATUS_SYMBOL = { pristine: '〇', modified: '△', missing: '×' };
92
-
93
- // ── subcommands ──────────────────────────────────────────────────────────────
94
-
95
- const [,, subcmd, ...args] = process.argv;
96
-
97
- if (!subcmd) {
98
- process.stderr.write([
99
- 'Usage: generation-manifest <subcommand> [options]',
100
- '',
101
- 'Subcommands:',
102
- ' record <path> Hash and store/update a file in the manifest',
103
- ' record-all Re-hash all files currently tracked in the manifest',
104
- ' check <path> Exit 0=pristine 1=modified 2=untracked 3=file missing',
105
- ' list [--modified] Table of tracked files with status',
106
- ' status Summary counts',
107
- ' remove <path> Remove a file from tracking',
108
- ' clear-namespace <prefix> Remove all entries whose path starts with <prefix>.',
109
- ' prefix must start with .forge/ or .claude/ and end with /',
110
- ].join('\n') + '\n');
111
- process.exit(2);
112
- }
113
-
114
- // ── record ───────────────────────────────────────────────────────────────────
115
-
116
- if (subcmd === 'record') {
117
- const filePath = args[0];
118
- if (!filePath) {
119
- process.stderr.write('Usage: generation-manifest record <path>\n');
120
- process.exit(2);
121
- }
122
- const relPath = toRelPath(filePath);
123
- const absPath = path.resolve(filePath);
124
- if (!fs.existsSync(absPath)) {
125
- process.stderr.write(`× File not found: ${filePath}\n`);
126
- process.exit(1);
127
- }
128
- const manifest = readManifest();
129
- if (!manifest.files) manifest.files = {};
130
- manifest.files[relPath] = {
131
- hash: hashContent(fs.readFileSync(absPath, 'utf8')),
132
- generatedAt: new Date().toISOString(),
133
- generatedByVersion: getForgeVersion(),
134
- };
135
- writeManifest(manifest);
136
- process.stdout.write(`〇 Recorded: ${relPath}\n`);
137
- process.exit(0);
138
- }
139
-
140
- // ── record-all ───────────────────────────────────────────────────────────────
141
-
142
- if (subcmd === 'record-all') {
143
- const manifest = readManifest();
144
- const files = manifest.files || {};
145
- if (Object.keys(files).length === 0) {
146
- process.stdout.write('── No files tracked — nothing to re-hash.\n');
147
- process.exit(0);
148
- }
149
- let updated = 0, missing = 0;
150
- const version = getForgeVersion();
151
- for (const relPath of Object.keys(files)) {
152
- const absPath = path.resolve(relPath);
153
- if (!fs.existsSync(absPath)) {
154
- process.stdout.write(`△ Missing (skipped): ${relPath}\n`);
155
- missing++;
156
- continue;
157
- }
158
- files[relPath] = {
159
- hash: hashContent(fs.readFileSync(absPath, 'utf8')),
160
- generatedAt: new Date().toISOString(),
161
- generatedByVersion: version,
162
- };
163
- updated++;
164
- }
165
- writeManifest(manifest);
166
- process.stdout.write(`〇 Re-hashed ${updated} file(s).${missing ? ` △ ${missing} missing.` : ''}\n`);
167
- process.exit(0);
168
- }
169
-
170
- // ── check ────────────────────────────────────────────────────────────────────
171
-
172
- if (subcmd === 'check') {
173
- const filePath = args[0];
174
- if (!filePath) {
175
- process.stderr.write('Usage: generation-manifest check <path>\n');
176
- process.exit(2);
177
- }
178
- const relPath = toRelPath(filePath);
179
- const manifest = readManifest();
180
- const entry = manifest.files && manifest.files[relPath];
181
-
182
- if (!entry) {
183
- process.stdout.write(`── ${relPath}: untracked\n`);
184
- process.exit(2);
185
- }
186
- const absPath = path.resolve(relPath);
187
- if (!fs.existsSync(absPath)) {
188
- process.stdout.write(`× ${relPath}: file not found\n`);
189
- process.exit(3);
190
- }
191
- const current = hashContent(fs.readFileSync(absPath, 'utf8'));
192
- if (current === entry.hash) {
193
- process.stdout.write(`〇 ${relPath}: pristine\n`);
194
- process.exit(0);
195
- } else {
196
- process.stdout.write(`△ ${relPath}: modified (generated by v${entry.generatedByVersion || '?'})\n`);
197
- process.exit(1);
198
- }
199
- }
200
-
201
- // ── list ─────────────────────────────────────────────────────────────────────
202
-
203
- if (subcmd === 'list') {
204
- const modifiedOnly = args.includes('--modified');
205
- const manifest = readManifest();
206
- const files = manifest.files || {};
207
-
208
- if (Object.keys(files).length === 0) {
209
- process.stdout.write('── No files tracked.\n');
210
- process.exit(0);
211
- }
212
-
213
- const rows = [];
214
- for (const [relPath, entry] of Object.entries(files)) {
215
- const status = fileStatus(relPath, entry);
216
- if (modifiedOnly && status === 'pristine') continue;
217
- rows.push({
218
- symbol: STATUS_SYMBOL[status],
219
- status,
220
- relPath,
221
- version: entry.generatedByVersion || '—',
222
- date: entry.generatedAt ? entry.generatedAt.slice(0, 10) : '—',
223
- });
224
- }
225
-
226
- if (rows.length === 0) {
227
- process.stdout.write('〇 All tracked files are pristine.\n');
228
- process.exit(0);
229
- }
230
-
231
- process.stdout.write('| Status | File | Version | Date |\n');
232
- process.stdout.write('|--------|------|---------|------|\n');
233
- for (const r of rows) {
234
- process.stdout.write(`| ${r.symbol} ${r.status} | \`${r.relPath}\` | ${r.version} | ${r.date} |\n`);
235
- }
236
- process.exit(0);
237
- }
238
-
239
- // ── status ───────────────────────────────────────────────────────────────────
240
-
241
- if (subcmd === 'status') {
242
- const manifest = readManifest();
243
- const files = manifest.files || {};
244
- let pristine = 0, modified = 0, missing = 0;
245
- for (const [relPath, entry] of Object.entries(files)) {
246
- const s = fileStatus(relPath, entry);
247
- if (s === 'pristine') pristine++;
248
- else if (s === 'modified') modified++;
249
- else missing++;
250
- }
251
- const total = pristine + modified + missing;
252
- if (total === 0) {
253
- process.stdout.write('── No files tracked.\n');
254
- process.exit(0);
255
- }
256
- process.stdout.write(`── ${total} file(s) tracked\n`);
257
- if (pristine > 0) process.stdout.write(`〇 ${pristine} pristine\n`);
258
- if (modified > 0) process.stdout.write(`△ ${modified} modified\n`);
259
- if (missing > 0) process.stdout.write(`× ${missing} missing\n`);
260
- process.exit(0);
261
- }
262
-
263
- // ── remove ───────────────────────────────────────────────────────────────────
264
-
265
- if (subcmd === 'remove') {
266
- const filePath = args[0];
267
- if (!filePath) {
268
- process.stderr.write('Usage: generation-manifest remove <path>\n');
269
- process.exit(2);
270
- }
271
- const relPath = toRelPath(filePath);
272
- const manifest = readManifest();
273
- if (!manifest.files || !manifest.files[relPath]) {
274
- process.stderr.write(`× Not tracked: ${relPath}\n`);
275
- process.exit(1);
276
- }
277
- delete manifest.files[relPath];
278
- writeManifest(manifest);
279
- process.stdout.write(`〇 Removed from tracking: ${relPath}\n`);
280
- process.exit(0);
281
- }
282
-
283
- // ── clear-namespace ──────────────────────────────────────────────────────────
284
-
285
- if (subcmd === 'clear-namespace') {
286
- const prefix = args[0];
287
- if (!prefix) {
288
- process.stderr.write('Usage: generation-manifest clear-namespace <prefix>\n');
289
- process.exit(2);
290
- }
291
- const validPrefix = (prefix.startsWith('.forge/') || prefix.startsWith('.claude/')) && prefix.endsWith('/');
292
- if (!validPrefix) {
293
- process.stderr.write('Usage error: prefix must start with .forge/ or .claude/ and end with /\n');
294
- process.exit(2);
295
- }
296
- const manifest = readManifest();
297
- const files = manifest.files || {};
298
- const keys = Object.keys(files).filter(k => k.startsWith(prefix));
299
- if (keys.length === 0) {
300
- process.stdout.write(`── No entries matching ${prefix}\n`);
301
- } else {
302
- for (const k of keys) delete files[k];
303
- writeManifest(manifest);
304
- process.stdout.write(`〇 Cleared ${keys.length} entries matching ${prefix}\n`);
305
- }
306
- process.exit(0);
307
- }
308
-
309
- process.stderr.write(`× Unknown subcommand: ${subcmd}\n`);
310
- process.exit(2);
311
- } // end if (require.main === module)
@@ -1,59 +0,0 @@
1
- 'use strict';
2
-
3
- // Forge shared helper: resolveForgeRoot
4
- // FR-001: 3-tier priority resolution for FORGE_ROOT with actionable error.
5
- //
6
- // Resolution order:
7
- // 1. FORGE_ROOT env var — accepted only if ${FORGE_ROOT}/.claude-plugin/plugin.json exists
8
- // 2. __dirname/.. — the parent of this module's directory (tools/lib -> forge root)
9
- // 3. Actionable error listing all checked paths
10
- //
11
- // This follows the same pattern as project-root.cjs (library module pattern):
12
- // - Export functions, no require.main === module CLI block
13
- // - Throw errors, don't call process.exit
14
-
15
- const fs = require('fs');
16
- const path = require('path');
17
-
18
- /**
19
- * Resolve the Forge plugin root directory.
20
- *
21
- * @param {string} [envForgeRoot] - Value of FORGE_ROOT environment variable.
22
- * If provided and points to a directory containing .claude-plugin/plugin.json,
23
- * it is accepted. If provided but plugin.json is missing, resolution falls
24
- * through to the __dirname fallback. If omitted/undefined, falls through.
25
- * @returns {string} Absolute path to the Forge plugin root.
26
- * @throws {Error} When no valid resolution path is found, with a message listing
27
- * all paths that were checked.
28
- */
29
- function resolveForgeRoot(envForgeRoot) {
30
- const checkedPaths = [];
31
-
32
- // Tier 1: Environment variable — only accepted if plugin.json exists
33
- if (envForgeRoot) {
34
- const pluginJsonPath = path.join(envForgeRoot, '.claude-plugin', 'plugin.json');
35
- checkedPaths.push(pluginJsonPath);
36
- if (fs.existsSync(pluginJsonPath)) {
37
- return envForgeRoot;
38
- }
39
- // Env var provided but doesn't point to a valid plugin root — fall through
40
- }
41
-
42
- // Tier 2: __dirname/../.. — the standard layout (forge/forge/tools/lib/ -> forge/forge/)
43
- // This module lives in tools/lib/, so going up two directories reaches the plugin root.
44
- const fallbackRoot = path.resolve(path.join(__dirname, '..', '..'));
45
- const fallbackPluginJson = path.join(fallbackRoot, '.claude-plugin', 'plugin.json');
46
- checkedPaths.push(fallbackPluginJson);
47
- if (fs.existsSync(fallbackPluginJson)) {
48
- return fallbackRoot;
49
- }
50
-
51
- // Tier 3: Actionable error
52
- throw new Error(
53
- `Cannot resolve Forge plugin root. Checked paths:\n` +
54
- checkedPaths.map(p => ` - ${p}`).join('\n') +
55
- '\nSet FORGE_ROOT to a directory containing .claude-plugin/plugin.json'
56
- );
57
- }
58
-
59
- module.exports = { resolveForgeRoot };
@@ -1,29 +0,0 @@
1
- 'use strict';
2
-
3
- /**
4
- * paths.cjs — Shared path-computation helpers for Forge tools.
5
- *
6
- * Single source of truth for directory-name conventions that must be
7
- * consistent across substitute-placeholders.cjs, check-structure.cjs, etc.
8
- *
9
- * Exported API:
10
- * getCommandsSubdir(prefix) — returns prefix.toLowerCase()
11
- */
12
-
13
- /**
14
- * Compute the commands subdirectory name from a project prefix.
15
- *
16
- * The canonical commands subfolder under .claude/commands/ is derived
17
- * from the project prefix (lowercased), NOT hardcoded as 'forge'.
18
- *
19
- * @param {string} prefix — project prefix (e.g. 'ACME', 'FORGE')
20
- * @returns {string} lowercased prefix (e.g. 'acme', 'forge')
21
- */
22
- function getCommandsSubdir(prefix) {
23
- if (typeof prefix !== 'string' || prefix.length === 0) {
24
- throw new Error('paths.getCommandsSubdir: prefix must be a non-empty string');
25
- }
26
- return prefix.toLowerCase();
27
- }
28
-
29
- module.exports = { getCommandsSubdir };
@@ -1,165 +0,0 @@
1
- 'use strict';
2
-
3
- /**
4
- * pricing.cjs — Shared pricing primitive for Forge cost tooling.
5
- *
6
- * Exports:
7
- * MODEL_PRICING - frozen object mapping canonical model name → { input, output, cacheRead, cacheWrite }
8
- * All rates are in USD per token (not per 1M tokens).
9
- * canonicalizeModel(model) - normalises model name variants/aliases to canonical form.
10
- * Returns { canonical, contextWindow } or null if unknown.
11
- * computeCost(opts) - computes USD cost from { inputTokens, outputTokens,
12
- * cacheReadTokens, cacheWriteTokens, model }.
13
- * Returns number (may be 0), or null for unknown model.
14
- *
15
- * This module is a pure library — no CLI, no process.exit.
16
- * Published Anthropic pricing (USD/MTok → divide by 1,000,000 for per-token rate):
17
- * claude-opus-4-5: input $15.00, output $75.00, cacheRead $1.50, cacheWrite $18.75
18
- * claude-opus-4-7: input $15.00, output $75.00, cacheRead $1.50, cacheWrite $18.75
19
- * claude-sonnet-4-5: input $3.00, output $15.00, cacheRead $0.30, cacheWrite $3.75
20
- * claude-sonnet-4-6: input $3.00, output $15.00, cacheRead $0.30, cacheWrite $3.75
21
- * claude-haiku-3-5: input $0.80, output $4.00, cacheRead $0.08, cacheWrite $1.00
22
- */
23
-
24
- // All rates in USD per token (divide MTok rate by 1,000,000)
25
- const MODEL_PRICING = Object.freeze({
26
- 'claude-opus-4-5': {
27
- input: 15.00 / 1_000_000,
28
- output: 75.00 / 1_000_000,
29
- cacheRead: 1.50 / 1_000_000,
30
- cacheWrite: 18.75 / 1_000_000,
31
- },
32
- 'claude-opus-4-7': {
33
- input: 15.00 / 1_000_000,
34
- output: 75.00 / 1_000_000,
35
- cacheRead: 1.50 / 1_000_000,
36
- cacheWrite: 18.75 / 1_000_000,
37
- },
38
- 'claude-sonnet-4-5': {
39
- input: 3.00 / 1_000_000,
40
- output: 15.00 / 1_000_000,
41
- cacheRead: 0.30 / 1_000_000,
42
- cacheWrite: 3.75 / 1_000_000,
43
- },
44
- 'claude-sonnet-4-6': {
45
- input: 3.00 / 1_000_000,
46
- output: 15.00 / 1_000_000,
47
- cacheRead: 0.30 / 1_000_000,
48
- cacheWrite: 3.75 / 1_000_000,
49
- },
50
- 'claude-haiku-3-5': {
51
- input: 0.80 / 1_000_000,
52
- output: 4.00 / 1_000_000,
53
- cacheRead: 0.08 / 1_000_000,
54
- cacheWrite: 1.00 / 1_000_000,
55
- },
56
- });
57
-
58
- /**
59
- * Known model families for canonical resolution.
60
- * Each entry is [baseCanonical, [...synonymPatterns]]
61
- * Patterns are tested after lower-casing the input model string.
62
- * The order matters: more specific patterns must come before more general ones.
63
- */
64
- const MODEL_FAMILIES = [
65
- // Opus family
66
- 'claude-opus-4-7',
67
- 'claude-opus-4-5',
68
- // Sonnet family
69
- 'claude-sonnet-4-6',
70
- 'claude-sonnet-4-5',
71
- // Haiku family
72
- 'claude-haiku-3-5',
73
- ];
74
-
75
- /**
76
- * canonicalizeModel(model)
77
- *
78
- * Given a raw model string (possibly fragmented / aliased), returns:
79
- * { canonical: string, contextWindow: string|null }
80
- * where:
81
- * - canonical is one of the keys in MODEL_PRICING
82
- * - contextWindow is '1m' when a 1M-context-window variant was detected, otherwise null
83
- *
84
- * Returns null if the model cannot be mapped to a known canonical name.
85
- *
86
- * Examples:
87
- * 'claude-sonnet-4-6' → { canonical: 'claude-sonnet-4-6', contextWindow: null }
88
- * 'claude-sonnet-4-6-1m' → { canonical: 'claude-sonnet-4-6', contextWindow: '1m' }
89
- * 'claude-sonnet-4-6[1m]'→ { canonical: 'claude-sonnet-4-6', contextWindow: '1m' }
90
- * 'sonnet-4-6' → { canonical: 'claude-sonnet-4-6', contextWindow: null }
91
- * 'opus-4-7-1m' → { canonical: 'claude-opus-4-7', contextWindow: '1m' }
92
- * 'totally-unknown-9-9' → null
93
- */
94
- function canonicalizeModel(model) {
95
- if (model == null || typeof model !== 'string' || model.trim() === '') return null;
96
-
97
- const raw = model.trim();
98
-
99
- // Detect and strip 1m context-window suffix variants:
100
- // -1m suffix (e.g. claude-sonnet-4-6-1m)
101
- // [1m] suffix (e.g. claude-sonnet-4-6[1m])
102
- let contextWindow = null;
103
- let stripped = raw;
104
- if (/\[1m\]$/i.test(stripped)) {
105
- contextWindow = '1m';
106
- stripped = stripped.replace(/\[1m\]$/i, '').replace(/-+$/, '');
107
- } else if (/-1m$/i.test(stripped)) {
108
- contextWindow = '1m';
109
- stripped = stripped.replace(/-1m$/i, '');
110
- }
111
-
112
- // Normalise to lowercase for matching
113
- const lower = stripped.toLowerCase();
114
-
115
- for (const canonical of MODEL_FAMILIES) {
116
- // Exact match (after stripping)
117
- if (lower === canonical) {
118
- return { canonical, contextWindow };
119
- }
120
- // Short alias: canonical without leading 'claude-'
121
- const short = canonical.replace(/^claude-/, '');
122
- if (lower === short) {
123
- return { canonical, contextWindow };
124
- }
125
- // Contains the canonical family segment (e.g. 'claude-sonnet-4-6' in a longer variant)
126
- if (lower.includes(canonical)) {
127
- return { canonical, contextWindow };
128
- }
129
- }
130
-
131
- return null;
132
- }
133
-
134
- /**
135
- * computeCost({ inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens, model })
136
- *
137
- * Returns the estimated USD cost as a number, or null if the model is unknown.
138
- * Returns 0 (not null) when all token counts are zero and the model is known.
139
- * Never returns a negative number.
140
- *
141
- * @param {object} opts
142
- * @param {number} opts.inputTokens
143
- * @param {number} opts.outputTokens
144
- * @param {number} opts.cacheReadTokens
145
- * @param {number} opts.cacheWriteTokens
146
- * @param {string} opts.model - raw model string (will be canonicalized)
147
- * @returns {number|null}
148
- */
149
- function computeCost({ inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens, model }) {
150
- const canonResult = canonicalizeModel(model);
151
- if (canonResult === null) return null;
152
-
153
- const rates = MODEL_PRICING[canonResult.canonical];
154
- if (!rates) return null;
155
-
156
- const cost =
157
- (inputTokens || 0) * rates.input +
158
- (outputTokens || 0) * rates.output +
159
- (cacheReadTokens || 0) * rates.cacheRead +
160
- (cacheWriteTokens || 0) * rates.cacheWrite;
161
-
162
- return Math.max(0, cost);
163
- }
164
-
165
- module.exports = { MODEL_PRICING, canonicalizeModel, computeCost };
@@ -1,32 +0,0 @@
1
- 'use strict';
2
-
3
- const fs = require('fs');
4
- const path = require('path');
5
-
6
- /**
7
- * Walk up from startDir to find the project root (directory containing .forge/config.json).
8
- * @param {string} [startDir] - Directory to start searching from. Defaults to process.cwd().
9
- * @returns {string|null} Absolute path to the project root, or null if not found.
10
- */
11
- function findProjectRoot(startDir) {
12
- let dir = path.resolve(startDir || process.cwd());
13
- const root = path.parse(dir).root;
14
-
15
- while (dir !== root) {
16
- const configPath = path.join(dir, '.forge', 'config.json');
17
- if (fs.existsSync(configPath)) {
18
- return dir;
19
- }
20
- dir = path.dirname(dir);
21
- }
22
-
23
- // Check root directory as well
24
- const rootConfigPath = path.join(root, '.forge', 'config.json');
25
- if (fs.existsSync(rootConfigPath)) {
26
- return root;
27
- }
28
-
29
- return null;
30
- }
31
-
32
- module.exports = { findProjectRoot };
@@ -1,40 +0,0 @@
1
- 'use strict';
2
-
3
- // Structured Result helpers for Forge CJS module APIs.
4
- //
5
- // Pattern: every exported function that can fail returns either
6
- // { ok: true, value } — success
7
- // { ok: false, code, message } — failure
8
- //
9
- // Code is always a string from RESULT_CODES, never a magic literal.
10
-
11
- const RESULT_CODES = {
12
- // collate.cjs — resolveTaskDir
13
- MISSING_DIR: 'MISSING_DIR', // no matching task directory found on disk
14
-
15
- // estimate-usage.cjs — estimateTokens
16
- E_ZERO_DURATION: 'E_ZERO_DURATION', // durationMinutes is exactly 0
17
- E_MISSING_DURATION: 'E_MISSING_DURATION', // durationMinutes is null or undefined
18
- };
19
-
20
- /**
21
- * Wrap a success value in a Result.
22
- * @template T
23
- * @param {T} [value]
24
- * @returns {{ ok: true, value: T }}
25
- */
26
- function ok(value) {
27
- return { ok: true, value };
28
- }
29
-
30
- /**
31
- * Wrap an error in a Result.
32
- * @param {string} code — one of RESULT_CODES
33
- * @param {string} message — human-readable explanation
34
- * @returns {{ ok: false, code: string, message: string }}
35
- */
36
- function fail(code, message) {
37
- return { ok: false, code, message };
38
- }
39
-
40
- module.exports = { ok, fail, RESULT_CODES };