@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.
- package/CHANGELOG.md +64 -0
- package/LICENSE +21 -0
- package/README.md +127 -0
- package/bin/code-forge.mjs +145 -0
- package/docs/README.md +37 -0
- package/docs/reference/blocks.json +288 -0
- package/docs/reference/config.md +162 -0
- package/examples/first-use/README.md +11 -0
- package/examples/first-use/clamp.acceptance.yml +8 -0
- package/examples/first-use/clamp.brief.md +16 -0
- package/examples/first-use/coder/src/math.mjs +31 -0
- package/examples/first-use/coder/test/clamp.test.mjs +26 -0
- package/examples/node-lib/.env.example +3 -0
- package/examples/node-lib/.gitattributes +3 -0
- package/examples/node-lib/.github/workflows/test.yml +11 -0
- package/examples/node-lib/CHANGELOG.md +13 -0
- package/examples/node-lib/README.md +17 -0
- package/examples/node-lib/gitignore +3 -0
- package/examples/node-lib/package.json +19 -0
- package/examples/node-lib/src/list.mjs +23 -0
- package/examples/node-lib/src/math.mjs +21 -0
- package/examples/node-lib/src/strings.mjs +34 -0
- package/examples/node-lib/test/list.test.mjs +12 -0
- package/examples/node-lib/test/math.test.mjs +13 -0
- package/examples/node-lib/test/package.test.mjs +18 -0
- package/examples/node-lib/test/strings.test.mjs +28 -0
- package/package.json +52 -0
- package/schema/code-forge.schema.json +331 -0
- package/schema/facts.schema.json +28 -0
- package/scripts/first-use.mjs +517 -0
- package/scripts/gen-config-doc.mjs +241 -0
- package/skill/SKILL.md +79 -0
- package/skill/references/adapters/claude-code.md +19 -0
- package/skill/references/adapters/solo.md +34 -0
- package/skill/references/adapters/subprocess.md +15 -0
- package/skill/references/code.md +64 -0
- package/skill/references/continuity.md +31 -0
- package/skill/references/decisions.md +50 -0
- package/skill/references/degraded.md +40 -0
- package/skill/references/harden.md +26 -0
- package/skill/references/ledger.md +23 -0
- package/skill/references/plan.md +43 -0
- package/skill/references/proof.md +34 -0
- package/skill/references/review.md +42 -0
- package/skill/references/rules-core.md +72 -0
- package/skill/references/security.md +29 -0
- package/skill/scripts/forge +40 -0
- package/skill/templates/block-record.md +19 -0
- package/skill/templates/facts-sheet.md +22 -0
- package/skill/templates/plan.md +53 -0
- package/src/cli/author.mjs +87 -0
- package/src/cli/block.mjs +262 -0
- package/src/cli/doctor.mjs +74 -0
- package/src/cli/facts.mjs +124 -0
- package/src/cli/gates.mjs +280 -0
- package/src/cli/help.mjs +48 -0
- package/src/cli/init.mjs +14 -0
- package/src/cli/jev.mjs +175 -0
- package/src/cli/keys.mjs +170 -0
- package/src/cli/ledger.mjs +160 -0
- package/src/cli/list.mjs +68 -0
- package/src/cli/models.mjs +131 -0
- package/src/cli/plan.mjs +70 -0
- package/src/cli/proof.mjs +316 -0
- package/src/cli/remove.mjs +76 -0
- package/src/cli/report.mjs +73 -0
- package/src/cli/resolve.mjs +79 -0
- package/src/cli/review-file.mjs +152 -0
- package/src/cli/run.mjs +159 -0
- package/src/cli/s2.mjs +93 -0
- package/src/cli/spawn.mjs +104 -0
- package/src/cli/upgrade.mjs +103 -0
- package/src/cli/validate.mjs +108 -0
- package/src/cli/version.mjs +18 -0
- package/src/cli/worker.mjs +76 -0
- package/src/config/defaults/anthropic.mjs +18 -0
- package/src/config/defaults/index.mjs +24 -0
- package/src/config/defaults/openai.mjs +16 -0
- package/src/config/defaults/xai.mjs +18 -0
- package/src/config/known-ids.mjs +171 -0
- package/src/config/load.mjs +135 -0
- package/src/config/migrate.mjs +46 -0
- package/src/config/refresh.mjs +276 -0
- package/src/config/schema-compile.mjs +211 -0
- package/src/config/schema-paths.mjs +84 -0
- package/src/config/secret-patterns.mjs +85 -0
- package/src/config/validate.mjs +694 -0
- package/src/decide/author-loop.mjs +90 -0
- package/src/decide/escalation.mjs +262 -0
- package/src/decide/fallback-rules.mjs +172 -0
- package/src/decide/jev-client.mjs +216 -0
- package/src/decide/questions.mjs +161 -0
- package/src/decide/thresholds.mjs +199 -0
- package/src/doctor/index.mjs +105 -0
- package/src/doctor/local.mjs +162 -0
- package/src/doctor/probes.mjs +358 -0
- package/src/doctor/rows.mjs +51 -0
- package/src/doctor/worker-probe.mjs +125 -0
- package/src/engines/alias-map.mjs +78 -0
- package/src/engines/builders/claude.mjs +224 -0
- package/src/engines/builders/codex.mjs +242 -0
- package/src/engines/builders/grok.mjs +132 -0
- package/src/engines/builders/index.mjs +35 -0
- package/src/engines/builders/validate-params.mjs +34 -0
- package/src/engines/codex-home.mjs +230 -0
- package/src/engines/isolation.mjs +84 -0
- package/src/engines/probe.mjs +116 -0
- package/src/engines/provider-cli.mjs +24 -0
- package/src/engines/render-rules.mjs +41 -0
- package/src/engines/sentinel.mjs +133 -0
- package/src/engines/usage-parse.mjs +149 -0
- package/src/gates/acceptance.mjs +95 -0
- package/src/gates/detect.mjs +187 -0
- package/src/gates/invariants.mjs +43 -0
- package/src/gates/run.mjs +72 -0
- package/src/gates/safe-edit.mjs +266 -0
- package/src/gates/scope.mjs +154 -0
- package/src/gates/secret-scan.mjs +79 -0
- package/src/gates/transcript-grep.mjs +27 -0
- package/src/install/agent-env.mjs +76 -0
- package/src/install/detect.mjs +121 -0
- package/src/install/harnesses.mjs +131 -0
- package/src/install/link.mjs +463 -0
- package/src/install/wizard/answers.mjs +389 -0
- package/src/install/wizard/flags.mjs +248 -0
- package/src/install/wizard/project-file.mjs +128 -0
- package/src/install/wizard/run.mjs +366 -0
- package/src/install/wizard/steps.mjs +201 -0
- package/src/keys/backends/constants.mjs +7 -0
- package/src/keys/backends/file.mjs +99 -0
- package/src/keys/backends/keychain.mjs +81 -0
- package/src/keys/backends/security-cli.mjs +105 -0
- package/src/keys/onepassword.mjs +46 -0
- package/src/keys/store.mjs +347 -0
- package/src/ledger/calibration.mjs +109 -0
- package/src/ledger/export.mjs +52 -0
- package/src/ledger/outcome.mjs +397 -0
- package/src/ledger/paths.mjs +30 -0
- package/src/ledger/prices.mjs +49 -0
- package/src/ledger/report.mjs +250 -0
- package/src/ledger/write.mjs +279 -0
- package/src/proof/export.mjs +358 -0
- package/src/proof/lock.mjs +138 -0
- package/src/proof/red-green.mjs +519 -0
- package/src/proof/red-parse.mjs +103 -0
- package/src/proof/tiers.mjs +62 -0
- package/src/review/budget.mjs +241 -0
- package/src/review/context.mjs +156 -0
- package/src/review/engine.mjs +264 -0
- package/src/review/finding.schema.json +47 -0
- package/src/review/fixloop.mjs +467 -0
- package/src/review/gate-check.mjs +249 -0
- package/src/review/lenses/A.md +22 -0
- package/src/review/lenses/B.md +23 -0
- package/src/review/lenses/full.md +23 -0
- package/src/review/lenses/judge.md +21 -0
- package/src/review/lenses/quick.md +21 -0
- package/src/review/lenses/recheck.md +23 -0
- package/src/review/packet.mjs +355 -0
- package/src/review/triage.mjs +149 -0
- package/src/review/validate-review.mjs +70 -0
- package/src/session/author.mjs +228 -0
- package/src/session/facts.mjs +855 -0
- package/src/session/plan-check.mjs +294 -0
- package/src/session/s2.mjs +164 -0
- package/src/session/spawn.mjs +610 -0
- package/src/state/block.mjs +289 -0
- package/src/state/cli-args.mjs +67 -0
- package/src/state/paths.mjs +92 -0
- package/src/state/registry.mjs +189 -0
- package/src/state/run.mjs +266 -0
- package/src/state/signer.mjs +132 -0
- package/src/util/exec.mjs +315 -0
- package/src/util/forbidden.mjs +787 -0
- package/src/util/git.mjs +381 -0
- package/src/util/log.mjs +100 -0
- package/src/util/reaper.mjs +300 -0
- package/src/util/redact.mjs +212 -0
- package/src/util/tmp.mjs +409 -0
- package/src/worker/engine.mjs +338 -0
- package/src/worker/loop.mjs +392 -0
- package/src/worker/queue.mjs +186 -0
- package/src/worker/review-state.mjs +145 -0
- 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.
|