@codedology/code-forge 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (184) hide show
  1. package/CHANGELOG.md +64 -0
  2. package/LICENSE +21 -0
  3. package/README.md +127 -0
  4. package/bin/code-forge.mjs +145 -0
  5. package/docs/README.md +37 -0
  6. package/docs/reference/blocks.json +288 -0
  7. package/docs/reference/config.md +162 -0
  8. package/examples/first-use/README.md +11 -0
  9. package/examples/first-use/clamp.acceptance.yml +8 -0
  10. package/examples/first-use/clamp.brief.md +16 -0
  11. package/examples/first-use/coder/src/math.mjs +31 -0
  12. package/examples/first-use/coder/test/clamp.test.mjs +26 -0
  13. package/examples/node-lib/.env.example +3 -0
  14. package/examples/node-lib/.gitattributes +3 -0
  15. package/examples/node-lib/.github/workflows/test.yml +11 -0
  16. package/examples/node-lib/CHANGELOG.md +13 -0
  17. package/examples/node-lib/README.md +17 -0
  18. package/examples/node-lib/gitignore +3 -0
  19. package/examples/node-lib/package.json +19 -0
  20. package/examples/node-lib/src/list.mjs +23 -0
  21. package/examples/node-lib/src/math.mjs +21 -0
  22. package/examples/node-lib/src/strings.mjs +34 -0
  23. package/examples/node-lib/test/list.test.mjs +12 -0
  24. package/examples/node-lib/test/math.test.mjs +13 -0
  25. package/examples/node-lib/test/package.test.mjs +18 -0
  26. package/examples/node-lib/test/strings.test.mjs +28 -0
  27. package/package.json +52 -0
  28. package/schema/code-forge.schema.json +331 -0
  29. package/schema/facts.schema.json +28 -0
  30. package/scripts/first-use.mjs +517 -0
  31. package/scripts/gen-config-doc.mjs +241 -0
  32. package/skill/SKILL.md +79 -0
  33. package/skill/references/adapters/claude-code.md +19 -0
  34. package/skill/references/adapters/solo.md +34 -0
  35. package/skill/references/adapters/subprocess.md +15 -0
  36. package/skill/references/code.md +64 -0
  37. package/skill/references/continuity.md +31 -0
  38. package/skill/references/decisions.md +50 -0
  39. package/skill/references/degraded.md +40 -0
  40. package/skill/references/harden.md +26 -0
  41. package/skill/references/ledger.md +23 -0
  42. package/skill/references/plan.md +43 -0
  43. package/skill/references/proof.md +34 -0
  44. package/skill/references/review.md +42 -0
  45. package/skill/references/rules-core.md +72 -0
  46. package/skill/references/security.md +29 -0
  47. package/skill/scripts/forge +40 -0
  48. package/skill/templates/block-record.md +19 -0
  49. package/skill/templates/facts-sheet.md +22 -0
  50. package/skill/templates/plan.md +53 -0
  51. package/src/cli/author.mjs +87 -0
  52. package/src/cli/block.mjs +262 -0
  53. package/src/cli/doctor.mjs +74 -0
  54. package/src/cli/facts.mjs +124 -0
  55. package/src/cli/gates.mjs +280 -0
  56. package/src/cli/help.mjs +48 -0
  57. package/src/cli/init.mjs +14 -0
  58. package/src/cli/jev.mjs +175 -0
  59. package/src/cli/keys.mjs +170 -0
  60. package/src/cli/ledger.mjs +160 -0
  61. package/src/cli/list.mjs +68 -0
  62. package/src/cli/models.mjs +131 -0
  63. package/src/cli/plan.mjs +70 -0
  64. package/src/cli/proof.mjs +316 -0
  65. package/src/cli/remove.mjs +76 -0
  66. package/src/cli/report.mjs +73 -0
  67. package/src/cli/resolve.mjs +79 -0
  68. package/src/cli/review-file.mjs +152 -0
  69. package/src/cli/run.mjs +159 -0
  70. package/src/cli/s2.mjs +93 -0
  71. package/src/cli/spawn.mjs +104 -0
  72. package/src/cli/upgrade.mjs +103 -0
  73. package/src/cli/validate.mjs +108 -0
  74. package/src/cli/version.mjs +18 -0
  75. package/src/cli/worker.mjs +76 -0
  76. package/src/config/defaults/anthropic.mjs +18 -0
  77. package/src/config/defaults/index.mjs +24 -0
  78. package/src/config/defaults/openai.mjs +16 -0
  79. package/src/config/defaults/xai.mjs +18 -0
  80. package/src/config/known-ids.mjs +171 -0
  81. package/src/config/load.mjs +135 -0
  82. package/src/config/migrate.mjs +46 -0
  83. package/src/config/refresh.mjs +276 -0
  84. package/src/config/schema-compile.mjs +211 -0
  85. package/src/config/schema-paths.mjs +84 -0
  86. package/src/config/secret-patterns.mjs +85 -0
  87. package/src/config/validate.mjs +694 -0
  88. package/src/decide/author-loop.mjs +90 -0
  89. package/src/decide/escalation.mjs +262 -0
  90. package/src/decide/fallback-rules.mjs +172 -0
  91. package/src/decide/jev-client.mjs +216 -0
  92. package/src/decide/questions.mjs +161 -0
  93. package/src/decide/thresholds.mjs +199 -0
  94. package/src/doctor/index.mjs +105 -0
  95. package/src/doctor/local.mjs +162 -0
  96. package/src/doctor/probes.mjs +358 -0
  97. package/src/doctor/rows.mjs +51 -0
  98. package/src/doctor/worker-probe.mjs +125 -0
  99. package/src/engines/alias-map.mjs +78 -0
  100. package/src/engines/builders/claude.mjs +224 -0
  101. package/src/engines/builders/codex.mjs +242 -0
  102. package/src/engines/builders/grok.mjs +132 -0
  103. package/src/engines/builders/index.mjs +35 -0
  104. package/src/engines/builders/validate-params.mjs +34 -0
  105. package/src/engines/codex-home.mjs +230 -0
  106. package/src/engines/isolation.mjs +84 -0
  107. package/src/engines/probe.mjs +116 -0
  108. package/src/engines/provider-cli.mjs +24 -0
  109. package/src/engines/render-rules.mjs +41 -0
  110. package/src/engines/sentinel.mjs +133 -0
  111. package/src/engines/usage-parse.mjs +149 -0
  112. package/src/gates/acceptance.mjs +95 -0
  113. package/src/gates/detect.mjs +187 -0
  114. package/src/gates/invariants.mjs +43 -0
  115. package/src/gates/run.mjs +72 -0
  116. package/src/gates/safe-edit.mjs +266 -0
  117. package/src/gates/scope.mjs +154 -0
  118. package/src/gates/secret-scan.mjs +79 -0
  119. package/src/gates/transcript-grep.mjs +27 -0
  120. package/src/install/agent-env.mjs +76 -0
  121. package/src/install/detect.mjs +121 -0
  122. package/src/install/harnesses.mjs +131 -0
  123. package/src/install/link.mjs +463 -0
  124. package/src/install/wizard/answers.mjs +389 -0
  125. package/src/install/wizard/flags.mjs +248 -0
  126. package/src/install/wizard/project-file.mjs +128 -0
  127. package/src/install/wizard/run.mjs +366 -0
  128. package/src/install/wizard/steps.mjs +201 -0
  129. package/src/keys/backends/constants.mjs +7 -0
  130. package/src/keys/backends/file.mjs +99 -0
  131. package/src/keys/backends/keychain.mjs +81 -0
  132. package/src/keys/backends/security-cli.mjs +105 -0
  133. package/src/keys/onepassword.mjs +46 -0
  134. package/src/keys/store.mjs +347 -0
  135. package/src/ledger/calibration.mjs +109 -0
  136. package/src/ledger/export.mjs +52 -0
  137. package/src/ledger/outcome.mjs +397 -0
  138. package/src/ledger/paths.mjs +30 -0
  139. package/src/ledger/prices.mjs +49 -0
  140. package/src/ledger/report.mjs +250 -0
  141. package/src/ledger/write.mjs +279 -0
  142. package/src/proof/export.mjs +358 -0
  143. package/src/proof/lock.mjs +138 -0
  144. package/src/proof/red-green.mjs +519 -0
  145. package/src/proof/red-parse.mjs +103 -0
  146. package/src/proof/tiers.mjs +62 -0
  147. package/src/review/budget.mjs +241 -0
  148. package/src/review/context.mjs +156 -0
  149. package/src/review/engine.mjs +264 -0
  150. package/src/review/finding.schema.json +47 -0
  151. package/src/review/fixloop.mjs +467 -0
  152. package/src/review/gate-check.mjs +249 -0
  153. package/src/review/lenses/A.md +22 -0
  154. package/src/review/lenses/B.md +23 -0
  155. package/src/review/lenses/full.md +23 -0
  156. package/src/review/lenses/judge.md +21 -0
  157. package/src/review/lenses/quick.md +21 -0
  158. package/src/review/lenses/recheck.md +23 -0
  159. package/src/review/packet.mjs +355 -0
  160. package/src/review/triage.mjs +149 -0
  161. package/src/review/validate-review.mjs +70 -0
  162. package/src/session/author.mjs +228 -0
  163. package/src/session/facts.mjs +855 -0
  164. package/src/session/plan-check.mjs +294 -0
  165. package/src/session/s2.mjs +164 -0
  166. package/src/session/spawn.mjs +610 -0
  167. package/src/state/block.mjs +289 -0
  168. package/src/state/cli-args.mjs +67 -0
  169. package/src/state/paths.mjs +92 -0
  170. package/src/state/registry.mjs +189 -0
  171. package/src/state/run.mjs +266 -0
  172. package/src/state/signer.mjs +132 -0
  173. package/src/util/exec.mjs +315 -0
  174. package/src/util/forbidden.mjs +787 -0
  175. package/src/util/git.mjs +381 -0
  176. package/src/util/log.mjs +100 -0
  177. package/src/util/reaper.mjs +300 -0
  178. package/src/util/redact.mjs +212 -0
  179. package/src/util/tmp.mjs +409 -0
  180. package/src/worker/engine.mjs +338 -0
  181. package/src/worker/loop.mjs +392 -0
  182. package/src/worker/queue.mjs +186 -0
  183. package/src/worker/review-state.mjs +145 -0
  184. package/src/worker/ticket.mjs +195 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,64 @@
1
+ # Changelog
2
+
3
+ All notable changes to `@codedology/code-forge` are recorded here. Format loosely follows
4
+ [Keep a Changelog](https://keepachangelog.com/); versions follow semver ahead of a 1.0.0 that
5
+ waits on a second real consumer (plan §10.4, Q17).
6
+
7
+ ## [0.1.0] — first release
8
+
9
+ The `plan` → `harden` → `code` → `review` pipeline for cross-model, parallel, evidence-gated
10
+ feature delivery, as one CLI (`code-forge`) plus one Agent Skill.
11
+
12
+ ### Added
13
+
14
+ - **CLI verbs:** `init`, `doctor`, `run`, `block`, `worker`, `review-file`, `spawn`, `s2`,
15
+ `author`, `facts`, `plan check`, `proof`, `gates`, `jev`, `keys`, `ledger`, `report`, `resolve`,
16
+ `models`, `list`, `remove`, `upgrade`, `validate`, `help`, `version`. Verbs are discovered from
17
+ `src/cli/*.mjs`, so adding one never touches the router.
18
+ - **Setup wizard** (`code-forge init`): nine steps (tools, harnesses, provider matrix, multimodel,
19
+ keys, engine, gates, proof, doctor), every question has a default, `--no-interaction` plus one
20
+ override flag per answer, an agent gets one JSON line, a second run changes nothing.
21
+ - **Decision layer:** System 1 (Jev) answers typed questions in well under a second; System 2 is a
22
+ fresh top-level session woken only when System 1 is unsure; deterministic escalation precedence
23
+ (retry → escalate one level → System 2 hand-off → the top level patches once → stop for a human).
24
+ - **Review engine:** a file-done trigger starts one or more fresh, isolated reviewer sessions
25
+ (adaptive by risk score, or two-provider consensus with a judge); a worker process runs review
26
+ outside the coder's own sandbox so review keeps moving even when the coder has no network; fix
27
+ rounds converge under a bounded rule (round ≥ 2 reviews only the fix hunks, the open finding
28
+ count must strictly shrink every round, four rounds is the cap).
29
+ - **Two-tier proof policy:** `light` and `high` (by risk score, a configured path, or a
30
+ security-sensitive change); a red→green runner proves a new test actually fails before the fix
31
+ and passes after. **No tool-made mutation testing ships in 0.1.0** — see "Removed" below.
32
+ - **Engine adapters** for Claude Code, Codex and Grok (subprocess argv snapshots, forbidden-list
33
+ rendering, closed-book sessions with the packet on stdin); Gemini/Cursor/Copilot get detection
34
+ and skill linking only.
35
+ - **Ledger and report:** an append-only, signed, per-project ledger (`~/.code-forge/ledger/`) and
36
+ a `code-forge report` verb with a terminal view and a `--export` JSON snapshot.
37
+ - **JSON Schema** for `.code-forge.yml` (`schema/code-forge.schema.json`) and a generated reference
38
+ at `docs/reference/config.md` (`node scripts/gen-config-doc.mjs [--check]`).
39
+
40
+ ### Security
41
+
42
+ - **`signer: same-user boundary only`** — signed ledger rows detect tampering by a different user
43
+ or a rewritten history; they are not a defense against an attacker who already runs code as you.
44
+ `doctor` always prints this line so nobody mistakes the signature for more than that.
45
+ - **Keys never reach a coder process.** Provider/Jev keys resolve only inside the worker or the
46
+ orchestrator's own CLI calls (chain: `env:<NAME>` → OS keychain → 1Password, cached briefly →
47
+ ask at setup only); `keys list` never prints a value; every output path is redacted.
48
+ - A **forbidden-command list** blocks a coder from starting its own worker, waiving a review, or
49
+ running other orchestrator-only actions, rendered into each provider's own deny-list mechanism.
50
+
51
+ ### Changed / decided late in the build
52
+
53
+ - **Q16 = cut (2026-09-25):** code-forge ships **no user-facing mutation-testing tool**. Proof for
54
+ both the product and this package's own tests is plain unit/feature tests plus the red→green
55
+ runner — exact assertions on the main behaviour, not "mutation-verified" coverage.
56
+ - **R10:** at most 2 coders run at a time (down from 3) for the rest of the build.
57
+ - **R13:** code-forge never runs against, installs into, or names any of Ricardo's own projects;
58
+ the first real, end-to-end use is a neutral example repository shipped inside the package.
59
+
60
+ ### Removed
61
+
62
+ - `@stryker-mutator/core`, `stryker.config.mjs` and the CI mutation-baseline job — this package
63
+ builds itself with unit and feature tests only (R14); nothing in this repository's own history
64
+ ran mutation testing.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ricardo A. Vargas R.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,127 @@
1
+ # @codedology/code-forge
2
+
3
+ A CLI (`code-forge`) plus one Agent Skill that runs the **plan → harden → code → review** pipeline
4
+ for cross-model, parallel, evidence-gated feature delivery — on Claude Code, Codex, Grok, and Solo.
5
+
6
+ > **Status:** built block by block from a maintainer-only design plan. The CLI, decision layer,
7
+ > review engine, worker, proof policy and installer are landed; the Agent Skill under `skill/` and
8
+ > the shipped example repo under `examples/` land in later blocks (see
9
+ > `docs/reference/blocks.json` for what has landed and what is still pending).
10
+
11
+ ## What ships
12
+
13
+ - A CLI (`code-forge`), installed globally via `npx @codedology/code-forge init` into every detected
14
+ harness (Claude Code, Codex, Grok, …).
15
+ - One Agent Skill (`skill/SKILL.md` + `references/`) that is prose only — every fact (model ids,
16
+ efforts, commands, thresholds, paths) lives in config or in the CLI, never in the skill text.
17
+ - A JSON Schema for `.code-forge.yml` (`schema/code-forge.schema.json`), documented in
18
+ `docs/reference/config.md` (generated — see below).
19
+
20
+ See the skill's `skill/references/` for the full architecture, decision layer, review engine, proof
21
+ policy and security model; `docs/reference/blocks.json` lists the build blocks.
22
+
23
+ ## Install
24
+
25
+ ```bash
26
+ npx @codedology/code-forge init
27
+ ```
28
+
29
+ The wizard asks nine short questions (tools, harnesses, provider matrix, multimodel, keys, engine,
30
+ gates, proof, doctor), every one with a default on Enter. Non-interactively:
31
+
32
+ ```bash
33
+ npx @codedology/code-forge init --no-interaction --no-jev
34
+ ```
35
+
36
+ `--no-jev` skips the System-1 key for a first try; drop it and pass `--jev-ref op://vault/item/field`
37
+ or `--jev-env MY_JEV_KEY` once you have one. A second run of `init` changes nothing it already wrote.
38
+
39
+ ## Quickstart
40
+
41
+ Once `init` has written `.code-forge.yml`, these all run for real:
42
+
43
+ ```bash
44
+ code-forge validate # loads and validates .code-forge.yml
45
+ code-forge resolve L1 # {provider, model, effort, fallback, cli} for a level
46
+ code-forge doctor --quick # config + links + key resolution only
47
+ code-forge list # what is installed where, and whether it still resolves
48
+ code-forge keys list # NAME / SOURCE / BACKEND / EXPIRES — never a value
49
+ code-forge models # the model catalog per provider
50
+ code-forge --help # every verb, one per line
51
+ code-forge version
52
+ ```
53
+
54
+ ## The verbs
55
+
56
+ Verbs are discovered from `src/cli/*.mjs` — adding one never touches the router
57
+ (`bin/code-forge.mjs`), so this list is exactly `code-forge --help`'s output plus each verb's own
58
+ usage line.
59
+
60
+ | Verb | Usage |
61
+ |---|---|
62
+ | `init` | `code-forge init [--no-interaction] [--tools recommended\|current] [--yes-tool <tool>]… [--harness a,b] [-g\|-p] [--copy] [--provider P] [--level Ln=model[:effort][@provider]]… [--refresh-models] [--multimodel on\|off] [--second-provider P] [--jev-ref op://… \| --jev-env NAME \| --no-jev] [--engine auto\|solo\|harness] [--solo-project N] [--gate name=cmd]… [--proof isolation=export\|lock \| high=a,b \| link_dirs=a,b \| copy_untracked=a,b]… [--skip-doctor]` |
63
+ | `doctor` | `code-forge doctor [--quick] [--json] [--cwd <dir>]` |
64
+ | `validate` | `code-forge validate [--file <path>]` |
65
+ | `resolve` | `code-forge resolve <L0\|L1\|L2\|L3>` |
66
+ | `run` | `code-forge run start [--cwd <dir>] [--run <id>] [--engine <e>] [--worker-pid <pid>]` · `run start --reattach --run <id>` · `run status --run <id>` · `run end --run <id>` |
67
+ | `block` | `code-forge block open <id> --run <r> --level L<n> --owned <paths…> --acceptance <file>` · `block attempt\|rebase\|claim\|close\|stop\|waive …` (§4.9; `waive` is human-only) |
68
+ | `worker` | `code-forge worker --run <id> [--cwd <dir>] [--poll-ms <n>] [--once]` — started detached by `run start`, never by a coder |
69
+ | `review-file` | `code-forge review-file <path> --block <id> [--run <id>]` (enqueue) · `review-file --wait <ticket> [--max <seconds>s]` (poll) |
70
+ | `spawn` | `code-forge spawn --level L<n> --role <role> --brief <file> [--schema <file>] [--cwd <dir>] [--run <id>] [--block <id>] [--timeout <s>] [--background]` |
71
+ | `s2` | `code-forge s2 --packet <file.json> [--run <id>] [--block <id>] [--timeout <s>]` |
72
+ | `author` | `code-forge author --job plan\|harden --brief <file> [--facts <file>] [--draft <file>] [--answers <file>] [--out <file>] [--run <id>] [--timeout <s>]` |
73
+ | `facts` | `code-forge facts --brief <file> [--sources <path>…] [--out <file>] [--run <id>] [--timeout <s>]` |
74
+ | `plan` | `code-forge plan check <plan-file> [--facts <sheet>]` |
75
+ | `proof` | `code-forge proof tier --file <path> --risk <0-3> [--security] [--cwd <dir>]` · `proof export\|lock\|unlock\|restore <block> --run <r>` |
76
+ | `gates` | `code-forge gates detect\|run\|secret-scan\|safe-edit\|scope\|acceptance\|transcript-grep --cwd <dir> …` |
77
+ | `jev` | `code-forge jev ask <question-id> --state <file.json> [--cwd <dir>] [--slug <slug>] [--key-ref <ref>]` |
78
+ | `keys` | `code-forge keys list \| set <name> [--op <ref>] \| test <name> [--ref <ref>] \| remove <name>` |
79
+ | `ledger` | `code-forge ledger calibration\|outcome\|tail --slug <slug> …` |
80
+ | `report` | `code-forge report --slug <slug> [--json] [--export <dir>]` |
81
+ | `models` | `code-forge models [--refresh --from-cli-caches]` |
82
+ | `list` | `code-forge list` |
83
+ | `remove` | `code-forge remove [<harness>] [--scope project\|global]` |
84
+ | `upgrade` | `code-forge upgrade [--source <path>]` |
85
+ | `help` / `version` | `code-forge --help` (or no verb) · `code-forge --version` |
86
+
87
+ `run`, `block`, `worker`, `spawn`, `s2`, `author`, `facts` and `review-file` are what the Agent
88
+ Skill's loop calls during a real block; you will not usually type them by hand, but they are plain
89
+ CLI verbs like any other and every one of them is exercised by this package's own test suite.
90
+
91
+ ## Security model
92
+
93
+ - **`signer: same-user boundary only`.** Every review/gate-relevant ledger row is HMAC-signed with
94
+ a per-project key, so tampering by a different user or a rewritten history is detectable.
95
+ `doctor` always prints this line — the signature is not a defense against an attacker who
96
+ already runs code as you, and no part of code-forge claims otherwise.
97
+ - **Keys never reach a coder process.** Provider and Jev keys resolve only inside the worker or the
98
+ orchestrator's own CLI calls (`env:<NAME>` → OS keychain → 1Password, cached briefly → ask at
99
+ setup only). `keys list`/`keys test` never print a value; every stdout/stderr path is redacted.
100
+ - **A forbidden-command list** stops a coder from starting its own worker, waiving a review, or
101
+ running other orchestrator-only actions — rendered into each provider's own deny-list mechanism
102
+ (`--disallowedTools` for Claude, an execpolicy rules file plus prose for Codex).
103
+ - **No mutation testing.** Neither this package's own tests nor the product it ships run mutation
104
+ testing (Ricardo's ruling R14/Q16, 2026-09-25). Proof is plain unit and feature tests with exact
105
+ assertions, plus a red→green runner that proves a new test fails before the fix and passes after.
106
+
107
+ ## Configuration reference
108
+
109
+ `schema/code-forge.schema.json` is the source of truth for `.code-forge.yml`.
110
+ `docs/reference/config.md` is generated from it:
111
+
112
+ ```bash
113
+ node scripts/gen-config-doc.mjs # regenerate docs/reference/config.md
114
+ node scripts/gen-config-doc.mjs --check # exit 0 if it's already up to date, 1 otherwise
115
+ ```
116
+
117
+ ## Development
118
+
119
+ ```bash
120
+ npm install
121
+ npm test # node --import ./test/helpers/isolate.mjs --test 'test/**/*.test.mjs'
122
+ npm run typecheck # tsc --checkJs --noEmit
123
+ ```
124
+
125
+ Node >= 22 required (ruling R7). No build step — the package ships plain ESM `.mjs`, run directly
126
+ by Node. `docs/reference/blocks.json` maps each build block to the files it owns and what it
127
+ depends on, kept in sync with the maintainer's design plan.
@@ -0,0 +1,145 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Verb router. Discovers verbs by `readdir(src/cli)` — a verb is a `.mjs` file's basename.
4
+ * Adding a new verb NEVER requires editing this file (plan §10.2 acceptance 2): drop
5
+ * `src/cli/<verb>.mjs` exporting a default `async function run(args) -> number|void` and it is
6
+ * live. `--help`/`-h` (or no verb at all) and `--version`/`-v` are the only aliases this router
7
+ * knows by name, and only as a UX convenience — both simply dispatch to the `help`/`version`
8
+ * verb modules like any other invocation would.
9
+ */
10
+
11
+ import { realpathSync } from 'node:fs';
12
+ import { readdir } from 'node:fs/promises';
13
+ import path from 'node:path';
14
+ import { fileURLToPath, pathToFileURL } from 'node:url';
15
+ import { redact } from '../src/util/redact.mjs';
16
+
17
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
18
+ const CLI_DIR = path.join(__dirname, '..', 'src', 'cli');
19
+
20
+ const HELP_ALIASES = new Set(['--help', '-h']);
21
+ const VERSION_ALIASES = new Set(['--version', '-v']);
22
+
23
+ /**
24
+ * A verb file's basename must match this. Duplicated (not imported) in `src/cli/help.mjs` —
25
+ * both B0-owned files must stay in sync since there is no shared-util file in B0's scope to hold
26
+ * this once. Rejects `.mjs` (empty verb name) and names with spaces/other characters that would
27
+ * break the `/^ {2}\S+$/` verb-line pattern `help.mjs` prints.
28
+ */
29
+ const VERB_FILE_PATTERN = /^[a-z][a-z0-9-]*\.mjs$/;
30
+
31
+ /**
32
+ * @returns {Promise<string[]>} verb names, sorted, discovered from `src/cli/*.mjs`.
33
+ */
34
+ export async function listVerbs() {
35
+ const entries = await readdir(CLI_DIR, { withFileTypes: true });
36
+ return entries
37
+ .filter((entry) => entry.isFile() && VERB_FILE_PATTERN.test(entry.name))
38
+ .map((entry) => entry.name.slice(0, -'.mjs'.length))
39
+ .sort();
40
+ }
41
+
42
+ /**
43
+ * @param {number} exitCode
44
+ * @returns {boolean}
45
+ */
46
+ function isValidExitCode(exitCode) {
47
+ return Number.isInteger(exitCode) && exitCode >= 0 && exitCode <= 255;
48
+ }
49
+
50
+ /**
51
+ * Point this process's lazy temp root at the project's `tmp.root` (B19, plan §9.6 rule 2): when
52
+ * `cwd` holds a `.code-forge.yml` with a string `tmp.root` and `env` does not name a base already,
53
+ * set `env.CODE_FORGE_TMP_ROOT` so `util/tmp.mjs` (and every child) uses it. A relative `tmp.root`
54
+ * is resolved against the config's directory (`cwd`). A missing or unloadable config changes nothing.
55
+ * @param {{env?: NodeJS.ProcessEnv, cwd?: string}} [opts]
56
+ * @returns {Promise<string | undefined>} the value `env.CODE_FORGE_TMP_ROOT` holds afterwards.
57
+ */
58
+ export async function applyTmpRoot({ env = process.env, cwd = process.cwd() } = {}) {
59
+ const name = 'CODE_FORGE_TMP_ROOT'; // `util/tmp.mjs` TMP_ROOT_ENV
60
+ if (typeof env[name] === 'string' && env[name].length > 0) return env[name];
61
+ let loaded;
62
+ try {
63
+ // imported here, not statically: the router must still start when the config loader is absent
64
+ const { loadProjectConfig } = await import('../src/config/load.mjs');
65
+ loaded = await loadProjectConfig(cwd);
66
+ } catch {
67
+ return env[name];
68
+ }
69
+ const root = loaded.ok ? loaded.config?.tmp?.root : undefined;
70
+ if (typeof root === 'string' && root.length > 0) env[name] = path.resolve(cwd, root);
71
+ return env[name];
72
+ }
73
+
74
+ /**
75
+ * @param {string[]} argv - e.g. `process.argv.slice(2)`.
76
+ * @returns {Promise<number>} the process exit code.
77
+ */
78
+ export async function run(argv) {
79
+ const [first, ...rest] = argv;
80
+ const verbs = await listVerbs();
81
+
82
+ let verb = first;
83
+ if (verb === undefined || HELP_ALIASES.has(verb)) {
84
+ verb = 'help';
85
+ } else if (VERSION_ALIASES.has(verb)) {
86
+ verb = 'version';
87
+ }
88
+
89
+ if (!verbs.includes(verb)) {
90
+ process.stderr.write(
91
+ redact(`code-forge: unknown verb "${verb}"\nVerbs:\n${verbs.map((v) => ` ${v}`).join('\n')}\n`),
92
+ );
93
+ return 1;
94
+ }
95
+
96
+ const modulePath = path.join(CLI_DIR, `${verb}.mjs`);
97
+
98
+ let exitCode;
99
+ try {
100
+ const mod = await import(pathToFileURL(modulePath).href);
101
+ const handler = mod.default;
102
+ if (typeof handler !== 'function') {
103
+ process.stderr.write(redact(`code-forge: verb "${verb}" has no default export function\n`));
104
+ return 1;
105
+ }
106
+ exitCode = await handler(rest, { verbs, verb });
107
+ } catch (err) {
108
+ // A verb's error can carry an argv/env token (a fake key in a test, a real one in
109
+ // production); every stderr path here is redacted, this one included.
110
+ process.stderr.write(redact(`code-forge: verb "${verb}" failed: ${err?.stack ?? String(err)}\n`));
111
+ return 1;
112
+ }
113
+
114
+ if (exitCode === undefined || exitCode === null) {
115
+ return 0;
116
+ }
117
+ return isValidExitCode(exitCode) ? exitCode : 1;
118
+ }
119
+
120
+ /**
121
+ * Resolve `argv[1]` to the real path `import.meta.url` would report for the same file, so the
122
+ * comparison survives (a) Node resolving symlinks for the main module — `npm`/`npx .` bin shims
123
+ * under `node_modules/.bin` are symlinks, and macOS temp dirs are `/var` → realpath `/private/var`
124
+ * — and (b) `import.meta.url` being percent-encoded while `argv[1]` is a raw path (spaces, `#`,
125
+ * `%`). Falls back to `false` (never treated as main) if `argv[1]` doesn't resolve to a real file.
126
+ * @returns {boolean}
127
+ */
128
+ function computeIsMain() {
129
+ if (!process.argv[1]) {
130
+ return false;
131
+ }
132
+ try {
133
+ const realArgvPath = realpathSync(process.argv[1]);
134
+ return import.meta.url === pathToFileURL(realArgvPath).href;
135
+ } catch {
136
+ return false;
137
+ }
138
+ }
139
+
140
+ const isMain = computeIsMain();
141
+ if (isMain) {
142
+ await applyTmpRoot();
143
+ const code = await run(process.argv.slice(2));
144
+ process.exitCode = code;
145
+ }
package/docs/README.md ADDED
@@ -0,0 +1,37 @@
1
+ # code-forge documentation
2
+
3
+ These pages explain what `@codedology/code-forge` is, how it works, and how to run it for the first
4
+ time. Every command they show exists in the CLI as built. When a page and a verb's own usage line
5
+ disagree, the verb is right: each verb prints its own usage line when its arguments are wrong.
6
+
7
+ ## Reading order
8
+
9
+ | # | Page | Read it when you want to… |
10
+ |---|---|---|
11
+ | 1 | [overview.md](overview.md) | know what code-forge is, the problem it solves, who it is for, and what it is not |
12
+ | 2 | [concepts.md](concepts.md) | look up a word: block, level, lane, tier, S1/S2, waiver, ledger… |
13
+ | 3 | [how-it-works.md](how-it-works.md) | follow the full workflow from a brief to a closed block, and see who does what and where state lives |
14
+ | 4 | [getting-started.md](getting-started.md) | install it, answer `init`, run `doctor`, and try the first commands |
15
+ | 5 | [tutorial-first-test.md](tutorial-first-test.md) | run one real end-to-end test: build the code-forge marketing site in a new repo |
16
+ | 6 | [examples/marketing-site/brief.md](examples/marketing-site/brief.md) | copy the brief the tutorial uses |
17
+ | 7 | [marketing.md](marketing.md) | read the product story: the problem, the promise, the honest limits |
18
+
19
+ New to the tool? Read 1 → 3 → 4, then do 5. Keep 2 open as a glossary.
20
+
21
+ ## Reference pages (already in the repo)
22
+
23
+ | Page | What it is |
24
+ |---|---|
25
+ | [reference/config.md](reference/config.md) | every `.code-forge.yml` key, its type and default. Generated from `schema/code-forge.schema.json` |
26
+ | [reference/blocks.json](reference/blocks.json) | the build blocks of this package and what each one owns |
27
+ | [marketing/code-forge-story.md](marketing/code-forge-story.md) | the marketing content bank (copy, lessons, what not to claim) |
28
+ | [`skill/SKILL.md`](../skill/SKILL.md) and [`skill/references/`](../skill/references/) | the Agent Skill the orchestrator follows. The deepest and most exact description of the rules |
29
+
30
+ ## Conventions in these pages
31
+
32
+ - `code-forge <verb>` is the CLI installed globally with `npm install -g @codedology/code-forge`
33
+ (see [getting-started.md](getting-started.md#install); a per-project pinned install is covered
34
+ there too). Inside the skill the same CLI is called `forge` (a shim at `skill/scripts/forge`).
35
+ - `<run>` is a run id, `<slug>` is the project slug (`project.slug` in `.code-forge.yml`, else the
36
+ folder name), `<id>` is a block id such as `B1`.
37
+ - "Not yet" marks something that is designed but not built. Nothing else on these pages is a plan.