@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
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$comment": "hand-maintained; checked against plan §10.4 + NIGHT-LOG by test/docs-blocks.test.mjs. Ids = every `| **Bxx** |` row in plans/code-forge-plan-v1.3.md §10.4 Waves 2-6 (19 ids), plus the blocks the root orchestrator added during the night run of 2026-09-25 that are not in that table (plans/NIGHT-LOG.md): B1.2, B4.1, B11.1, B12c (B3.1 was already in the v1.3 §10.4 table). Wave 0-1 blocks (B0-B8) are the pre-v1.3 baseline landed before this plan revision; they have no entry under blocks and are listed in baseline_ids so every depends_on id resolves; see plans/code-forge-plan-v1.3.md §10.2-§10.3 for their record. B10c is listed with deleted:true — the maintainer answered Q16 'cut' on 2026-09-25, so B10c (consumer-facing mutation-testing tools) never lands.",
|
|
3
|
+
"checked_against": [
|
|
4
|
+
"plans/code-forge-plan-v1.3.md §10.4",
|
|
5
|
+
"plans/NIGHT-LOG.md"
|
|
6
|
+
],
|
|
7
|
+
"block_count": 23,
|
|
8
|
+
"baseline_ids": ["B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8"],
|
|
9
|
+
"baseline_note": "Wave 0-1 blocks, landed before v1.3; depends_on may name them, but they have no entry under blocks.",
|
|
10
|
+
"blocks": {
|
|
11
|
+
"B0.1": {
|
|
12
|
+
"title": "B0 follow-up: exec stdin input + pid registry, git.mjs GIT_* strip, per-run temp root, orphan reaper, test preload, Stryker removal (R14)",
|
|
13
|
+
"wave": 2,
|
|
14
|
+
"level": "L2",
|
|
15
|
+
"status": "landed",
|
|
16
|
+
"commit": "cb54864",
|
|
17
|
+
"owned": [
|
|
18
|
+
"src/util/exec.mjs", "src/util/git.mjs", "src/util/tmp.mjs", "src/util/reaper.mjs",
|
|
19
|
+
"test/util/exec.test.mjs", "test/util/git.test.mjs", "test/util/tmp.test.mjs", "test/util/reaper.test.mjs",
|
|
20
|
+
"test/helpers/isolate.mjs", "test/harness-isolation.test.mjs", "test/no-leak.test.mjs", "test/router.test.mjs",
|
|
21
|
+
"package.json", "npm-shrinkwrap.json", ".gitignore", ".github/workflows/ci.yml", "stryker.config.mjs (deleted)"
|
|
22
|
+
],
|
|
23
|
+
"depends_on": ["B0"]
|
|
24
|
+
},
|
|
25
|
+
"B1.1": {
|
|
26
|
+
"title": "Schema amendment: the six v1.3 keys, caps.coders 2, two refusals + one warning",
|
|
27
|
+
"wave": 2,
|
|
28
|
+
"level": "L1",
|
|
29
|
+
"status": "landed",
|
|
30
|
+
"commit": "1cc1de4",
|
|
31
|
+
"owned": [
|
|
32
|
+
"schema/code-forge.schema.json", "src/config/validate.mjs", "src/config/schema-paths.mjs",
|
|
33
|
+
"src/config/defaults/anthropic.mjs", "src/config/defaults/openai.mjs", "src/config/defaults/xai.mjs", "src/config/defaults/index.mjs",
|
|
34
|
+
"test/config/schema.test.mjs", "test/config/schema-paths.test.mjs", "test/config/validate.test.mjs", "test/config/defaults.test.mjs",
|
|
35
|
+
"test/fixtures/config/valid-full.code-forge.yml"
|
|
36
|
+
],
|
|
37
|
+
"depends_on": ["B1"]
|
|
38
|
+
},
|
|
39
|
+
"B1.2": {
|
|
40
|
+
"title": "Schema cut (Q16 = cut): remove mutation-tool proof keys (proof.tiers.high.tool/min_msi, proof.js, proof.nightly)",
|
|
41
|
+
"wave": 2,
|
|
42
|
+
"level": "L1",
|
|
43
|
+
"status": "landed",
|
|
44
|
+
"commit": "1749768",
|
|
45
|
+
"owned": [
|
|
46
|
+
"schema/code-forge.schema.json", "src/config/validate.mjs",
|
|
47
|
+
"test/config/schema-coverage.test.mjs", "test/config/schema.test.mjs", "test/config/secret-leak-cases.mjs", "test/config/validate.test.mjs"
|
|
48
|
+
],
|
|
49
|
+
"depends_on": ["B1.1"],
|
|
50
|
+
"note": "root-added (night run), not in the v1.3 §10.4 table; landed the Q16 = cut ruling in the schema"
|
|
51
|
+
},
|
|
52
|
+
"B10a": {
|
|
53
|
+
"title": "Proof: two tiers, measurement export (hardened, C5), proof lock, proof verb (baseline, restore, lock)",
|
|
54
|
+
"wave": 2,
|
|
55
|
+
"level": "L2",
|
|
56
|
+
"status": "landed",
|
|
57
|
+
"commit": "eb969fb",
|
|
58
|
+
"owned": [
|
|
59
|
+
"src/proof/tiers.mjs", "src/proof/export.mjs", "src/proof/lock.mjs", "src/cli/proof.mjs",
|
|
60
|
+
"test/proof/tiers.test.mjs", "test/proof/export.test.mjs", "test/proof/lock.test.mjs", "test/proof/helpers.mjs",
|
|
61
|
+
"test/fixtures/repos/export-ignore/**"
|
|
62
|
+
],
|
|
63
|
+
"depends_on": ["B3", "B5", "B6", "B8"]
|
|
64
|
+
},
|
|
65
|
+
"B9a": {
|
|
66
|
+
"title": "Isolated sessions: spawn verb (stdin piping V3, outPath, background, pid file + registry, logs, usage, retry ladder V9), s2",
|
|
67
|
+
"wave": 2,
|
|
68
|
+
"level": "L2",
|
|
69
|
+
"status": "landed",
|
|
70
|
+
"commit": "fc75988",
|
|
71
|
+
"owned": [
|
|
72
|
+
"src/session/spawn.mjs", "src/session/s2.mjs", "src/cli/spawn.mjs", "src/cli/s2.mjs",
|
|
73
|
+
"test/session/spawn.test.mjs", "test/session/s2.test.mjs", "test/session/helpers.mjs", "test/fixtures/bin/**"
|
|
74
|
+
],
|
|
75
|
+
"depends_on": ["B0.1", "B1", "B4", "B6", "B8"]
|
|
76
|
+
},
|
|
77
|
+
"B2.1": {
|
|
78
|
+
"title": "Keys follow-up: security backend writes through stdin",
|
|
79
|
+
"wave": 2,
|
|
80
|
+
"level": "L2",
|
|
81
|
+
"status": "landed",
|
|
82
|
+
"commit": "1e58b0b",
|
|
83
|
+
"owned": [
|
|
84
|
+
"src/keys/backends/security-cli.mjs", "test/keys/backends.test.mjs"
|
|
85
|
+
],
|
|
86
|
+
"depends_on": ["B0.1", "B2"]
|
|
87
|
+
},
|
|
88
|
+
"B10b": {
|
|
89
|
+
"title": "Proof: journaled red->green runner, red-parse (O13)",
|
|
90
|
+
"wave": 2,
|
|
91
|
+
"level": "L2",
|
|
92
|
+
"status": "landed",
|
|
93
|
+
"commit": "9fd7907",
|
|
94
|
+
"owned": [
|
|
95
|
+
"src/proof/red-green.mjs", "src/proof/red-parse.mjs",
|
|
96
|
+
"test/proof/red-green.test.mjs", "test/proof/red-parse.test.mjs", "test/fixtures/repos/red-green/**"
|
|
97
|
+
],
|
|
98
|
+
"depends_on": ["B10a"]
|
|
99
|
+
},
|
|
100
|
+
"B11": {
|
|
101
|
+
"title": "Worker: queue, tickets, loop, worker verb, review-file enqueue/--wait, key resolution inside the worker, root-relative rows (V4)",
|
|
102
|
+
"wave": 2,
|
|
103
|
+
"level": "L2",
|
|
104
|
+
"status": "landed",
|
|
105
|
+
"commit": "6b3a68c",
|
|
106
|
+
"owned": [
|
|
107
|
+
"src/worker/**", "src/cli/worker.mjs", "src/cli/review-file.mjs", "test/worker/**"
|
|
108
|
+
],
|
|
109
|
+
"depends_on": ["B0.1", "B2", "B8", "B9a"]
|
|
110
|
+
},
|
|
111
|
+
"B9b": {
|
|
112
|
+
"title": "author + loop, facts + plan check (§3.8, C16; V6/R10 checks)",
|
|
113
|
+
"wave": 2,
|
|
114
|
+
"level": "L2",
|
|
115
|
+
"status": "landed",
|
|
116
|
+
"commit": "b301e94",
|
|
117
|
+
"owned": [
|
|
118
|
+
"src/session/author.mjs", "src/session/facts.mjs", "src/session/plan-check.mjs",
|
|
119
|
+
"src/cli/author.mjs", "src/cli/facts.mjs", "src/cli/plan.mjs", "schema/facts.schema.json",
|
|
120
|
+
"test/session/author.test.mjs", "test/session/facts.test.mjs", "test/session/plan-check.test.mjs",
|
|
121
|
+
"test/fixtures/briefs/**", "test/fixtures/plans/**"
|
|
122
|
+
],
|
|
123
|
+
"depends_on": ["B9a", "B1", "B3"]
|
|
124
|
+
},
|
|
125
|
+
"B12a": {
|
|
126
|
+
"title": "Review engine: adaptive + consensus session plans, packet builder + context rule (C10), lenses (incl. recheck), finding schema + stub guard, engine hook into B11, root-relative refusal (V4)",
|
|
127
|
+
"wave": 3,
|
|
128
|
+
"level": "L2",
|
|
129
|
+
"status": "landed",
|
|
130
|
+
"commit": "5197141",
|
|
131
|
+
"owned": [
|
|
132
|
+
"src/review/engine.mjs", "src/review/packet.mjs", "src/review/context.mjs", "src/review/validate-review.mjs",
|
|
133
|
+
"src/review/lenses/**", "src/review/finding.schema.json",
|
|
134
|
+
"test/review/engine.test.mjs", "test/review/packet.test.mjs", "test/review/context.test.mjs",
|
|
135
|
+
"test/review/validate-review.test.mjs", "test/review/helpers.mjs", "test/fixtures/review/**"
|
|
136
|
+
],
|
|
137
|
+
"depends_on": ["B5", "B9a", "B10b", "B11"]
|
|
138
|
+
},
|
|
139
|
+
"B13b": {
|
|
140
|
+
"title": "doctor (table, --quick, --json, live Jev, per-role pings incl. facts, isolation probe, path-deny probe, worker start/stop, signer line, tmp line, Codex rules probe)",
|
|
141
|
+
"wave": 3,
|
|
142
|
+
"level": "L2",
|
|
143
|
+
"status": "landed",
|
|
144
|
+
"commit": "51a325d",
|
|
145
|
+
"owned": [
|
|
146
|
+
"src/cli/doctor.mjs", "src/doctor/**", "test/doctor/**"
|
|
147
|
+
],
|
|
148
|
+
"depends_on": ["B0.1", "B1", "B2", "B4", "B5", "B6", "B7", "B8", "B9a", "B11"]
|
|
149
|
+
},
|
|
150
|
+
"B12b": {
|
|
151
|
+
"title": "Triage (judge-final), fix loop = §4.11 convergence rule, budget (§4.10), block-gate review check, block waive (human-only, forbidden to coders via mergeForbidden)",
|
|
152
|
+
"wave": 3,
|
|
153
|
+
"level": "L2",
|
|
154
|
+
"status": "landed",
|
|
155
|
+
"commit": "4947f30",
|
|
156
|
+
"owned": [
|
|
157
|
+
"src/review/triage.mjs", "src/review/fixloop.mjs", "src/review/budget.mjs", "src/review/gate-check.mjs",
|
|
158
|
+
"test/review/triage.test.mjs", "test/review/fixloop.test.mjs", "test/review/budget.test.mjs", "test/review/gate-check.test.mjs"
|
|
159
|
+
],
|
|
160
|
+
"depends_on": ["B12a", "B3", "B1.1"]
|
|
161
|
+
},
|
|
162
|
+
"B12c": {
|
|
163
|
+
"title": "Integration: wire worker -> triage -> fixloop so a real review-file call writes the signed review.approved row that block close now requires",
|
|
164
|
+
"wave": 4,
|
|
165
|
+
"level": "L2",
|
|
166
|
+
"status": "in_review",
|
|
167
|
+
"owned": [
|
|
168
|
+
"src/worker/**", "src/cli/review-file.mjs", "test/worker/**", "test/integration/**"
|
|
169
|
+
],
|
|
170
|
+
"depends_on": ["B11", "B12a", "B12b"],
|
|
171
|
+
"note": "root-added (night run, started after B12b landed); not in the v1.3 §10.4 table"
|
|
172
|
+
},
|
|
173
|
+
"B13a": {
|
|
174
|
+
"title": "init wizard (9 steps, flags, --no-interaction, idempotent diff, no-subagent stop text)",
|
|
175
|
+
"wave": 3,
|
|
176
|
+
"level": "L2",
|
|
177
|
+
"status": "landed",
|
|
178
|
+
"commit": "a211098",
|
|
179
|
+
"owned": [
|
|
180
|
+
"src/cli/init.mjs", "src/install/wizard/**", "test/init/**", "test/fixtures/repos/laravel-vue/**"
|
|
181
|
+
],
|
|
182
|
+
"depends_on": ["B13b", "B1", "B2", "B7"]
|
|
183
|
+
},
|
|
184
|
+
"B3.1": {
|
|
185
|
+
"title": "Escalation rules 2b (review_stall) and 6 (review_cap) in escalation.mjs (§3.6)",
|
|
186
|
+
"wave": 3,
|
|
187
|
+
"level": "L1",
|
|
188
|
+
"status": "landed",
|
|
189
|
+
"commit": "c70f1c5",
|
|
190
|
+
"owned": [
|
|
191
|
+
"src/decide/escalation.mjs", "test/decide/escalation.test.mjs"
|
|
192
|
+
],
|
|
193
|
+
"depends_on": ["B3", "B1.1"]
|
|
194
|
+
},
|
|
195
|
+
"B4.1": {
|
|
196
|
+
"title": "Security follow-up: wire the rendered Codex execpolicy rules into the Codex coder invocation (per-run location, never ~/.codex); merge built.env into the child env so a real Codex spawn gets the rules",
|
|
197
|
+
"wave": 3,
|
|
198
|
+
"level": "L2",
|
|
199
|
+
"status": "in_review",
|
|
200
|
+
"owned": [
|
|
201
|
+
"src/engines/builders/codex.mjs", "src/engines/codex-home.mjs", "src/session/spawn.mjs",
|
|
202
|
+
"src/cli/block.mjs", "src/cli/gates.mjs", "src/doctor/probes.mjs", "src/util/forbidden.mjs",
|
|
203
|
+
"test/engines/builders/argv-snapshots.test.mjs", "test/engines/builders/forbidden-rendering.test.mjs",
|
|
204
|
+
"test/engines/codex-home.test.mjs", "test/doctor/codex-rules.test.mjs",
|
|
205
|
+
"test/session/spawn.test.mjs", "test/state/block.test.mjs", "test/util/forbidden.test.mjs", "test/cli/**"
|
|
206
|
+
],
|
|
207
|
+
"depends_on": ["B4", "B8", "B9a", "B13b"],
|
|
208
|
+
"note": "root-added (night run), not in the v1.3 §10.4 table; closes the B13b follow-up 'codex: forbidden list is prose-only'"
|
|
209
|
+
},
|
|
210
|
+
"B11.1": {
|
|
211
|
+
"title": "No-follow file reads (symlink TOCTOU hardening): readRegularFileNoFollow used by contentHash and every packet content read",
|
|
212
|
+
"wave": 3,
|
|
213
|
+
"level": "L2",
|
|
214
|
+
"status": "landed",
|
|
215
|
+
"commit": "989efbc",
|
|
216
|
+
"owned": [
|
|
217
|
+
"src/review/packet.mjs", "src/worker/ticket.mjs", "test/review/packet.test.mjs", "test/worker/nofollow.test.mjs"
|
|
218
|
+
],
|
|
219
|
+
"depends_on": ["B11", "B12a"],
|
|
220
|
+
"note": "root-added (night run), not in the v1.3 §10.4 table; the architect's follow-up to B12a"
|
|
221
|
+
},
|
|
222
|
+
"B14": {
|
|
223
|
+
"title": "The skill: SKILL.md, references/*, templates/*, rules-core.md (R1-R16), scripts/forge shim, adapters",
|
|
224
|
+
"wave": 4,
|
|
225
|
+
"level": "L3 (architect-authored)",
|
|
226
|
+
"status": "pending",
|
|
227
|
+
"owned": [
|
|
228
|
+
"skill/**", "test/skill/**"
|
|
229
|
+
],
|
|
230
|
+
"depends_on": ["B12b", "B13a"]
|
|
231
|
+
},
|
|
232
|
+
"B16": {
|
|
233
|
+
"title": "Docs: README, CHANGELOG 0.1.0, config reference generated from the schema, docs/reference/blocks.json, marketing corrections (C14), no project name (R13)",
|
|
234
|
+
"wave": 4,
|
|
235
|
+
"level": "L0",
|
|
236
|
+
"status": "in_progress",
|
|
237
|
+
"owned": [
|
|
238
|
+
"README.md", "CHANGELOG.md", "docs/reference/config.md", "docs/reference/blocks.json",
|
|
239
|
+
"scripts/gen-config-doc.mjs", "docs/marketing/code-forge-story.md", "test/docs-marketing.test.mjs",
|
|
240
|
+
"test/docs-config.test.mjs", "test/docs-blocks.test.mjs"
|
|
241
|
+
],
|
|
242
|
+
"depends_on": ["B13a"]
|
|
243
|
+
},
|
|
244
|
+
"B15": {
|
|
245
|
+
"title": "Evals (19 cases), plugin manifest, publish.yml, ci/outcome.yml template, evals/run.mjs, imports-order test, no-project-names test",
|
|
246
|
+
"wave": 4,
|
|
247
|
+
"level": "L1",
|
|
248
|
+
"status": "pending",
|
|
249
|
+
"owned": [
|
|
250
|
+
"evals/**", ".claude-plugin/**", ".github/workflows/publish.yml", "ci/**",
|
|
251
|
+
"test/evals-smoke.test.mjs", "test/imports-order.test.mjs", "test/no-project-names.test.mjs"
|
|
252
|
+
],
|
|
253
|
+
"depends_on": ["B14", "B16"]
|
|
254
|
+
},
|
|
255
|
+
"B17": {
|
|
256
|
+
"title": "First real use on a neutral example repo shipped inside the package (examples/node-lib/, scripts/first-use.mjs)",
|
|
257
|
+
"wave": 5,
|
|
258
|
+
"level": "L1 (coder); orchestrated by the root",
|
|
259
|
+
"status": "pending",
|
|
260
|
+
"owned": [
|
|
261
|
+
"examples/**", "scripts/first-use.mjs", "test/first-use.test.mjs"
|
|
262
|
+
],
|
|
263
|
+
"depends_on": ["B15", "B16"]
|
|
264
|
+
},
|
|
265
|
+
"B18": {
|
|
266
|
+
"title": "Retire the predecessor skill: 5-line alias for one release, then delete",
|
|
267
|
+
"wave": 5,
|
|
268
|
+
"level": "human",
|
|
269
|
+
"status": "pending",
|
|
270
|
+
"owned": [
|
|
271
|
+
"the predecessor skill directory (the maintainer only, outside this repo)"
|
|
272
|
+
],
|
|
273
|
+
"depends_on": ["B17"]
|
|
274
|
+
},
|
|
275
|
+
"B10c": {
|
|
276
|
+
"title": "Proof tools for consumers (D14 half): Infection/Stryker wrappers, proof mutate, proof nightly, ci/nightly-proof.yml, proof baseline",
|
|
277
|
+
"wave": 6,
|
|
278
|
+
"level": "L1",
|
|
279
|
+
"status": "deleted",
|
|
280
|
+
"deleted": true,
|
|
281
|
+
"deleted_reason": "Q16 answered 'cut' (the maintainer, 2026-09-25): the product ships no tool-made mutation testing for users' code. B10c never lands; proof.tiers.high.tool / proof.js / proof.nightly left the schema in B1.2.",
|
|
282
|
+
"owned": [
|
|
283
|
+
"src/proof/tools/**", "src/proof/nightly.mjs", "ci/nightly-proof.yml", "test/proof/tools/**", "test/proof/nightly.test.mjs"
|
|
284
|
+
],
|
|
285
|
+
"depends_on": ["B10a", "B10b"]
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# `.code-forge.yml` configuration reference
|
|
2
|
+
|
|
3
|
+
Generated by `node scripts/gen-config-doc.mjs` from `schema/code-forge.schema.json` — do not hand-edit this file. Run `node scripts/gen-config-doc.mjs --check` in CI to catch a schema change that was not followed by a regeneration.
|
|
4
|
+
|
|
5
|
+
Plan v1.2 §1.3 — every key any section of the plan reads must appear here. additionalProperties: false everywhere a key set is fixed; a typed additionalProperties schema (never bare true) marks the handful of genuinely open-ended maps (thresholds.<question>, system1.criteria_extra, known_extra, keys, review.second_levels — the last restricted to L0..L3 keys only via propertyNames). v1.3 (B1.1) amendment: review.max_rounds_per_file/recheck_scope/late_findings (§4.11), budget.block_cases (§10.7), caps.coders re-defaulted to 2 (R10), tmp.root (§9.6/§1.2, V12).
|
|
6
|
+
|
|
7
|
+
`version`, `provider` and `levels` are required. `version`, `provider` and `levels` have no schema-level default; a config omitting any of them is refused.
|
|
8
|
+
|
|
9
|
+
Every other key defaults as shown, or is entirely absent from a minimal file. `additionalProperties: false` applies everywhere a key set is fixed; the rows marked `<key>` are the handful of open-ended maps where the schema accepts a caller-chosen name (a question id, a level name, a key name, a provider id).
|
|
10
|
+
|
|
11
|
+
| Key | Type | Required | Default | Notes |
|
|
12
|
+
|---|---|---|---|---|
|
|
13
|
+
| `version` | const `1` | yes | — | |
|
|
14
|
+
| `project` | object | | — | |
|
|
15
|
+
| `project.name` | string | | — | |
|
|
16
|
+
| `project.slug` | string | | — | |
|
|
17
|
+
| `project.plans_dir` | string | | — | |
|
|
18
|
+
| `project.languages` | object | | — | |
|
|
19
|
+
| `project.languages.chat` | string | | — | |
|
|
20
|
+
| `project.languages.ui` | string | | — | |
|
|
21
|
+
| `project.languages.pr` | string | | — | |
|
|
22
|
+
| `provider` | enum: `anthropic`, `openai`, `xai` | yes | — | |
|
|
23
|
+
| `levels` | object | yes | — | |
|
|
24
|
+
| `levels.L0` | object | yes | — | |
|
|
25
|
+
| `levels.L0.model` | string | yes | — | minLength 1 |
|
|
26
|
+
| `levels.L0.effort` | string | | — | |
|
|
27
|
+
| `levels.L0.provider` | enum: `anthropic`, `openai`, `xai` | | — | |
|
|
28
|
+
| `levels.L0.fallback` | array<object> | | — | |
|
|
29
|
+
| `levels.L0.fallback[].provider` | enum: `anthropic`, `openai`, `xai` | yes | — | |
|
|
30
|
+
| `levels.L0.fallback[].model` | string | yes | — | minLength 1 |
|
|
31
|
+
| `levels.L0.fallback[].effort` | string | | — | |
|
|
32
|
+
| `levels.L1` | object | yes | — | |
|
|
33
|
+
| `levels.L1.model` | string | yes | — | minLength 1 |
|
|
34
|
+
| `levels.L1.effort` | string | | — | |
|
|
35
|
+
| `levels.L1.provider` | enum: `anthropic`, `openai`, `xai` | | — | |
|
|
36
|
+
| `levels.L1.fallback` | array<object> | | — | |
|
|
37
|
+
| `levels.L1.fallback[].provider` | enum: `anthropic`, `openai`, `xai` | yes | — | |
|
|
38
|
+
| `levels.L1.fallback[].model` | string | yes | — | minLength 1 |
|
|
39
|
+
| `levels.L1.fallback[].effort` | string | | — | |
|
|
40
|
+
| `levels.L2` | object | yes | — | |
|
|
41
|
+
| `levels.L2.model` | string | yes | — | minLength 1 |
|
|
42
|
+
| `levels.L2.effort` | string | | — | |
|
|
43
|
+
| `levels.L2.provider` | enum: `anthropic`, `openai`, `xai` | | — | |
|
|
44
|
+
| `levels.L2.fallback` | array<object> | | — | |
|
|
45
|
+
| `levels.L2.fallback[].provider` | enum: `anthropic`, `openai`, `xai` | yes | — | |
|
|
46
|
+
| `levels.L2.fallback[].model` | string | yes | — | minLength 1 |
|
|
47
|
+
| `levels.L2.fallback[].effort` | string | | — | |
|
|
48
|
+
| `levels.L3` | object | yes | — | |
|
|
49
|
+
| `levels.L3.model` | string | yes | — | minLength 1 |
|
|
50
|
+
| `levels.L3.effort` | string | | — | |
|
|
51
|
+
| `levels.L3.provider` | enum: `anthropic`, `openai`, `xai` | | — | |
|
|
52
|
+
| `levels.L3.fallback` | array<object> | | — | |
|
|
53
|
+
| `levels.L3.fallback[].provider` | enum: `anthropic`, `openai`, `xai` | yes | — | |
|
|
54
|
+
| `levels.L3.fallback[].model` | string | yes | — | minLength 1 |
|
|
55
|
+
| `levels.L3.fallback[].effort` | string | | — | |
|
|
56
|
+
| `orchestrator` | enum: `L0`, `L1`, `L2`, `L3` | | — | |
|
|
57
|
+
| `system1` | object | | — | |
|
|
58
|
+
| `system1.provider` | enum: `anthropic`, `openai`, `xai` | | — | |
|
|
59
|
+
| `system1.model` | string | | — | |
|
|
60
|
+
| `system1.key` | string | | — | |
|
|
61
|
+
| `system1.fallback` | enum: `rules`, `s2`, `stop` | | `"rules"` | |
|
|
62
|
+
| `system1.criteria_extra` | object | | — | |
|
|
63
|
+
| `system1.criteria_extra.<key>` | object | | — | user-defined key |
|
|
64
|
+
| `system1.criteria_extra.<key>.instructions` | string | | — | |
|
|
65
|
+
| `system1.criteria_extra.<key>.criteria` | object | | — | |
|
|
66
|
+
| `system1.criteria_extra.<key>.criteria.<key>` | string | | — | user-defined key |
|
|
67
|
+
| `system1.disable` | array<string> | | — | |
|
|
68
|
+
| `thresholds` | object | | — | |
|
|
69
|
+
| `thresholds.default` | object | | — | |
|
|
70
|
+
| `thresholds.default.act` | number | | — | min 0; max 1 |
|
|
71
|
+
| `thresholds.default.check` | number | | — | min 0; max 1 |
|
|
72
|
+
| `thresholds.default.close_margin` | number | | — | min 0; max 1 |
|
|
73
|
+
| `thresholds.findings` | object | | — | |
|
|
74
|
+
| `thresholds.findings.fix` | number | | `0.9` | min 0; max 1 |
|
|
75
|
+
| `thresholds.findings.nit` | number | | `0.4` | min 0; max 1 |
|
|
76
|
+
| `thresholds.findings.resolved` | number | | `0.9` | min 0; max 1 |
|
|
77
|
+
| `thresholds.<key>` | object | | — | user-defined key |
|
|
78
|
+
| `thresholds.<key>.act` | number | | — | min 0; max 1 |
|
|
79
|
+
| `thresholds.<key>.check` | number | | — | min 0; max 1 |
|
|
80
|
+
| `thresholds.<key>.close_margin` | number | | — | min 0; max 1 |
|
|
81
|
+
| `escalation` | object | | — | |
|
|
82
|
+
| `escalation.retries_per_level` | integer | | `2` | min 0 |
|
|
83
|
+
| `escalation.review_rounds_per_level` | integer | | `2` | min 0 |
|
|
84
|
+
| `escalation.stop_at` | const `"L3"` | | `"L3"` | |
|
|
85
|
+
| `escalation.l3_mode` | enum: `patch`, `code` | | `"patch"` | |
|
|
86
|
+
| `review` | object | | — | |
|
|
87
|
+
| `review.multimodel` | boolean | | `false` | |
|
|
88
|
+
| `review.second_provider` | enum: `anthropic`, `openai`, `xai` \| null | | `null` | |
|
|
89
|
+
| `review.second_levels` | null \| object | | `null` | |
|
|
90
|
+
| `review.closed_book` | const `true` | | `true` | |
|
|
91
|
+
| `review.judge_sees_diff` | boolean | | `false` | |
|
|
92
|
+
| `review.budgets` | object | | — | |
|
|
93
|
+
| `review.budgets.quick_in` | integer | | `6000` | min 1 |
|
|
94
|
+
| `review.budgets.full_in` | integer | | `12000` | min 1 |
|
|
95
|
+
| `review.budgets.judge_in` | integer | | `8000` | min 1 |
|
|
96
|
+
| `review.budgets.out` | integer | | `1200` | min 1 |
|
|
97
|
+
| `review.context` | object | | — | |
|
|
98
|
+
| `review.context.whole_file_max_lines` | integer | | `400` | min 0 |
|
|
99
|
+
| `review.context.hunk_context_lines` | integer | | `40` | min 0 |
|
|
100
|
+
| `review.context.min_context_lines` | integer | | `10` | min 0 |
|
|
101
|
+
| `review.block_budget_tokens` | integer | | `150000` | min 1 |
|
|
102
|
+
| `review.block_budget_usd` | number \| null | | `null` | |
|
|
103
|
+
| `review.min_tokens_out` | integer | | `120` | min 1 |
|
|
104
|
+
| `review.batch_small_files` | boolean | | `true` | |
|
|
105
|
+
| `review.session_timeout_s` | integer | | `600` | min 1 |
|
|
106
|
+
| `review.max_rounds_per_file` | integer | | `4` | min 2; max 6 |
|
|
107
|
+
| `review.recheck_scope` | enum: `fix_hunks`, `file` | | `"fix_hunks"` | |
|
|
108
|
+
| `review.late_findings` | enum: `sweep`, `block` | | `"sweep"` | |
|
|
109
|
+
| `engine` | enum: `auto`, `solo`, `harness`, `subprocess` | | `"auto"` | |
|
|
110
|
+
| `harnesses` | array<string> | | — | |
|
|
111
|
+
| `harness` | object | | — | |
|
|
112
|
+
| `harness.claude` | object | | — | |
|
|
113
|
+
| `harness.claude.worktree` | boolean | | `false` | |
|
|
114
|
+
| `harness.solo` | object | | — | |
|
|
115
|
+
| `harness.solo.interactive_harden` | boolean | | `false` | |
|
|
116
|
+
| `caps` | object | | — | |
|
|
117
|
+
| `caps.coders` | integer | | `2` | min 1 |
|
|
118
|
+
| `gates` | object | | — | |
|
|
119
|
+
| `gates.test` | array<string> \| null | | — | |
|
|
120
|
+
| `gates.lint` | array<string> \| null | | — | |
|
|
121
|
+
| `gates.types` | array<string> \| null | | — | |
|
|
122
|
+
| `gates.format` | array<string> \| null | | — | |
|
|
123
|
+
| `gates.extra` | object | | — | |
|
|
124
|
+
| `gates.extra.secret_scan` | boolean | | `true` | |
|
|
125
|
+
| `gates.extra.safe_edit` | boolean | | `true` | |
|
|
126
|
+
| `gates.extra.invariants` | string | | — | |
|
|
127
|
+
| `gates.full_suite_threshold_files` | integer | | `10` | min 1 |
|
|
128
|
+
| `proof` | object | | — | |
|
|
129
|
+
| `proof.tiers` | object | | — | |
|
|
130
|
+
| `proof.tiers.high` | object | | — | |
|
|
131
|
+
| `proof.tiers.high.paths` | array<string> | | — | |
|
|
132
|
+
| `proof.tiers.light` | object | | — | |
|
|
133
|
+
| `proof.extra` | array<string> | | — | |
|
|
134
|
+
| `proof.isolation` | enum: `export`, `lock` | | `"export"` | |
|
|
135
|
+
| `proof.export` | object | | — | |
|
|
136
|
+
| `proof.export.link_dirs` | array<string> | | `["vendor","node_modules"]` | |
|
|
137
|
+
| `proof.export.copy_untracked` | array<string> | | `[".env",".env.testing"]` | |
|
|
138
|
+
| `budget` | object | | — | |
|
|
139
|
+
| `budget.block_lines` | integer | | `600` | min 1 |
|
|
140
|
+
| `budget.block_tokens_soft` | integer | | — | min 1 |
|
|
141
|
+
| `budget.block_tokens_hard` | integer | | — | min 1 |
|
|
142
|
+
| `budget.run_usd_soft` | number | | — | min 0 |
|
|
143
|
+
| `budget.block_cases` | integer | | `80` | min 10 |
|
|
144
|
+
| `autonomy` | object | | — | |
|
|
145
|
+
| `autonomy.coder_may_open_draft_pr` | boolean | | `true` | |
|
|
146
|
+
| `autonomy.coder_may_push` | boolean | | `true` | |
|
|
147
|
+
| `commit` | object | | — | |
|
|
148
|
+
| `commit.trailers` | enum: `none`, `harness` | | `"none"` | |
|
|
149
|
+
| `wip` | object | | — | |
|
|
150
|
+
| `wip.ready_prs` | integer | | `1` | min 1 |
|
|
151
|
+
| `production` | object | | — | |
|
|
152
|
+
| `production.markers` | array<string> | | — | |
|
|
153
|
+
| `production.names` | array<string> | | — | |
|
|
154
|
+
| `known_extra` | object | | — | |
|
|
155
|
+
| `known_extra.<key>` | array<string> | | — | user-defined key |
|
|
156
|
+
| `calibration` | object | | — | |
|
|
157
|
+
| `calibration.shadow_rate` | number | | `0` | min 0; max 0.5 |
|
|
158
|
+
| `keys` | object | | — | |
|
|
159
|
+
| `keys.<key>` | string | | — | user-defined key |
|
|
160
|
+
| `tmp` | object | | — | |
|
|
161
|
+
| `tmp.root` | string \| null | | `null` | |
|
|
162
|
+
| `telemetry` | const `"off"` | | `"off"` | |
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# first-use inputs
|
|
2
|
+
|
|
3
|
+
What `scripts/first-use.mjs` feeds the one block of the first real use ("add `clamp()` with tests")
|
|
4
|
+
on a copy of `examples/node-lib/`:
|
|
5
|
+
|
|
6
|
+
- `clamp.brief.md` — the brief; `code-forge facts` builds its facts sheet, `code-forge author --job
|
|
7
|
+
plan` drafts the plan from both, and `block open --brief` points the coder at the plan.
|
|
8
|
+
- `clamp.acceptance.yml` — the block's acceptance clauses (`block open --acceptance`).
|
|
9
|
+
- `coder/` — the deterministic edit `--coder scripted` applies (the automated test uses it). In a
|
|
10
|
+
real run the coder is a harness subagent (`--coder harness`) or a `code-forge spawn` coder process
|
|
11
|
+
(`--engine subprocess`, which spawns it with `--coder spawn`).
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
- clause: clamp returns the value itself when it is inside [min, max]
|
|
2
|
+
tests: [test/clamp.test.mjs::clamp returns the value inside the range]
|
|
3
|
+
- clause: clamp returns min when the value is below min
|
|
4
|
+
tests: [test/clamp.test.mjs::clamp returns min below the range]
|
|
5
|
+
- clause: clamp returns max when the value is above max
|
|
6
|
+
tests: [test/clamp.test.mjs::clamp returns max above the range]
|
|
7
|
+
- clause: clamp throws a RangeError when min > max
|
|
8
|
+
tests: [test/clamp.test.mjs::clamp refuses min above max]
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Block B1 — add `clamp()` with tests
|
|
2
|
+
|
|
3
|
+
Add `clamp(value, min, max)` to ./src/math.mjs. It returns `value` limited to the closed range
|
|
4
|
+
`[min, max]` and throws a `RangeError` when `min > max`. Nothing else in the module changes.
|
|
5
|
+
|
|
6
|
+
Tests go in a new file, ./test/clamp.test.mjs, run by the example's `npm test` (plain `node:test`,
|
|
7
|
+
the `--test` runner). One test per acceptance clause, named exactly as the acceptance file names it:
|
|
8
|
+
inside the range ⇒ the value, below min ⇒ min, above max ⇒ max, min above max ⇒ `RangeError`.
|
|
9
|
+
|
|
10
|
+
So that the red step against the base version (no `clamp` export) is always an assertion failure,
|
|
11
|
+
never an import error or a `TypeError`: import the module as a namespace
|
|
12
|
+
(`import * as ns from '../src/math.mjs'`), start EVERY test with
|
|
13
|
+
`assert.strictEqual(typeof ns.clamp, 'function')`, and check the throw with
|
|
14
|
+
`assert.throws(() => ns.clamp(1, 10, 0), RangeError)`.
|
|
15
|
+
|
|
16
|
+
Owned files: `src/math.mjs`, `test/clamp.test.mjs`. Level L1.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/** Small number helpers. */
|
|
2
|
+
|
|
3
|
+
/** @param {number} a @param {number} b @returns {number} */
|
|
4
|
+
export function add(a, b) {
|
|
5
|
+
return a + b;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/** @param {number} a @param {number} b @returns {number} */
|
|
9
|
+
export function sub(a, b) {
|
|
10
|
+
return a - b;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @param {number[]} values
|
|
15
|
+
* @returns {number} the arithmetic mean
|
|
16
|
+
* @throws {RangeError} on an empty list
|
|
17
|
+
*/
|
|
18
|
+
export function mean(values) {
|
|
19
|
+
if (values.length === 0) throw new RangeError('mean of an empty list');
|
|
20
|
+
return values.reduce(add, 0) / values.length;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @param {number} value @param {number} min @param {number} max
|
|
25
|
+
* @returns {number} `value` limited to the closed range [min, max]
|
|
26
|
+
* @throws {RangeError} when `min > max`
|
|
27
|
+
*/
|
|
28
|
+
export function clamp(value, min, max) {
|
|
29
|
+
if (min > max) throw new RangeError('clamp: min is above max');
|
|
30
|
+
return Math.min(Math.max(value, min), max);
|
|
31
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { test } from 'node:test';
|
|
3
|
+
// A namespace import and a `typeof` assertion first in every case: at the base version `ns.clamp`
|
|
4
|
+
// is undefined, so the red step fails on an assertion (RED), never on an import error or a
|
|
5
|
+
// TypeError (RED_INVALID).
|
|
6
|
+
import * as ns from '../src/math.mjs';
|
|
7
|
+
|
|
8
|
+
test('clamp returns the value inside the range', () => {
|
|
9
|
+
assert.strictEqual(typeof ns.clamp, 'function');
|
|
10
|
+
assert.strictEqual(ns.clamp(5, 0, 10), 5);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
test('clamp returns min below the range', () => {
|
|
14
|
+
assert.strictEqual(typeof ns.clamp, 'function');
|
|
15
|
+
assert.strictEqual(ns.clamp(-3, 0, 10), 0);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test('clamp returns max above the range', () => {
|
|
19
|
+
assert.strictEqual(typeof ns.clamp, 'function');
|
|
20
|
+
assert.strictEqual(ns.clamp(42, 0, 10), 10);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test('clamp refuses min above max', () => {
|
|
24
|
+
assert.strictEqual(typeof ns.clamp, 'function');
|
|
25
|
+
assert.throws(() => ns.clamp(1, 10, 0), RangeError);
|
|
26
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# node-lib (example)
|
|
2
|
+
|
|
3
|
+
A three-module ESM library with a `node:test` suite. It ships inside `@codedology/code-forge` as the
|
|
4
|
+
repository the **first real use** runs on (`scripts/first-use.mjs`): the script copies it to a temp
|
|
5
|
+
directory, `git init`s it and runs one block — "add `clamp()` with tests" — end to end.
|
|
6
|
+
|
|
7
|
+
- `src/math.mjs` — `add`, `sub`, `mean`
|
|
8
|
+
- `src/strings.mjs` — `pad`, `capitalize`, `greet`
|
|
9
|
+
- `src/list.mjs` — `chunk`, `unique`
|
|
10
|
+
|
|
11
|
+
`npm test` runs the suite. `.gitattributes` marks `/.github` and `CHANGELOG.md` `export-ignore`, and
|
|
12
|
+
`test/package.test.mjs` reads both, so the suite only passes in code-forge's measurement export when
|
|
13
|
+
the export restores them. `.env.example` is copied to `.env` (untracked) before the run; `greet()`
|
|
14
|
+
reads `GREETING` from it.
|
|
15
|
+
|
|
16
|
+
The ignore rules ship as `gitignore` (npm pack drops a nested `.gitignore`); `first-use.mjs` renames
|
|
17
|
+
it to `.gitignore` in the copy before `git init`.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "node-lib-example",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "A tiny ESM library that code-forge's first-use run works on.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"private": true,
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"exports": {
|
|
9
|
+
"./math": "./src/math.mjs",
|
|
10
|
+
"./strings": "./src/strings.mjs",
|
|
11
|
+
"./list": "./src/list.mjs"
|
|
12
|
+
},
|
|
13
|
+
"engines": {
|
|
14
|
+
"node": ">=22"
|
|
15
|
+
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"test": "node --test"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** Small array helpers. */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @template T
|
|
5
|
+
* @param {T[]} items @param {number} size
|
|
6
|
+
* @returns {T[][]} consecutive slices of at most `size` items
|
|
7
|
+
* @throws {RangeError} when `size` is below 1
|
|
8
|
+
*/
|
|
9
|
+
export function chunk(items, size) {
|
|
10
|
+
if (!Number.isInteger(size) || size < 1) throw new RangeError('chunk size must be an integer >= 1');
|
|
11
|
+
const out = [];
|
|
12
|
+
for (let i = 0; i < items.length; i += size) out.push(items.slice(i, i + size));
|
|
13
|
+
return out;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @template T
|
|
18
|
+
* @param {T[]} items
|
|
19
|
+
* @returns {T[]} the first occurrence of each item, in order
|
|
20
|
+
*/
|
|
21
|
+
export function unique(items) {
|
|
22
|
+
return [...new Set(items)];
|
|
23
|
+
}
|