Skip to content

Commit 7f87d2e

Browse files
Merge pull request #72 from CodeWithJuber/claude/parallel-solving-3dc0ji
fix: close all cross-layer trust gaps from the v0.22.0 deep re-audit (RA-01..RA-24)
2 parents 65d2a3d + 1843b4e commit 7f87d2e

43 files changed

Lines changed: 2942 additions & 336 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,115 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

77
## [Unreleased]
88

9+
### Changed
10+
11+
- **Only real profiles (RA-14).** `forge init --profile` now accepts `minimal` and
12+
`standard` only — the former `web-app`/`backend-service`/`library`/`regulated` names
13+
were always aliases of the full pack (sync only ever branched on `minimal`) and are now
14+
deprecated aliases of `standard`: accepted with a deprecation warning, stored as
15+
`standard`, and a legacy name already stored in an existing config still syncs as
16+
standard (with a one-time warning). New exported `validateProfile()` maps legacy names
17+
before any side effect.
18+
- **Honest wording (RA-22, RA-23, RA-24).** package.json's description now says what
19+
ships — shared memory, impact analysis, and guardrail hooks emitted as native config —
20+
instead of "the cognitive substrate every frozen model is missing". README's loop intro
21+
now states that the automatic pre-action check is Claude Code hooks (advisory by
22+
default, enforcement opt-in via `FORGE_ENFORCE=1`) while other tools receive
23+
instructions and MCP tools to invoke; the comparison table now claims what the ledger
24+
does today — evidence must name a known oracle and a typed, format-checked reference at
25+
append time — rather than implying stored evidence is re-verified on load.
26+
27+
### Security
28+
29+
- **Git read-command bypasses closed (RA-05).** The `protect-paths` guard now classifies
30+
`git diff`, `git stash`, `git cat-file`, `git archive`, and `git grep` as content
31+
readers, closing the secret-file read path via git plumbing (`git diff -- .env`,
32+
`git cat-file -p HEAD:.env`, `git archive HEAD .env`). The settings template moves
33+
`Bash(git stash:*)` from allow to ask: `git stash show -p` can dump stashed secret
34+
content with no path token for the guard to match, so the permission prompt is the
35+
only defense there.
36+
- **Secret redactor no longer fails silently (RA-06).** Any redaction failure prints a
37+
visible `secret redaction DEGRADED` warning to stderr, and new `FORGE_GUARD_STRICT=1`
38+
escalates degraded redaction to a blocking exit instead of passing unredacted output
39+
through.
40+
41+
### Fixed
42+
43+
- **`forge verify --deep` can no longer pass when nothing ran (RA-01).** Deep `ok` now
44+
requires the core tests status to be PASS in addition to the lens consensus; the
45+
result, provenance, and metrics carry an additive four-state `status`
46+
(PASS/FAIL/INCOMPLETE/NOT_CONFIGURED), and a repo with no configured verifier exits 1
47+
with `NOT VERIFIED` instead of printing PASS.
48+
- **`forge verify` executes the detected runner (RA-08).** pnpm/yarn/bun repos spawn
49+
their own package manager instead of a hardcoded `npm test`; pytest runs directly;
50+
non-executable detections (go/cargo/mvn/gradle/dotnet/rspec/phpunit) or a missing
51+
binary report an honest INCOMPLETE naming the real command. `detectStack()` gains a
52+
structured `testRunners` field (`{bin, args, label}`) alongside the unchanged
53+
`testCommands` strings; npx-based runners are report-only and never executed.
54+
- **CLI verify output distinguishes all four states (RA-09).** `BLOCKED` is reserved for
55+
a runner that actually failed; anything that never completed prints `NOT VERIFIED`
56+
(still exit 1) instead of the misleading "tests failing".
57+
- **Ledger log lines are hash-verified at read time (RA-02).** `readLog()` recomputes
58+
every record's content hash and drops forged/corrupt lines (evidence lines must also
59+
be valid outcomes), so a hand-edited log line can no longer move `val()`. Imports and
60+
merges no longer bypass validation: imported evidence goes through the full append
61+
gate (oracle/ref checks including `git:` resolution against the destination repo), and
62+
rejected records are quarantined as sealed audit lines under `quarantine/<claimId>.log`
63+
with a `quarantined` count surfaced by `forge ledger merge`. `validOutcome()` rejects
64+
typed-but-empty refs (e.g. `git:`), matching append-time validation.
65+
- **Stale ambient atlas is no longer authoritative (RA-07).** When the atlas is stale and
66+
a hook can't rebuild it, impact, impacted files, and predicted tests are dropped rather
67+
than silently computed from stale data; renderers say "impact unavailable" without a
68+
contradicting test list, and `FORGE_ENFORCE=1` never hard-blocks on a stale blast
69+
radius.
70+
- **Completion gate enforces real obligations (RA-10).** Code changes now owe test
71+
evidence — a test file moved with the change or a fresh passing `forge verify` run — in
72+
addition to docs/state; a handoff alone no longer satisfies the gate for code
73+
(config-only changes keep the lighter docs-or-handoff bar). Kill switch, block-once,
74+
and fail-open behavior are unchanged.
75+
- **Doctor honesty (RA-19, RA-20).** A missing atlas reports subsystem health
76+
`UNAVAILABLE` (new neutral `na` status) instead of `ACTIVE`, and `doctor --fix`
77+
records a repair that returns an error object (e.g. `mergeSettings` refusing a corrupt
78+
file) as failed with the reason instead of successful.
79+
- **Settings failures propagate (RA-04).** `forge init` fails (exit 1, stderr) when the
80+
settings merge is refused or errors, and `install.sh` reports `Install INCOMPLETE` and
81+
exits 1 instead of printing `Done.` when hooks weren't wired.
82+
- **Hook paths are shell-quoted (RA-12).** Hook and statusline commands merged into
83+
`~/.claude/settings.json` single-quote the package path, so installs under a path
84+
containing spaces work; old unquoted entries dedupe against and are upgraded to the
85+
quoted form on re-merge.
86+
- **Invalid profile aborts before side effects (RA-13).** An invalid
87+
`forge init --profile` value no longer leaves a partial repo scaffold, `.forge/` write,
88+
or settings merge behind.
89+
- **Reversible settings uninstall (RA-11, RA-17, RA-18).** New
90+
`forge init --remove-settings` (also run by `install.sh --uninstall`) reverses the
91+
settings merge — template-shaped hooks/permissions/statusline and the `_forge` marker
92+
are removed with a backup, user entries untouched. The installer header now tells the
93+
truth about the global, reversible merge, announces it before merging, and gains
94+
`--no-settings` to skip it.
95+
- **MCP emitters are no longer destructive (RA-03).** Installed integrations are recorded
96+
in `.forge/forge.config.json` (`mcp.integrations`), and `forge sync` and
97+
`forge integrations add` both emit the same full managed set — the sync/add/sync
98+
oscillation that deleted each other's servers is gone.
99+
- **MCP configs respect ownership (RA-21).** Continue gets one forge-marked YAML per
100+
managed server (the legacy combined file is migrated away only when provably forge's),
101+
Codex servers live in `# forge:managed:<name>` blocks refreshed by byte-compare, and a
102+
same-name JSON entry the user configured is never overwritten — it is reported with an
103+
`--adopt` hint instead. New `forge integrations remove <name>` reverses an add,
104+
deleting only forge-owned entries, blocks, and files; running it twice is a no-op.
105+
- **Stop-hook auto-sync detects body drift (RA-16).** Auto-sync now byte-compares
106+
`AGENTS.md` against the exact content sync would write instead of trusting the embedded
107+
marker hash, so a hand-edited body with an intact marker is repaired.
108+
- **Repo config consolidated; malformed JSON fails loudly (RA-15).** `src/repo_config.js`
109+
is now the single per-repo config module: `readForgeConfig`/`writeForgeConfig` operate
110+
on the unified `.forge/forge.config.json` (unknown keys round-trip through writes),
111+
migration-read the legacy `.forge/config.json` (`primaryTool`/`tools`; the unified file
112+
wins on key conflicts, the legacy file is left in place), and never silently discard
113+
corrupt JSON — reads warn once per process on stderr and report the corrupt path, and
114+
writers (`forge init --profile`, `forge tools <name>`) refuse to overwrite an
115+
unparseable config instead of replacing it with defaults. `forge sync` still fail-opens
116+
to default rules on a corrupt config but surfaces a warning in its report.
117+
9118
## [0.22.0] - 2026-07-17
10119

11120
### Fixed (audit remediation)

README.md

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,15 @@ Then, in your project:
162162
forge init # emit every AI tool's native config from one shared source
163163
forge doctor # pass/fail health check: tools, guards, MCP, config drift
164164
forge doctor --fix # auto-repair the safely fixable findings, then re-check
165+
```
166+
167+
`forge init` also merges Forge's hooks + permissions into `~/.claude/settings.json`
168+
that file is **global** (it affects all your repos), so init says so before reporting the
169+
merge. Opt out with `forge init --no-settings`; reverse a past merge any time with
170+
`forge init --remove-settings` (your own entries are preserved, and a timestamped backup
171+
is written first).
172+
173+
```bash
165174

166175
# pre-action check before you (or your agent) edit anything:
167176
forge substrate "Change verifyToken in src/auth.js to require length > 20; update tests"
@@ -193,7 +202,8 @@ The first time you run a real command before `~/.claude/settings.json` is forge-
193202
one tip line points at `forge init` (or `forge doctor --fix`) to wire hooks + permissions;
194203
it self-silences once init runs and `FORGE_NO_HINT=1` mutes it entirely. `install.sh` does
195204
this wiring for you via `forge init --settings-only` — an idempotent, marker-guarded merge
196-
that never clobbers your existing settings.
205+
that never clobbers your existing settings (skip it with `install.sh --no-settings`;
206+
`install.sh --uninstall` or `forge init --remove-settings` reverses it).
197207

198208
| Group | Command | Does |
199209
| -------------------------- | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
@@ -204,7 +214,7 @@ that never clobbers your existing settings.
204214
| | `forge update` | self-update — `--check` reports if a newer version exists, bare applies it, `--to <version>` pins/downgrades |
205215
| | `forge docs` | docs↔code drift — `check` reconciles commands/env/MCP/CHANGELOG; `sync` sweeps the diff for stale doc mentions |
206216
| | `forge config` | provider setup — show / switch / add providers, set the default model |
207-
| | `forge integrations` | opt-in third-party MCP servers (e.g. context7) — shows package/network, writes only with `--yes` |
217+
| | `forge integrations` | opt-in third-party MCP servers (e.g. context7) — `add` records the managed set and writes only with `--yes` (`--adopt` claims a same-name entry you already had); `remove` reverses it |
208218
| | `forge harden` | wire the pre-commit gate (gitleaks + commit gate) + sandbox settings |
209219
| | `forge catalog` | Start-Here index of every tool / crew / guard |
210220
| | `forge brand` | print the brand token map |
@@ -276,14 +286,14 @@ Structural differences only — each row is checkable against the named source,
276286
tables (including what each adjacent tool does _better_) are in
277287
[`reports/benchmarks.md` → Uniqueness](reports/benchmarks.md#uniqueness--structural-contrasts-with-adjacent-tools):
278288

279-
| Property | Forge | Note stores / gateways / RAG |
280-
| -------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
281-
| Memory confidence moved **only by independent oracles** (tests, CI, human) | yes — closed `ORACLES` table; unverifiable evidence rejected (`src/ledger.js`) | note stores keep notes as written |
282-
| Unreviewed knowledge decays toward _uncertainty_, not deletion | yes — confidence fades over time toward _unsure_; dormant claims kept for audit, never deleted | notes persist unchanged until deleted |
283-
| Conflict-free team merge over plain git | yes — two teammates' memories combine by set-union, so they never conflict (property-tested) | per-machine SQLite or a hosted store |
284-
| Routing decision visible and diffable **before** dispatch | yes — a deterministic rubric you can read in the repo (`src/model_tiers.json`) | gateways decide inside the proxy at request time |
285-
| Cached code served **only with verification evidence**, revalidated against the current code graph | yes — a cache hit is served only if its evidence clears a confidence floor and still matches today's code | plain RAG serves on similarity alone |
286-
| **What they do better** || hosted sync, web UIs, embedding search that catches paraphrase; gateways actually _move traffic_ (failover, quotas). Forge is a transparency layer, not a replacement |
289+
| Property | Forge | Note stores / gateways / RAG |
290+
| -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
291+
| Memory confidence moved **only by independent oracles** (tests, CI, human) | yes — closed `ORACLES` table; unverifiable evidence rejected at append, forged log lines fail their content-hash recheck at read and imports are quarantined (`src/ledger.js`) | note stores keep notes as written |
292+
| Unreviewed knowledge decays toward _uncertainty_, not deletion | yes — confidence fades over time toward _unsure_; dormant claims kept for audit, never deleted | notes persist unchanged until deleted |
293+
| Conflict-free team merge over plain git | yes — two teammates' memories combine by set-union, so they never conflict (property-tested) | per-machine SQLite or a hosted store |
294+
| Routing decision visible and diffable **before** dispatch | yes — a deterministic rubric you can read in the repo (`src/model_tiers.json`) | gateways decide inside the proxy at request time |
295+
| Cached code served **only with verification evidence**, revalidated against the current code graph | yes — a cache hit is served only if its evidence clears a confidence floor and still matches today's code | plain RAG serves on similarity alone |
296+
| **What they do better** | | hosted sync, web UIs, embedding search that catches paraphrase; gateways actually _move traffic_ (failover, quotas). Forge is a transparency layer, not a replacement |
287297

288298
## Honest limits
289299

0 commit comments

Comments
 (0)