@entelligentsia/forgecli 1.0.21 → 1.0.36

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 (218) hide show
  1. package/CHANGELOG.md +346 -0
  2. package/README.md +2 -0
  3. package/dist/CHANGELOG-forge-plugin.md +281 -0
  4. package/dist/bin/argv.d.ts +2 -2
  5. package/dist/bin/argv.js +25 -0
  6. package/dist/bin/argv.js.map +1 -1
  7. package/dist/bin/forge.js +12 -0
  8. package/dist/bin/forge.js.map +1 -1
  9. package/dist/bin/init.d.ts +23 -0
  10. package/dist/bin/init.js +123 -0
  11. package/dist/bin/init.js.map +1 -0
  12. package/dist/bin/uninstall.d.ts +20 -0
  13. package/dist/bin/uninstall.js +141 -0
  14. package/dist/bin/uninstall.js.map +1 -0
  15. package/dist/extensions/forgecli/claude-bootstrap/bootstrap.d.ts +40 -0
  16. package/dist/extensions/forgecli/claude-bootstrap/bootstrap.js +593 -0
  17. package/dist/extensions/forgecli/claude-bootstrap/bootstrap.js.map +1 -0
  18. package/dist/extensions/forgecli/claude-bootstrap/settings-merge.d.ts +46 -0
  19. package/dist/extensions/forgecli/claude-bootstrap/settings-merge.js +245 -0
  20. package/dist/extensions/forgecli/claude-bootstrap/settings-merge.js.map +1 -0
  21. package/dist/extensions/forgecli/claude-bootstrap/uninstall.d.ts +23 -0
  22. package/dist/extensions/forgecli/claude-bootstrap/uninstall.js +215 -0
  23. package/dist/extensions/forgecli/claude-bootstrap/uninstall.js.map +1 -0
  24. package/dist/extensions/forgecli/dashboard/component.js +10 -7
  25. package/dist/extensions/forgecli/dashboard/component.js.map +1 -1
  26. package/dist/extensions/forgecli/forge-tools.d.ts +1 -0
  27. package/dist/extensions/forgecli/forge-tools.js +73 -0
  28. package/dist/extensions/forgecli/forge-tools.js.map +1 -1
  29. package/dist/extensions/forgecli/lib/forge-root.d.ts +5 -0
  30. package/dist/extensions/forgecli/lib/forge-root.js +14 -1
  31. package/dist/extensions/forgecli/lib/forge-root.js.map +1 -1
  32. package/dist/extensions/forgecli/orchestrators/bug/bug-body.d.ts +1 -0
  33. package/dist/extensions/forgecli/orchestrators/bug/bug-body.js +65 -0
  34. package/dist/extensions/forgecli/orchestrators/bug/bug-body.js.map +1 -0
  35. package/dist/extensions/forgecli/orchestrators/bug/bug-id.d.ts +23 -0
  36. package/dist/extensions/forgecli/orchestrators/bug/bug-id.js +140 -0
  37. package/dist/extensions/forgecli/orchestrators/bug/bug-id.js.map +1 -0
  38. package/dist/extensions/forgecli/orchestrators/bug/bug-phase-dispatch.d.ts +54 -0
  39. package/dist/extensions/forgecli/orchestrators/bug/bug-phase-dispatch.js +349 -0
  40. package/dist/extensions/forgecli/orchestrators/bug/bug-phase-dispatch.js.map +1 -0
  41. package/dist/extensions/forgecli/orchestrators/bug/bug-phases.d.ts +8 -0
  42. package/dist/extensions/forgecli/orchestrators/bug/bug-phases.js +60 -0
  43. package/dist/extensions/forgecli/orchestrators/bug/bug-phases.js.map +1 -0
  44. package/dist/extensions/forgecli/orchestrators/bug/bug-state.d.ts +14 -0
  45. package/dist/extensions/forgecli/orchestrators/bug/bug-state.js +100 -0
  46. package/dist/extensions/forgecli/orchestrators/bug/bug-state.js.map +1 -0
  47. package/dist/extensions/forgecli/orchestrators/bug/bug-triage-routing.d.ts +72 -0
  48. package/dist/extensions/forgecli/orchestrators/bug/bug-triage-routing.js +204 -0
  49. package/dist/extensions/forgecli/orchestrators/bug/bug-triage-routing.js.map +1 -0
  50. package/dist/extensions/forgecli/orchestrators/bug/bug-verdict-loop.d.ts +38 -0
  51. package/dist/extensions/forgecli/orchestrators/bug/bug-verdict-loop.js +166 -0
  52. package/dist/extensions/forgecli/orchestrators/bug/bug-verdict-loop.js.map +1 -0
  53. package/dist/extensions/forgecli/orchestrators/bug/bug-verdict.d.ts +3 -0
  54. package/dist/extensions/forgecli/orchestrators/bug/bug-verdict.js +55 -0
  55. package/dist/extensions/forgecli/orchestrators/bug/bug-verdict.js.map +1 -0
  56. package/dist/extensions/forgecli/orchestrators/bug/run-bug-command.d.ts +7 -0
  57. package/dist/extensions/forgecli/orchestrators/bug/run-bug-command.js +293 -0
  58. package/dist/extensions/forgecli/orchestrators/bug/run-bug-command.js.map +1 -0
  59. package/dist/extensions/forgecli/orchestrators/bug/run-bug-pipeline.d.ts +2 -0
  60. package/dist/extensions/forgecli/orchestrators/bug/run-bug-pipeline.js +501 -0
  61. package/dist/extensions/forgecli/orchestrators/bug/run-bug-pipeline.js.map +1 -0
  62. package/dist/extensions/forgecli/orchestrators/bug/run-bug-types.d.ts +41 -0
  63. package/dist/extensions/forgecli/orchestrators/bug/run-bug-types.js +5 -0
  64. package/dist/extensions/forgecli/orchestrators/bug/run-bug-types.js.map +1 -0
  65. package/dist/extensions/forgecli/orchestrators/common/orchestrator-entry.d.ts +43 -0
  66. package/dist/extensions/forgecli/orchestrators/common/orchestrator-entry.js +85 -0
  67. package/dist/extensions/forgecli/orchestrators/common/orchestrator-entry.js.map +1 -0
  68. package/dist/extensions/forgecli/orchestrators/common/orchestrator-misc.d.ts +8 -0
  69. package/dist/extensions/forgecli/orchestrators/common/orchestrator-misc.js +37 -0
  70. package/dist/extensions/forgecli/orchestrators/common/orchestrator-misc.js.map +1 -0
  71. package/dist/extensions/forgecli/orchestrators/common/orchestrator-notify.d.ts +28 -0
  72. package/dist/extensions/forgecli/orchestrators/common/orchestrator-notify.js +45 -0
  73. package/dist/extensions/forgecli/orchestrators/common/orchestrator-notify.js.map +1 -0
  74. package/dist/extensions/forgecli/orchestrators/common/orchestrator-transcript-session.d.ts +26 -0
  75. package/dist/extensions/forgecli/orchestrators/common/orchestrator-transcript-session.js +75 -0
  76. package/dist/extensions/forgecli/orchestrators/common/orchestrator-transcript-session.js.map +1 -0
  77. package/dist/extensions/forgecli/orchestrators/common/summary-recovery.d.ts +24 -0
  78. package/dist/extensions/forgecli/orchestrators/common/summary-recovery.js +37 -0
  79. package/dist/extensions/forgecli/orchestrators/common/summary-recovery.js.map +1 -0
  80. package/dist/extensions/forgecli/orchestrators/fix-bug.d.ts +9 -92
  81. package/dist/extensions/forgecli/orchestrators/fix-bug.js +23 -1695
  82. package/dist/extensions/forgecli/orchestrators/fix-bug.js.map +1 -1
  83. package/dist/extensions/forgecli/orchestrators/run-sprint.d.ts +3 -12
  84. package/dist/extensions/forgecli/orchestrators/run-sprint.js +97 -270
  85. package/dist/extensions/forgecli/orchestrators/run-sprint.js.map +1 -1
  86. package/dist/extensions/forgecli/orchestrators/run-task.d.ts +10 -214
  87. package/dist/extensions/forgecli/orchestrators/run-task.js +31 -1481
  88. package/dist/extensions/forgecli/orchestrators/run-task.js.map +1 -1
  89. package/dist/extensions/forgecli/orchestrators/sprint/sprint-ceremony.d.ts +33 -0
  90. package/dist/extensions/forgecli/orchestrators/sprint/sprint-ceremony.js +135 -0
  91. package/dist/extensions/forgecli/orchestrators/sprint/sprint-ceremony.js.map +1 -0
  92. package/dist/extensions/forgecli/orchestrators/sprint/sprint-state.d.ts +18 -0
  93. package/dist/extensions/forgecli/orchestrators/sprint/sprint-state.js +55 -0
  94. package/dist/extensions/forgecli/orchestrators/sprint/sprint-state.js.map +1 -0
  95. package/dist/extensions/forgecli/orchestrators/task/run-task-command.d.ts +9 -0
  96. package/dist/extensions/forgecli/orchestrators/task/run-task-command.js +174 -0
  97. package/dist/extensions/forgecli/orchestrators/task/run-task-command.js.map +1 -0
  98. package/dist/extensions/forgecli/orchestrators/task/run-task-pipeline.d.ts +2 -0
  99. package/dist/extensions/forgecli/orchestrators/task/run-task-pipeline.js +494 -0
  100. package/dist/extensions/forgecli/orchestrators/task/run-task-pipeline.js.map +1 -0
  101. package/dist/extensions/forgecli/orchestrators/task/run-task-types.d.ts +62 -0
  102. package/dist/extensions/forgecli/orchestrators/task/run-task-types.js +5 -0
  103. package/dist/extensions/forgecli/orchestrators/task/run-task-types.js.map +1 -0
  104. package/dist/extensions/forgecli/orchestrators/task/task-body.d.ts +4 -0
  105. package/dist/extensions/forgecli/orchestrators/task/task-body.js +48 -0
  106. package/dist/extensions/forgecli/orchestrators/task/task-body.js.map +1 -0
  107. package/dist/extensions/forgecli/orchestrators/task/task-events.d.ts +63 -0
  108. package/dist/extensions/forgecli/orchestrators/task/task-events.js +185 -0
  109. package/dist/extensions/forgecli/orchestrators/task/task-events.js.map +1 -0
  110. package/dist/extensions/forgecli/orchestrators/task/task-gates.d.ts +34 -0
  111. package/dist/extensions/forgecli/orchestrators/task/task-gates.js +78 -0
  112. package/dist/extensions/forgecli/orchestrators/task/task-gates.js.map +1 -0
  113. package/dist/extensions/forgecli/orchestrators/task/task-phase-dispatch.d.ts +42 -0
  114. package/dist/extensions/forgecli/orchestrators/task/task-phase-dispatch.js +370 -0
  115. package/dist/extensions/forgecli/orchestrators/task/task-phase-dispatch.js.map +1 -0
  116. package/dist/extensions/forgecli/orchestrators/task/task-phases.d.ts +14 -0
  117. package/dist/extensions/forgecli/orchestrators/task/task-phases.js +26 -0
  118. package/dist/extensions/forgecli/orchestrators/task/task-phases.js.map +1 -0
  119. package/dist/extensions/forgecli/orchestrators/task/task-record.d.ts +9 -0
  120. package/dist/extensions/forgecli/orchestrators/task/task-record.js +58 -0
  121. package/dist/extensions/forgecli/orchestrators/task/task-record.js.map +1 -0
  122. package/dist/extensions/forgecli/orchestrators/task/task-state.d.ts +14 -0
  123. package/dist/extensions/forgecli/orchestrators/task/task-state.js +35 -0
  124. package/dist/extensions/forgecli/orchestrators/task/task-state.js.map +1 -0
  125. package/dist/extensions/forgecli/orchestrators/task/task-verdict-loop.d.ts +36 -0
  126. package/dist/extensions/forgecli/orchestrators/task/task-verdict-loop.js +152 -0
  127. package/dist/extensions/forgecli/orchestrators/task/task-verdict-loop.js.map +1 -0
  128. package/dist/extensions/forgecli/update/forge-update-command.js +10 -7
  129. package/dist/extensions/forgecli/update/forge-update-command.js.map +1 -1
  130. package/dist/forge-payload/.base-pack/commands/approve.md +2 -2
  131. package/dist/forge-payload/.base-pack/commands/check-agent.md +2 -2
  132. package/dist/forge-payload/.base-pack/commands/collate.md +2 -2
  133. package/dist/forge-payload/.base-pack/commands/commit.md +2 -2
  134. package/dist/forge-payload/.base-pack/commands/enhance.md +2 -2
  135. package/dist/forge-payload/.base-pack/commands/fix-bug.md +2 -2
  136. package/dist/forge-payload/.base-pack/commands/implement.md +2 -2
  137. package/dist/forge-payload/.base-pack/commands/init.md +278 -0
  138. package/dist/forge-payload/.base-pack/commands/new-sprint.md +2 -2
  139. package/dist/forge-payload/.base-pack/commands/plan-sprint.md +2 -2
  140. package/dist/forge-payload/.base-pack/commands/plan.md +2 -2
  141. package/dist/forge-payload/.base-pack/commands/retro.md +2 -2
  142. package/dist/forge-payload/.base-pack/commands/review-code.md +2 -2
  143. package/dist/forge-payload/.base-pack/commands/review-plan.md +2 -2
  144. package/dist/forge-payload/.base-pack/commands/run-sprint.md +2 -2
  145. package/dist/forge-payload/.base-pack/commands/run-task.md +2 -2
  146. package/dist/forge-payload/.base-pack/commands/validate.md +2 -2
  147. package/dist/forge-payload/.base-pack/workflows/_fragments/event-emission-schema.md +4 -0
  148. package/dist/forge-payload/.base-pack/workflows/_fragments/event-vocabulary.md +88 -0
  149. package/dist/forge-payload/.base-pack/workflows/collator_agent.md +5 -6
  150. package/dist/forge-payload/.base-pack/workflows/commit_task.md +41 -38
  151. package/dist/forge-payload/.base-pack/workflows/implement_plan.md +3 -3
  152. package/dist/forge-payload/.base-pack/workflows/migrate_structural.md +1 -1
  153. package/dist/forge-payload/.base-pack/workflows-js/wfl-fix-bug.js +42 -6
  154. package/dist/forge-payload/.base-pack/workflows-js/wfl-init.js +449 -0
  155. package/dist/forge-payload/.base-pack/workflows-js/wfl-run-task.js +32 -1
  156. package/dist/forge-payload/.claude-plugin/plugin.json +1 -1
  157. package/dist/forge-payload/.schemas/enum-catalog.json +2 -2
  158. package/dist/forge-payload/.schemas/event.schema.json +8 -3
  159. package/dist/forge-payload/.schemas/migrations.json +141 -0
  160. package/dist/forge-payload/commands/add-pipeline.md +1 -1
  161. package/dist/forge-payload/commands/add-task.md +3 -3
  162. package/dist/forge-payload/commands/ask.md +1 -1
  163. package/dist/forge-payload/commands/check-agent.md +1 -1
  164. package/dist/forge-payload/commands/config.md +1 -1
  165. package/dist/forge-payload/commands/health.md +1 -1
  166. package/dist/forge-payload/commands/init.md +62 -7
  167. package/dist/forge-payload/commands/rebuild.md +3 -3
  168. package/dist/forge-payload/commands/remove.md +1 -1
  169. package/dist/forge-payload/commands/repair.md +1 -1
  170. package/dist/forge-payload/commands/report-bug.md +1 -1
  171. package/dist/forge-payload/commands/status.md +1 -1
  172. package/dist/forge-payload/commands/update.md +3 -3
  173. package/dist/forge-payload/hooks/lib/common.cjs +228 -0
  174. package/dist/forge-payload/hooks/lib/plugin-detection.cjs +106 -0
  175. package/dist/forge-payload/hooks/lib/update-msg.cjs +23 -0
  176. package/dist/forge-payload/hooks/lib/update-url.cjs +46 -0
  177. package/dist/forge-payload/hooks/lib/write-registry.js +53 -0
  178. package/dist/forge-payload/init/discovery/discover-database.md +32 -0
  179. package/dist/forge-payload/init/discovery/discover-processes.md +31 -0
  180. package/dist/forge-payload/init/discovery/discover-routing.md +31 -0
  181. package/dist/forge-payload/init/discovery/discover-stack.md +33 -0
  182. package/dist/forge-payload/init/discovery/discover-testing.md +34 -0
  183. package/dist/forge-payload/init/generation/generate-commands.md +171 -0
  184. package/dist/forge-payload/init/generation/generate-kb-doc.md +60 -0
  185. package/dist/forge-payload/init/generation/generate-knowledge-base.md +56 -0
  186. package/dist/forge-payload/init/generation/generate-persona.md +73 -0
  187. package/dist/forge-payload/init/generation/generate-personas.md +54 -0
  188. package/dist/forge-payload/init/generation/generate-skill.md +66 -0
  189. package/dist/forge-payload/init/generation/generate-skills.md +36 -0
  190. package/dist/forge-payload/init/generation/generate-template.md +60 -0
  191. package/dist/forge-payload/init/generation/generate-templates.md +39 -0
  192. package/dist/forge-payload/init/generation/generate-tools.md +133 -0
  193. package/dist/forge-payload/init/generation/generate-workflows.md +78 -0
  194. package/dist/forge-payload/init/phases/phase-1-collect.md +10 -2
  195. package/dist/forge-payload/init/phases/phase-3-materialize.md +1 -1
  196. package/dist/forge-payload/init/phases/phase-4-register.md +8 -0
  197. package/dist/forge-payload/init/workflow-gen-plan.json +17 -0
  198. package/dist/forge-payload/integrity.json +16 -16
  199. package/dist/forge-payload/meta/store-schema/event.schema.md +7 -0
  200. package/dist/forge-payload/meta/workflows/_fragments/event-emission-schema.md +4 -0
  201. package/dist/forge-payload/meta/workflows/_fragments/event-vocabulary.md +88 -0
  202. package/dist/forge-payload/meta/workflows/meta-collate.md +5 -6
  203. package/dist/forge-payload/meta/workflows/meta-commit.md +46 -43
  204. package/dist/forge-payload/meta/workflows/meta-fix-bug.md +7 -2
  205. package/dist/forge-payload/meta/workflows/meta-implement.md +3 -3
  206. package/dist/forge-payload/meta/workflows/meta-migrate.md +1 -1
  207. package/dist/forge-payload/meta/workflows/meta-orchestrate.md +4 -1
  208. package/dist/forge-payload/schemas/enum-catalog.json +2 -2
  209. package/dist/forge-payload/schemas/event.schema.json +8 -3
  210. package/dist/forge-payload/schemas/structure-manifest.json +5 -12
  211. package/dist/forge-payload/tools/commit-task.cjs +218 -0
  212. package/dist/forge-payload/tools/forge-preflight.cjs +268 -0
  213. package/dist/forge-payload/tools/lib/paths.cjs +12 -11
  214. package/dist/forge-payload/tools/lib/pricing.cjs +31 -11
  215. package/dist/forge-payload/tools/query-logger.cjs +34 -0
  216. package/dist/forge-payload/tools/store-cli.cjs +6 -1
  217. package/dist/forge-payload/tools/substitute-placeholders.cjs +5 -6
  218. package/package.json +2 -2
@@ -0,0 +1,218 @@
1
+ 'use strict';
2
+
3
+ // commit-task.cjs — deterministic commit choreography (forge-engineering#40).
4
+ //
5
+ // The commit phase was the most expensive phase of the SDLC pipeline
6
+ // (15–31% of run input tokens across instrumented runs) because an LLM
7
+ // re-derived deterministic choreography turn-by-turn: gate, state check,
8
+ // staging-set discovery, boundary verification, commit, terminal status
9
+ // transition. This tool owns the whole sequence; the agent supplies only the
10
+ // commit message (the single judgement-worthy step).
11
+ //
12
+ // Staging-set derivation (the commit boundary mirrors the task boundary):
13
+ // 1. record.path — the task/bug artifact directory (always staged)
14
+ // 2. record.summaries.implementation.files_changed — provenance recorded
15
+ // by the implement phase (PHASE_SUMMARY_SCHEMA.files_changed)
16
+ // 3. --also <path> extras (each validated to stay inside the project root)
17
+ //
18
+ // Usage:
19
+ // node commit-task.cjs --task <id> | --bug <id>
20
+ // --message <msg> commit subject/body (required unless --dry-run)
21
+ // [--trailer <line>] optional Co-authored-by trailer
22
+ // [--also <path>] extra path to stage (repeatable)
23
+ // [--skip-gate] skip preflight-gate (orchestrator already ran it)
24
+ // [--force] bypass the status precondition
25
+ // [--dry-run] print the staging plan as JSON; no writes
26
+ //
27
+ // Exit codes: 0 ok | 1 failure (message on stderr).
28
+
29
+ const fs = require('node:fs');
30
+ const path = require('node:path');
31
+ const { spawnSync } = require('node:child_process');
32
+ const { findProjectRoot } = require('./lib/project-root.cjs');
33
+
34
+ const ALLOWED_STATUS = { task: 'approved', bug: 'in-progress' };
35
+ const TERMINAL_STATUS = { task: 'committed', bug: 'fixed' };
36
+
37
+ function fail(msg) {
38
+ console.error(`commit-task: ${msg}`);
39
+ process.exit(1);
40
+ }
41
+
42
+ function parseArgs(argv) {
43
+ const opts = { also: [] };
44
+ for (let i = 0; i < argv.length; i++) {
45
+ const a = argv[i];
46
+ switch (a) {
47
+ case '--task': opts.entityKind = 'task'; opts.recordId = argv[++i]; break;
48
+ case '--bug': opts.entityKind = 'bug'; opts.recordId = argv[++i]; break;
49
+ case '--message': opts.message = argv[++i]; break;
50
+ case '--trailer': opts.trailer = argv[++i]; break;
51
+ case '--also': opts.also.push(argv[++i]); break;
52
+ case '--skip-gate': opts.skipGate = true; break;
53
+ case '--force': opts.force = true; break;
54
+ case '--dry-run': opts.dryRun = true; break;
55
+ default: fail(`unknown argument: ${a}`);
56
+ }
57
+ }
58
+ if (!opts.entityKind || !opts.recordId) {
59
+ fail('usage: commit-task.cjs --task <id> | --bug <id> --message <msg> [--trailer <line>] [--also <path>]... [--skip-gate] [--force] [--dry-run]');
60
+ }
61
+ if (!opts.dryRun && (!opts.message || !opts.message.trim())) {
62
+ fail('--message is required (the commit message is the only input the agent supplies)');
63
+ }
64
+ return opts;
65
+ }
66
+
67
+ function git(root, args, { allowFail = false } = {}) {
68
+ const r = spawnSync('git', args, { cwd: root, encoding: 'utf8' });
69
+ if (r.status !== 0 && !allowFail) {
70
+ fail(`git ${args.join(' ')} failed: ${(r.stderr || r.stdout || '').trim()}`);
71
+ }
72
+ return r;
73
+ }
74
+
75
+ // Resolve a repo-relative path and reject anything escaping the project root.
76
+ function insideRoot(root, p) {
77
+ const abs = path.resolve(root, p);
78
+ const rel = path.relative(root, abs);
79
+ if (rel.startsWith('..') || path.isAbsolute(rel)) return null;
80
+ return rel;
81
+ }
82
+
83
+ function main() {
84
+ const opts = parseArgs(process.argv.slice(2));
85
+ const root = findProjectRoot();
86
+ if (!root) fail('no .forge/config.json found above the current directory');
87
+
88
+ // 1. Record read (store facade — never raw .forge/store writes).
89
+ const store = require('./store.cjs');
90
+ const record = opts.entityKind === 'task' ? store.getTask(opts.recordId) : store.getBug(opts.recordId);
91
+ if (!record) fail(`${opts.entityKind} ${opts.recordId} not found in the store`);
92
+
93
+ // 2. Status precondition (deterministic version of the Pipeline Step Guard).
94
+ const allowed = ALLOWED_STATUS[opts.entityKind];
95
+ if (record.status !== allowed && !opts.force) {
96
+ const hint = opts.entityKind === 'task' ? "; /forge:approve must complete first" : '';
97
+ fail(`× ${opts.recordId} is in state '${record.status}' — commit requires '${allowed}'${hint}. Use --force to bypass (operator-gated, user-invoked re-runs only).`);
98
+ }
99
+
100
+ // 3. Preflight gate (skippable when the orchestrator already ran it).
101
+ if (!opts.skipGate) {
102
+ const gate = spawnSync(process.execPath,
103
+ [path.join(__dirname, 'preflight-gate.cjs'), '--phase', 'commit', `--${opts.entityKind}`, opts.recordId],
104
+ { cwd: root, encoding: 'utf8' });
105
+ if (gate.status !== 0) {
106
+ fail(`preflight gate failed (exit ${gate.status}):\n${(gate.stderr || gate.stdout || '').trim()}`);
107
+ }
108
+ }
109
+
110
+ // 4. Staging set: artifact dir + implementation provenance + --also extras.
111
+ const stage = [];
112
+ const warnings = [];
113
+ if (record.path) {
114
+ const rel = insideRoot(root, record.path);
115
+ if (rel && fs.existsSync(path.join(root, rel))) stage.push(rel);
116
+ else warnings.push(`artifact dir missing on disk: ${record.path}`);
117
+ }
118
+ const provenance = record.summaries?.implementation?.files_changed;
119
+ if (Array.isArray(provenance) && provenance.length > 0) {
120
+ for (const p of provenance) {
121
+ const rel = insideRoot(root, p);
122
+ if (rel === null) { warnings.push(`provenance path outside the project root, skipped: ${p}`); continue; }
123
+ if (!fs.existsSync(path.join(root, rel))) {
124
+ // Deleted files are legitimate changes — git add stages deletions of
125
+ // tracked paths; only warn when git knows nothing about the path.
126
+ const known = git(root, ['ls-files', '--error-unmatch', '--', rel], { allowFail: true });
127
+ if (known.status !== 0) { warnings.push(`provenance path not found (skipped): ${p}`); continue; }
128
+ }
129
+ stage.push(rel);
130
+ }
131
+ } else {
132
+ warnings.push('no files_changed provenance in summaries.implementation — staging the artifact dir only. Pass source files via --also if the task changed code.');
133
+ }
134
+ for (const p of opts.also) {
135
+ const rel = insideRoot(root, p);
136
+ if (rel === null) fail(`--also path outside the project root: ${p}`);
137
+ stage.push(rel);
138
+ }
139
+ let stageSet = [...new Set(stage)];
140
+ if (stageSet.length === 0) fail('nothing to stage — no artifact dir, no provenance, no --also paths');
141
+
142
+ // Pre-filter gitignored paths (live-run finding, forge-engineering#40):
143
+ // `git add` of the whole set is all-or-nothing — one ignored path aborts
144
+ // everything. check-ignore each path; warn-skip the ignored ones.
145
+ const ignored = [];
146
+ for (const rel of stageSet) {
147
+ const ci = git(root, ['check-ignore', '-q', '--', rel], { allowFail: true });
148
+ if (ci.status === 0) ignored.push(rel);
149
+ }
150
+ if (ignored.length > 0) {
151
+ warnings.push(`gitignored path(s) skipped from the staging set: ${ignored.join(', ')} — track them or stage explicitly with git add -f outside this tool.`);
152
+ stageSet = stageSet.filter((p) => !ignored.includes(p));
153
+ }
154
+
155
+ for (const w of warnings) console.error(`commit-task: warning: ${w}`);
156
+
157
+ if (opts.dryRun) {
158
+ process.stdout.write(JSON.stringify({ dryRun: true, entityKind: opts.entityKind, recordId: opts.recordId, stage: stageSet }, null, 2) + '\n');
159
+ return;
160
+ }
161
+
162
+ // 5. Commit-boundary guard: a pre-populated index means someone else's
163
+ // changes would be swept into this commit. Abort loudly (Iron Law:
164
+ // the commit boundary mirrors the task boundary).
165
+ const preStaged = git(root, ['diff', '--cached', '--name-only']).stdout.trim();
166
+ if (preStaged) {
167
+ fail(`index already has staged changes — refusing to sweep them into the ${opts.recordId} commit:\n${preStaged}\nUnstage them (git reset) or commit them separately, then re-run.`);
168
+ }
169
+
170
+ // 6. Terminal status transition helper — through store-cli (single source
171
+ // of truth for transition legality — never reimplemented here).
172
+ const target = TERMINAL_STATUS[opts.entityKind];
173
+ const transition = (context) => {
174
+ const updArgs = [path.join(__dirname, 'store-cli.cjs'), 'update-status', opts.entityKind, opts.recordId, 'status', target];
175
+ if (opts.force) updArgs.push('--force'); // --force bypasses the transition map too (user-invoked re-runs)
176
+ const upd = spawnSync(process.execPath, updArgs, { cwd: root, encoding: 'utf8' });
177
+ if (upd.status !== 0) {
178
+ fail(`${context} but status transition to '${target}' failed:\n${(upd.stderr || upd.stdout || '').trim()}`);
179
+ }
180
+ };
181
+
182
+ // 7. Stage exactly the derived set. A clean staging set is a legitimate
183
+ // terminal state (e.g. a bug whose fix is already at HEAD): no commit,
184
+ // but the record is still sealed (live-run finding, forge-engineering#40).
185
+ const noop = (why) => {
186
+ console.error(`commit-task: ${why} — nothing to commit; sealing the record without a commit.`);
187
+ transition('no-op commit');
188
+ process.stdout.write(JSON.stringify(
189
+ { ok: true, committed: false, reason: 'nothing-to-commit', skippedIgnored: ignored, status: target }, null, 2) + '\n');
190
+ };
191
+ if (stageSet.length === 0) {
192
+ noop('entire staging set is gitignored');
193
+ return;
194
+ }
195
+ git(root, ['add', '--', ...stageSet]);
196
+ const staged = git(root, ['diff', '--cached', '--name-only']).stdout.trim().split('\n').filter(Boolean);
197
+ if (staged.length === 0) {
198
+ noop('working tree already clean for the staging set');
199
+ return;
200
+ }
201
+
202
+ // 8. Commit. Message comes from the agent; the optional trailer is appended
203
+ // after a blank line per git convention.
204
+ const message = opts.trailer ? `${opts.message.trim()}\n\n${opts.trailer.trim()}\n` : `${opts.message.trim()}\n`;
205
+ git(root, ['commit', '-m', message]);
206
+ const sha = git(root, ['rev-parse', 'HEAD']).stdout.trim();
207
+
208
+ transition(`commit ${sha} created`);
209
+
210
+ process.stdout.write(JSON.stringify({ ok: true, committed: true, sha, staged, status: target }, null, 2) + '\n');
211
+ }
212
+
213
+ try {
214
+ main();
215
+ } catch (err) {
216
+ console.error(`commit-task: ${err.message}`);
217
+ process.exit(1);
218
+ }
@@ -0,0 +1,268 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ // forge-preflight.cjs — FORGE-S27-T01 (item A1).
5
+ //
6
+ // Bundles the deterministic pre-dispatch glue the LLM orchestrator currently
7
+ // hand-runs turn-by-turn — FORGE_ROOT resolution, config reconciliation,
8
+ // generation-manifest state, calibration-baseline freshness, MASTER_INDEX
9
+ // hashing, structure check, run timestamp — into ONE compact JSON blob,
10
+ // emitted once. The orchestrator reads this single blob instead of issuing
11
+ // ~20 separate round-trips before the first phase dispatch.
12
+ //
13
+ // Design: a PURE AGGREGATOR. It composes the existing deterministic tools
14
+ // (generation-manifest.cjs's hashContent, check-structure.cjs's
15
+ // checkNamespaces/validateManifest) and reads config — it does NOT reimplement
16
+ // their logic, so there is one source of truth per concern.
17
+ //
18
+ // Contract:
19
+ // - Reads and checks only by default; no implicit side effects (idempotent).
20
+ // - Fast-fail-safe: any probed-concern failure is captured into warnings[]
21
+ // and sets ok:false; the tool NEVER throws uncaught and never half-writes
22
+ // state. It always emits a parseable JSON blob on stdout.
23
+ // - Output is keyed for a freshness/idempotency guard via masterIndexHash +
24
+ // configMtime so callers can skip recompute when the blob is current.
25
+ //
26
+ // Usage:
27
+ // node forge-preflight.cjs [--sprint <id>] [--task <id>] [--bug <id>]
28
+ // [--path <project-root>]
29
+
30
+ // Fast-fail-safe: an uncaught error must still surface as a blob, not a stack
31
+ // trace. We register a last-resort handler that emits ok:false and exits 0
32
+ // (the orchestrator branches on blob.ok, not on the exit code).
33
+ process.on('uncaughtException', (err) => {
34
+ try {
35
+ process.stdout.write(JSON.stringify({
36
+ ok: false,
37
+ forgeRoot: null,
38
+ masterIndexHash: null,
39
+ generatedAt: new Date().toISOString(),
40
+ warnings: [`uncaught: ${err && err.message ? err.message : String(err)}`],
41
+ }) + '\n');
42
+ } catch (_) { /* nothing more we can do */ }
43
+ process.exit(0);
44
+ });
45
+
46
+ const fs = require('fs');
47
+ const path = require('path');
48
+
49
+ // ---------------------------------------------------------------------------
50
+ // Arg parsing (tolerant — unknown flags are ignored, not fatal).
51
+ // ---------------------------------------------------------------------------
52
+ function parseArgs(argv) {
53
+ const out = { projectRoot: process.cwd() };
54
+ for (let i = 0; i < argv.length; i++) {
55
+ const a = argv[i];
56
+ if (a === '--sprint' && argv[i + 1]) { out.sprintId = argv[++i]; }
57
+ else if (a === '--task' && argv[i + 1]) { out.taskId = argv[++i]; }
58
+ else if (a === '--bug' && argv[i + 1]) { out.bugId = argv[++i]; }
59
+ else if (a === '--path' && argv[i + 1]) { out.projectRoot = path.resolve(argv[++i]); }
60
+ }
61
+ return out;
62
+ }
63
+
64
+ // ---------------------------------------------------------------------------
65
+ // Pure helpers. Each concern is wrapped so a single failure becomes a warning
66
+ // rather than aborting the whole preflight.
67
+ // ---------------------------------------------------------------------------
68
+
69
+ // Compose generation-manifest.cjs's hashing primitive — single source of truth
70
+ // for content hashing across the plugin.
71
+ function loadHashContent(forgeRoot) {
72
+ try {
73
+ const mod = require(path.join(forgeRoot, 'tools', 'generation-manifest.cjs'));
74
+ if (mod && typeof mod.hashContent === 'function') return mod.hashContent;
75
+ } catch (_) { /* fall through to local */ }
76
+ // Fallback that matches generation-manifest's algorithm (sha256 hex) so the
77
+ // blob is still useful if the module cannot be required.
78
+ const crypto = require('crypto');
79
+ return (content) => crypto.createHash('sha256').update(content, 'utf8').digest('hex');
80
+ }
81
+
82
+ function readConfig(projectRoot) {
83
+ const configPath = path.join(projectRoot, '.forge', 'config.json');
84
+ const stat = fs.statSync(configPath); // throws if missing -> caught by caller
85
+ const raw = fs.readFileSync(configPath, 'utf8');
86
+ const config = JSON.parse(raw); // throws on malformed -> caught by caller
87
+ return { config, configMtime: stat.mtimeMs };
88
+ }
89
+
90
+ function hashMasterIndex(projectRoot, config, hashContent) {
91
+ const engineering = (config.paths && config.paths.engineering) || 'engineering';
92
+ const indexPath = path.join(projectRoot, engineering, 'MASTER_INDEX.md');
93
+ if (!fs.existsSync(indexPath)) return null;
94
+ return hashContent(fs.readFileSync(indexPath, 'utf8'));
95
+ }
96
+
97
+ // calibrationFresh carries ACTIONABLE detail, not a bare boolean: when stale,
98
+ // it names the remedy and the reason so the orchestrator can act on the blob
99
+ // without re-deriving the signal.
100
+ function assessCalibration(config, masterIndexHash) {
101
+ const baseline = config.calibrationBaseline;
102
+ if (!baseline) {
103
+ return { fresh: false, suggest: '/forge:calibrate', reason: 'no calibrationBaseline in config' };
104
+ }
105
+ if (masterIndexHash && baseline.masterIndexHash && baseline.masterIndexHash !== masterIndexHash) {
106
+ return { fresh: false, suggest: '/forge:calibrate', reason: 'masterIndexHash drift since last calibration' };
107
+ }
108
+ return { fresh: true, lastCalibrated: baseline.lastCalibrated || null };
109
+ }
110
+
111
+ // Compose check-structure.cjs's namespace check. Returns actionable detail.
112
+ function assessStructure(forgeRoot, projectRoot, warnings) {
113
+ try {
114
+ const cs = require(path.join(forgeRoot, 'tools', 'check-structure.cjs'));
115
+ const manifestPath = path.join(forgeRoot, 'schemas', 'structure-manifest.json');
116
+ if (typeof cs.checkNamespaces !== 'function' || !fs.existsSync(manifestPath)) {
117
+ return { ok: null, reason: 'structure check unavailable' };
118
+ }
119
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
120
+ const result = cs.checkNamespaces(manifest, projectRoot, {});
121
+ // checkNamespaces returns a structured result; treat any missing-file
122
+ // namespace as "not ok" but non-fatal (a warning, not a throw).
123
+ const allOk = result && (result.ok === true || result.allPresent === true ||
124
+ (Array.isArray(result.namespaces) && result.namespaces.every((n) => n.missing === 0 || (n.missing && n.missing.length === 0))));
125
+ if (!allOk) warnings.push('structure check reported missing namespace files');
126
+ return { ok: !!allOk };
127
+ } catch (err) {
128
+ warnings.push(`structure check error: ${err.message}`);
129
+ return { ok: null, reason: err.message };
130
+ }
131
+ }
132
+
133
+ // generation-manifest status — actionable manifest state.
134
+ function assessManifest(forgeRoot, projectRoot, warnings) {
135
+ const manifestPath = path.join(projectRoot, '.forge', 'schemas', 'generation-manifest.json');
136
+ // The manifest lives in the generated instance; absence is informational,
137
+ // not an error (e.g. fresh project or fast-mode).
138
+ if (!fs.existsSync(manifestPath)) {
139
+ return { present: false, reason: 'no generation-manifest in instance (informational)' };
140
+ }
141
+ try {
142
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
143
+ const count = manifest.files ? Object.keys(manifest.files).length : 0;
144
+ return { present: true, trackedFiles: count };
145
+ } catch (err) {
146
+ warnings.push(`generation-manifest parse error: ${err.message}`);
147
+ return { present: true, error: err.message };
148
+ }
149
+ }
150
+
151
+ // Resolve FORGE_ROOT via forgeRef-based cache scan — mirrors manage-config.cjs
152
+ // Priority 2 logic (FORGE-S29-T03). paths.forgeRoot is deprecated and no longer read.
153
+ function resolveForgeRootFromConfig(config) {
154
+ const forgeRef = config.paths && config.paths.forgeRef;
155
+ if (!forgeRef) return null;
156
+ const homeDir = require('os').homedir();
157
+ const candidates = [
158
+ path.join(homeDir, '.claude', 'plugins', 'cache', 'forge', 'forge', forgeRef),
159
+ path.join(homeDir, '.claude', 'plugins', 'marketplaces', 'skillforge', 'forge', 'forge', forgeRef),
160
+ ];
161
+ for (const c of candidates) {
162
+ try {
163
+ const pluginJsonPath = path.join(c, '.claude-plugin', 'plugin.json');
164
+ if (fs.existsSync(pluginJsonPath)) {
165
+ const manifest = JSON.parse(fs.readFileSync(pluginJsonPath, 'utf8'));
166
+ if (manifest.version === forgeRef) return c;
167
+ }
168
+ } catch (_) { /* try next candidate */ }
169
+ }
170
+ return null;
171
+ }
172
+
173
+ // ---------------------------------------------------------------------------
174
+ // Main aggregation.
175
+ // ---------------------------------------------------------------------------
176
+ function preflight(opts) {
177
+ const warnings = [];
178
+ const blob = {
179
+ ok: true,
180
+ forgeRoot: null,
181
+ sprintId: opts.sprintId || null,
182
+ configReconciled: false,
183
+ manifestState: null,
184
+ calibrationFresh: null,
185
+ masterIndexHash: null,
186
+ configMtime: null,
187
+ structureOk: null,
188
+ generatedAt: new Date().toISOString(),
189
+ warnings,
190
+ };
191
+
192
+ // 1. Config — the load-bearing input. A failure here is fatal to ok.
193
+ let config;
194
+ try {
195
+ const r = readConfig(opts.projectRoot);
196
+ config = r.config;
197
+ blob.configMtime = r.configMtime;
198
+ blob.configReconciled = true;
199
+ } catch (err) {
200
+ blob.ok = false;
201
+ warnings.push(`config unreadable: ${err.message}`);
202
+ return blob; // fast-fail-safe: cannot proceed without config, but no throw
203
+ }
204
+
205
+ // 2. FORGE_ROOT resolution — mirrors manage-config.cjs Priority 2 (forgeRef cache scan).
206
+ // paths.forgeRoot is deprecated (FORGE-S29-T03); we resolve via forgeRef only.
207
+ // blob.forgeRoot is still emitted for backward-compatible telemetry.
208
+ blob.forgeRoot = resolveForgeRootFromConfig(config);
209
+ if (!blob.forgeRoot) {
210
+ const forgeRef = (config.paths && config.paths.forgeRef) || null;
211
+ const hint = forgeRef
212
+ ? `Cannot resolve Forge plugin root from forgeRef "${forgeRef}" — run /forge:update to refresh.`
213
+ : 'paths.forgeRef missing from config — cannot resolve Forge plugin root.';
214
+ blob.ok = false;
215
+ warnings.push(hint);
216
+ return blob;
217
+ }
218
+
219
+ const hashContent = loadHashContent(blob.forgeRoot);
220
+
221
+ // 3. MASTER_INDEX hash (freshness key + calibration input).
222
+ try {
223
+ blob.masterIndexHash = hashMasterIndex(opts.projectRoot, config, hashContent);
224
+ if (!blob.masterIndexHash) warnings.push('MASTER_INDEX.md absent — hash null');
225
+ } catch (err) {
226
+ warnings.push(`master-index hash error: ${err.message}`);
227
+ }
228
+
229
+ // 4. Calibration freshness (actionable).
230
+ try {
231
+ blob.calibrationFresh = assessCalibration(config, blob.masterIndexHash);
232
+ } catch (err) {
233
+ warnings.push(`calibration assessment error: ${err.message}`);
234
+ }
235
+
236
+ // 5. Manifest state (actionable).
237
+ try {
238
+ blob.manifestState = assessManifest(blob.forgeRoot, opts.projectRoot, warnings);
239
+ } catch (err) {
240
+ warnings.push(`manifest assessment error: ${err.message}`);
241
+ }
242
+
243
+ // 6. Structure check.
244
+ try {
245
+ const s = assessStructure(blob.forgeRoot, opts.projectRoot, warnings);
246
+ blob.structureOk = s.ok;
247
+ } catch (err) {
248
+ warnings.push(`structure assessment error: ${err.message}`);
249
+ }
250
+
251
+ // Aggregate verdict: ok stays true for soft/informational warnings (absent
252
+ // master index, missing optional manifest); it is only flipped false above
253
+ // for hard failures (no config / no forgeRoot). This keeps the orchestrator
254
+ // running on a healthy-but-fresh project while still surfacing advisories.
255
+ return blob;
256
+ }
257
+
258
+ // ---------------------------------------------------------------------------
259
+ // CLI entry.
260
+ // ---------------------------------------------------------------------------
261
+ if (require.main === module) {
262
+ const opts = parseArgs(process.argv.slice(2));
263
+ const blob = preflight(opts);
264
+ process.stdout.write(JSON.stringify(blob) + '\n');
265
+ process.exit(0); // exit code is always 0; callers branch on blob.ok
266
+ }
267
+
268
+ module.exports = { preflight, parseArgs, assessCalibration };
@@ -7,23 +7,24 @@
7
7
  * consistent across substitute-placeholders.cjs, check-structure.cjs, etc.
8
8
  *
9
9
  * Exported API:
10
- * getCommandsSubdir(prefix) — returns prefix.toLowerCase()
10
+ * getCommandsSubdir() — returns 'forge' (fixed namespace)
11
11
  */
12
12
 
13
13
  /**
14
- * Compute the commands subdirectory name from a project prefix.
14
+ * Commands subdirectory name under .claude/commands/.
15
15
  *
16
- * The canonical commands subfolder under .claude/commands/ is derived
17
- * from the project prefix (lowercased), NOT hardcoded as 'forge'.
16
+ * CLI-first redesign: the namespace is FIXED to 'forge'. Project-prefix
17
+ * command namespaces (/acme:*, /hello:*) are retired every project gets
18
+ * the same /forge:* surface, matching what the 4ge bootstrap vendors into
19
+ * .claude/commands/forge/. The prefix-derived namespace existed to avoid
20
+ * collisions with the Forge *plugin's* own /forge:* commands; moot now
21
+ * that the plugin mechanism is retired.
18
22
  *
19
- * @param {string} prefixproject prefix (e.g. 'ACME', 'FORGE')
20
- * @returns {string} lowercased prefix (e.g. 'acme', 'forge')
23
+ * @param {string} [_prefix]vestigial, ignored (kept for caller compat)
24
+ * @returns {string} always 'forge'
21
25
  */
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();
26
+ function getCommandsSubdir(_prefix) {
27
+ return 'forge';
27
28
  }
28
29
 
29
30
  module.exports = { getCommandsSubdir };
@@ -13,9 +13,15 @@
13
13
  * Returns number (may be 0), or null for unknown model.
14
14
  *
15
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
16
+ * Published Anthropic pricing (USD/MTok → divide by 1,000,000 for per-token rate).
17
+ * Verified against Anthropic support + the pi vendor model table on 2026-06-08.
18
+ * cacheWrite uses the 5-minute-TTL rate; Forge phases run inside the 5-min cache
19
+ * window, so the 5-min write rate is the one that bills (1-hour TTL is higher but
20
+ * not used). All Opus 4.x generations share one rate tier:
21
+ * claude-opus-4-5: input $5.00, output $25.00, cacheRead $0.50, cacheWrite $6.25
22
+ * claude-opus-4-6: input $5.00, output $25.00, cacheRead $0.50, cacheWrite $6.25
23
+ * claude-opus-4-7: input $5.00, output $25.00, cacheRead $0.50, cacheWrite $6.25
24
+ * claude-opus-4-8: input $5.00, output $25.00, cacheRead $0.50, cacheWrite $6.25
19
25
  * claude-sonnet-4-5: input $3.00, output $15.00, cacheRead $0.30, cacheWrite $3.75
20
26
  * claude-sonnet-4-6: input $3.00, output $15.00, cacheRead $0.30, cacheWrite $3.75
21
27
  * claude-haiku-3-5: input $0.80, output $4.00, cacheRead $0.08, cacheWrite $1.00
@@ -24,16 +30,28 @@
24
30
  // All rates in USD per token (divide MTok rate by 1,000,000)
25
31
  const MODEL_PRICING = Object.freeze({
26
32
  '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,
33
+ input: 5.00 / 1_000_000,
34
+ output: 25.00 / 1_000_000,
35
+ cacheRead: 0.50 / 1_000_000,
36
+ cacheWrite: 6.25 / 1_000_000,
37
+ },
38
+ 'claude-opus-4-6': {
39
+ input: 5.00 / 1_000_000,
40
+ output: 25.00 / 1_000_000,
41
+ cacheRead: 0.50 / 1_000_000,
42
+ cacheWrite: 6.25 / 1_000_000,
31
43
  },
32
44
  '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,
45
+ input: 5.00 / 1_000_000,
46
+ output: 25.00 / 1_000_000,
47
+ cacheRead: 0.50 / 1_000_000,
48
+ cacheWrite: 6.25 / 1_000_000,
49
+ },
50
+ 'claude-opus-4-8': {
51
+ input: 5.00 / 1_000_000,
52
+ output: 25.00 / 1_000_000,
53
+ cacheRead: 0.50 / 1_000_000,
54
+ cacheWrite: 6.25 / 1_000_000,
37
55
  },
38
56
  'claude-sonnet-4-5': {
39
57
  input: 3.00 / 1_000_000,
@@ -63,7 +81,9 @@ const MODEL_PRICING = Object.freeze({
63
81
  */
64
82
  const MODEL_FAMILIES = [
65
83
  // Opus family
84
+ 'claude-opus-4-8',
66
85
  'claude-opus-4-7',
86
+ 'claude-opus-4-6',
67
87
  'claude-opus-4-5',
68
88
  // Sonnet family
69
89
  'claude-sonnet-4-6',
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+ // query-logger.cjs — PostToolUse hook: logs store-cli query invocations
4
+ // Reads TOOL_INPUT env var, appends entry to .forge/store/query-log.jsonl
5
+
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+
9
+ try {
10
+ const toolInput = process.env.TOOL_INPUT || '';
11
+ if (!/store-cli(?:\.cjs)?["']?\s+query/.test(toolInput)) process.exit(0);
12
+
13
+ const logEntry = {
14
+ timestamp: new Date().toISOString(),
15
+ tool: 'store-cli',
16
+ command: 'query',
17
+ input: toolInput.substring(0, 500),
18
+ };
19
+
20
+ let storeRel = '.forge/store';
21
+ try {
22
+ const cfgPath = path.join(process.cwd(), '.forge', 'config.json');
23
+ if (fs.existsSync(cfgPath)) {
24
+ const cfg = JSON.parse(fs.readFileSync(cfgPath, 'utf8'));
25
+ if (cfg.paths?.store) storeRel = cfg.paths.store;
26
+ }
27
+ } catch {}
28
+
29
+ const logPath = path.join(process.cwd(), storeRel, 'query-log.jsonl');
30
+ fs.mkdirSync(path.dirname(logPath), { recursive: true });
31
+ fs.appendFileSync(logPath, JSON.stringify(logEntry) + '\n');
32
+ } catch {
33
+ // Silent fail — hooks must not crash the session
34
+ }
@@ -77,7 +77,12 @@ const PHASE_SUMMARY_SCHEMA = {
77
77
  verdict: { type: 'string', enum: ['approved', 'revision', 'n/a'] },
78
78
  written_at: { type: 'string' },
79
79
  artifact_ref:{ type: 'string' },
80
- route: { type: 'string', enum: ['A', 'B'] }
80
+ route: { type: 'string', enum: ['A', 'B'] },
81
+ // forge-engineering#40: implement-phase file provenance. The implement
82
+ // workflow records the repo-relative paths it created/modified;
83
+ // commit-task.cjs derives its staging set from this list instead of the
84
+ // LLM re-deriving the change surface from git each run.
85
+ files_changed: { type: 'array', items: { type: 'string', maxLength: 300 }, maxItems: 100 }
81
86
  },
82
87
  additionalProperties: false
83
88
  };
@@ -9,7 +9,7 @@
9
9
  * output to the appropriate output directories.
10
10
  *
11
11
  * Output path mapping (--target claude-code, default):
12
- * base-pack/commands/ → <outRoot>/.claude/commands/<prefix>/
12
+ * base-pack/commands/ → <outRoot>/.claude/commands/forge/ (fixed namespace)
13
13
  * base-pack/personas/ → <outRoot>/.forge/personas/
14
14
  * base-pack/skills/ → <outRoot>/.forge/skills/
15
15
  * base-pack/workflows/ → <outRoot>/.forge/workflows/
@@ -116,8 +116,8 @@ const RUNTIME_PASSTHROUGH_KEYS = new Set([
116
116
 
117
117
  /**
118
118
  * Maps a base-pack subdirectory name to an output directory path relative to
119
- * the project root. The 'commands' entry is computed dynamically from the
120
- * project prefix via getCommandsSubdir() — see walkBasePack.
119
+ * the project root. The 'commands' entry is the fixed 'forge' namespace via
120
+ * getCommandsSubdir() — see walkBasePack.
121
121
  */
122
122
  const SUBDIR_OUTPUT_MAP = {
123
123
  personas: path.join('.forge', 'personas'),
@@ -392,9 +392,8 @@ function renderDeploymentTable(envs) {
392
392
  function walkBasePack(basePack, map, outRoot, dryRun, io, categoryFilter) {
393
393
  const warn = (io && io.warn) || ((msg) => process.stderr.write(msg + '\n'));
394
394
 
395
- // Extract prefix from substitution map for commands path computation
396
- const prefix = map.get('PREFIX') || '';
397
- const commandsSubdir = prefix ? getCommandsSubdir(prefix) : 'forge';
395
+ // Commands namespace is fixed to 'forge' (CLI-first redesign)
396
+ const commandsSubdir = getCommandsSubdir();
398
397
 
399
398
  // Sorted readdir for deterministic idempotent output (Advisory Note 7)
400
399
  const topEntries = fs.readdirSync(basePack).sort();