voidforge-build 23.21.0 → 23.22.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/dist/CHANGELOG.md CHANGED
@@ -6,6 +6,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/), and this
6
6
 
7
7
  ---
8
8
 
9
+ ## [23.22.0] - 2026-06-24
10
+
11
+ ### `update` now auto-activates `/contextmeter` (matches `init`)
12
+
13
+ - **Fixed** — `npx voidforge-build update` now wires the `/contextmeter` status line + `UserPromptSubmit` awareness hook into `.claude/settings.json`, the same default-on way `init` does. Previously `update` copied `scripts/statusline/` but left the meter inactive until you ran `/contextmeter` by hand. `mergeStatuslineSettings` is now shared by `init` and `update`; it stays idempotent and **non-clobbering** — never overwrites a project's own `statusLine`, never duplicates the awareness hook. `--dry-run` / diff now reports the pending `.claude/settings.json` change honestly (reading the snippet from the source so it's accurate even before the scripts are copied). +3 updater tests (suite 1420→1423).
14
+
15
+ Both packages bumped in lockstep (version-consistency gate). Dep `^23.21.0` → `^23.22.0`.
16
+
9
17
  ## [23.21.0] - 2026-06-24
10
18
 
11
19
  ### Triaged field reports #382 / #383 / #384 → `/seal`-hardening + DevOps/QA/orchestration fixes + a pattern-distribution gap
package/dist/VERSION.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Version
2
2
 
3
- **Current:** 23.21.0
3
+ **Current:** 23.22.0
4
4
 
5
5
  ## Versioning Scheme
6
6
 
@@ -14,6 +14,7 @@ This project uses [Semantic Versioning](https://semver.org/):
14
14
 
15
15
  | Version | Date | Summary |
16
16
  |---------|------|---------|
17
+ | 23.22.0 | 2026-06-24 | **`update` now auto-activates `/contextmeter` (matches `init`).** `npx voidforge-build update` wires the context-meter status line + `UserPromptSubmit` awareness hook into `.claude/settings.json` the same default-on way `init` does — previously `update` copied `scripts/statusline/` but left the meter inactive until a manual `/contextmeter` run. `mergeStatuslineSettings` is now shared by init + update, idempotent + non-clobbering (never overwrites a project's own `statusLine`, never duplicates the hook); `--dry-run` reports the pending `.claude/settings.json` change. +3 updater tests (1420→1423). Dep `^23.21.0` → `^23.22.0`. |
17
18
  | 23.21.0 | 2026-06-24 | **Triaged field reports #382 / #383 / #384 → `/seal`-hardening + DevOps/QA/orchestration fixes + a pattern-distribution gap.** **#384:** release Step 0 unrelated/pre-existing-change detection (`/git`, `/seal`, `RELEASE_MANAGER.md`) — split session-authored vs out-of-scope changes, dependency-manifest scrutiny, never `git add -A` (the `vercel` near-miss, mechanized); creation-time native-collision gate (`BUILD_PROTOCOL.md`, `NATIVE_CAPABILITIES.md`) — check a new command's name + add its row at creation, not release re-audit; `bypass.sh` stale-pointer self-repair via `CLAUDE_CODE_SESSION_ID` (repoints a dead-session pointer on the first try, no re-run; +4 tests, gate 27→31). **#382:** DevOps ACL-traverse enumeration + post-lock prod-FE 200 check; new `egress-sandbox.sh` pattern (`systemd-run --uid/--gid`, uid-independent egress); SUB_AGENTS spend ceiling reserves in-flight child budget; QA coverage-fidelity honesty + per-lane ledger; headless-OAuth bootstrap note. **Distribution:** `prepack.sh`/`copy-assets.sh` now ship every `docs/patterns/` file regardless of extension (`.sh`/`.py`/`.conf` were silently dropped — LRN-11 gap). #383 closed as already-shipped. Build clean, suite 1420. Dep `^23.20.0` → `^23.21.0`. |
18
19
  | 23.20.0 | 2026-06-23 | **Triaged 12 upstream field reports (#364–#378) → methodology hardening, + `/seal` and `/contextmeter`.** Applied every accepted fix across ~41 method docs / agents / patterns / commands (throughput/scale gates, ADR concurrency verification, deny-list discipline, runtime-path tracers, render-gate coverage, OAuth/external-claim verification, HTTP two-principal isolation, dall-e→gpt-image-1 currency, `/debrief` gate-gap docs) + 2 new patterns (`post-deploy-probe.sh`, `exclusion-set-invariant.md`); implemented the two wizard reports — non-destructive CLAUDE.md `update` merge (#368) + legacy-marker detection (#369). **New `/seal`** — session closeout (git → debrief → vault → handoff). **New `/contextmeter`** — context-budget meter + `UserPromptSubmit` awareness hook, default-on (warn 80% / crit 92%), `scripts/statusline/` wired through all four distribution paths + npm `files`. Build clean, suite 1392→1420. Dep `^23.19.0` → `^23.20.0`. |
19
20
  | 23.19.0 | 2026-06-13 | **Gauntlet acceptance test → 14 fixes (the ADR-067 re-platform, validated by running it on itself).** Ran the new `gauntlet.workflow.js` live on the v23.13–v23.18 platform code (10-agent Surfer roster → 347 agents → 99 distinct claims → 66 confirmed + 24 crossfire, 0 Critical) and fixed the 3-lens-confirmed findings. **Gate (security):** `_paths.sh` reap was missing `-mindepth 1` → could `rm -rf` the entire `sessions/` tree (every live roster/bypass); the reaper now refreshes `$SESSION_DIR` mtime on activity + threshold raised above the TTL, closing the documented reap-vs-fresh-roster/bypass race; `shasum`→`sha256sum` fallback (gate silently broke on Alpine); `bypass.sh` run before the first hook fire now records a repo-scoped *pending* bypass that `check.sh` promotes (was a silent no-op). **Workflows:** strike no longer re-runs the same ≤5-agent roster twice; crossfire `survives:true+REFUTED` verdicts no longer vanish into no bucket (logged in `crossfireRefutedLog`); dedup keeps the **highest** severity + `raisedBy` (was first-write-wins); guarded `JSON.parse(args)`; undefined-domain prompt guard. **Distribution:** `npx voidforge-build init` now copies `.claude/workflows/` + `AGENT_CLASSIFICATION.md`; `update` now propagates `.claude/workflows` + `scripts/surfer-gate` (both were stranded). **Validation:** new `scripts/validate-workflows.sh` (wraps the runtime shape, then `node --check`) wired into `pretest` — corrects the false "scripts pass `node --check`" claim and gates syntax errors from shipping. **Docs:** `WORKFLOWS.md` example `agentType: a.id`→`a.name` + new gotchas; stale `/tmp/voidforge-*` paths fixed in gate README + CLAUDE.md (ADR-060). **CI:** `recover-partial` derives the version from `package.json` not `github.ref_name` (broke on dispatch); Playwright cache key off the committed manifests not the regenerated lockfile. Gate suite 23→27, full suite 1390→1392. Deferred (field-report candidates): concurrent same-repo pointer collision, `workflow_dispatch` branch guard. Dep `^23.18.0` → `^23.19.0`. |
@@ -21,4 +21,21 @@ export interface ProjectResult {
21
21
  markerId: string;
22
22
  filesCreated: number;
23
23
  }
24
+ /**
25
+ * Wire the /contextmeter status line + awareness hook into settings.json (default-on).
26
+ * Mirrors mergeSettingsHook: set `statusLine` only when the project doesn't already have
27
+ * one (never clobber a user's), and append the UserPromptSubmit awareness hook unless an
28
+ * equivalent is already present (idempotent). Defaults (warn 80 / crit 92) live in the
29
+ * scripts, so the wired commands carry no env prefix.
30
+ *
31
+ * Shared by `init` (copyMethodology) and `update` (applyUpdate) so `update` auto-activates
32
+ * the meter the same way `init` does (#384 follow-up). Returns `true` when it makes — or,
33
+ * under `{ dryRun: true }`, WOULD make — a change, so the updater can report the pending
34
+ * settings edit and decide `applied`. `snippetDir` lets the dry-run read the snippet from
35
+ * the methodology SOURCE before the scripts have been copied into the project.
36
+ */
37
+ export declare function mergeStatuslineSettings(projectDir: string, opts?: {
38
+ dryRun?: boolean;
39
+ snippetDir?: string;
40
+ }): Promise<boolean>;
24
41
  export declare function createProject(config: ProjectConfig): Promise<ProjectResult>;
@@ -206,17 +206,30 @@ async function mergeSettingsHook(projectDir) {
206
206
  * one (never clobber a user's), and append the UserPromptSubmit awareness hook unless an
207
207
  * equivalent is already present (idempotent). Defaults (warn 80 / crit 92) live in the
208
208
  * scripts, so the wired commands carry no env prefix.
209
+ *
210
+ * Shared by `init` (copyMethodology) and `update` (applyUpdate) so `update` auto-activates
211
+ * the meter the same way `init` does (#384 follow-up). Returns `true` when it makes — or,
212
+ * under `{ dryRun: true }`, WOULD make — a change, so the updater can report the pending
213
+ * settings edit and decide `applied`. `snippetDir` lets the dry-run read the snippet from
214
+ * the methodology SOURCE before the scripts have been copied into the project.
209
215
  */
210
- async function mergeStatuslineSettings(projectDir) {
211
- const snippetPath = join(projectDir, 'scripts', 'statusline', 'settings-snippet.json');
216
+ export async function mergeStatuslineSettings(projectDir, opts = {}) {
217
+ const snippetDir = opts.snippetDir ?? join(projectDir, 'scripts', 'statusline');
218
+ const snippetPath = join(snippetDir, 'settings-snippet.json');
212
219
  const settingsPath = join(projectDir, '.claude', 'settings.json');
213
220
  if (!existsSync(snippetPath))
214
- return;
215
- const snippet = JSON.parse(await readFile(snippetPath, 'utf-8'));
221
+ return false;
222
+ let snippet;
223
+ try {
224
+ snippet = JSON.parse(await readFile(snippetPath, 'utf-8'));
225
+ }
226
+ catch {
227
+ return false;
228
+ }
216
229
  const snippetStatusLine = snippet?.statusLine;
217
230
  const snippetUserPrompt = (snippet?.hooks?.UserPromptSubmit ?? []);
218
231
  if (!snippetStatusLine && snippetUserPrompt.length === 0)
219
- return;
232
+ return false;
220
233
  let settings = {};
221
234
  if (existsSync(settingsPath)) {
222
235
  try {
@@ -224,15 +237,15 @@ async function mergeStatuslineSettings(projectDir) {
224
237
  }
225
238
  catch {
226
239
  // Existing settings.json is unreadable — leave it alone.
227
- return;
240
+ return false;
228
241
  }
229
242
  }
230
- else {
231
- await mkdir(join(projectDir, '.claude'), { recursive: true });
232
- }
243
+ let changed = false;
233
244
  // statusLine: never clobber a project's existing one.
234
245
  if (snippetStatusLine && !settings.statusLine) {
235
- settings.statusLine = snippetStatusLine;
246
+ if (!opts.dryRun)
247
+ settings.statusLine = snippetStatusLine;
248
+ changed = true;
236
249
  }
237
250
  // UserPromptSubmit: append the awareness hook unless an equivalent is already present.
238
251
  const existingHooks = (settings.hooks ?? {});
@@ -242,12 +255,19 @@ async function mergeStatuslineSettings(projectDir) {
242
255
  return hooks.some((h) => typeof h?.command === 'string' && h.command.includes('context-awareness-hook'));
243
256
  });
244
257
  if (!alreadyHasMeter && snippetUserPrompt.length > 0) {
245
- settings.hooks = {
246
- ...existingHooks,
247
- UserPromptSubmit: [...existingUserPrompt, ...snippetUserPrompt],
248
- };
258
+ if (!opts.dryRun) {
259
+ settings.hooks = {
260
+ ...existingHooks,
261
+ UserPromptSubmit: [...existingUserPrompt, ...snippetUserPrompt],
262
+ };
263
+ }
264
+ changed = true;
249
265
  }
250
- await writeFile(settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf-8');
266
+ if (changed && !opts.dryRun) {
267
+ await mkdir(join(projectDir, '.claude'), { recursive: true });
268
+ await writeFile(settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf-8');
269
+ }
270
+ return changed;
251
271
  }
252
272
  // ── Identity Injection ───────────────────────────────────
253
273
  async function injectIdentity(projectDir, config) {
@@ -8,6 +8,7 @@ import { join } from 'node:path';
8
8
  import { execSync } from 'node:child_process';
9
9
  import { readMarker, writeMarker, DEFAULT_CLAUDE_MD_STRATEGY } from './marker.js';
10
10
  import { planClaudeMdUpdate, UPSTREAM_SUFFIX } from './claude-md-strategy.js';
11
+ import { mergeStatuslineSettings } from './project-init.js';
11
12
  /**
12
13
  * Decide which `update` mode the given argv selects. Pure — no I/O, no exit.
13
14
  *
@@ -103,8 +104,9 @@ export async function diffMethodology(projectDir) {
103
104
  { src: 'docs/patterns', dest: 'docs/patterns' },
104
105
  { src: 'scripts/thumper', dest: 'scripts/thumper' },
105
106
  { src: 'scripts/surfer-gate', dest: 'scripts/surfer-gate' },
106
- // Context-meter status line + awareness hook (/contextmeter). Scripts propagate on
107
- // update; activation (statusLine + UserPromptSubmit hook in settings.json) stays opt-in.
107
+ // Context-meter status line + awareness hook (/contextmeter). Scripts propagate here;
108
+ // activation (statusLine + UserPromptSubmit hook in settings.json) is wired below the
109
+ // same way `init` does it, so `update` auto-activates the meter too (#384 follow-up).
108
110
  { src: 'scripts/statusline', dest: 'scripts/statusline' },
109
111
  ];
110
112
  // CLAUDE.md is handled via the non-destructive strategy mechanism (issue #368)
@@ -179,6 +181,22 @@ export async function diffMethodology(projectDir) {
179
181
  }
180
182
  }
181
183
  }
184
+ // /contextmeter activation (#384 follow-up): `update` now wires the statusLine +
185
+ // awareness hook into .claude/settings.json the same way `init` does. Report the pending
186
+ // settings change here so `--dry-run` shows it. The snippet is read from the SOURCE — the
187
+ // project may not have the statusline scripts yet on this update — and the check is
188
+ // idempotent + non-clobbering (it returns false once the meter is wired, or when the
189
+ // project already has its own statusLine).
190
+ const statuslineNeedsWiring = await mergeStatuslineSettings(projectDir, {
191
+ dryRun: true,
192
+ snippetDir: join(sourceRoot, 'scripts', 'statusline'),
193
+ });
194
+ if (statuslineNeedsWiring) {
195
+ const settingsEntry = '.claude/settings.json';
196
+ if (!plan.modified.includes(settingsEntry) && !plan.added.includes(settingsEntry)) {
197
+ plan.modified.push(settingsEntry);
198
+ }
199
+ }
182
200
  return plan;
183
201
  }
184
202
  // ── Apply Update ─────────────────────────────────────────
@@ -214,13 +232,20 @@ export async function applyUpdate(projectDir) {
214
232
  await writeFile(`${claudeMdDestPath}${UPSTREAM_SUFFIX}`, claudeMdPlan.sideFileContent, 'utf-8');
215
233
  }
216
234
  }
217
- // The CLAUDE.md plan entries are handled above — exclude them from the
218
- // generic verbatim copy loop so we don't double-write or clobber.
219
- const claudeMdEntries = new Set(['CLAUDE.md', `CLAUDE.md${UPSTREAM_SUFFIX}`]);
235
+ // Skip from the generic verbatim copy loop:
236
+ // - CLAUDE.md entries: handled above by the non-destructive strategy.
237
+ // - .claude/settings.json: wired below by mergeStatuslineSettings, not copied verbatim
238
+ // (the methodology source carries no project settings.json — copying it would throw,
239
+ // or in dev clobber the project with the methodology repo's own dogfood settings).
240
+ const skipVerbatim = new Set([
241
+ 'CLAUDE.md',
242
+ `CLAUDE.md${UPSTREAM_SUFFIX}`,
243
+ '.claude/settings.json',
244
+ ]);
220
245
  // Copy added + modified files
221
246
  const { mkdir } = await import('node:fs/promises');
222
247
  for (const file of [...plan.added, ...plan.modified]) {
223
- if (claudeMdEntries.has(file))
248
+ if (skipVerbatim.has(file))
224
249
  continue;
225
250
  const srcPath = join(sourceRoot, file);
226
251
  const destPath = join(projectDir, file);
@@ -228,6 +253,12 @@ export async function applyUpdate(projectDir) {
228
253
  await mkdir(destDir, { recursive: true });
229
254
  await cp(srcPath, destPath);
230
255
  }
256
+ // /contextmeter activation (#384 follow-up): wire the statusLine + awareness hook into
257
+ // .claude/settings.json so `update` matches `init`'s default-on behavior. The scripts
258
+ // were copied above; this turns the meter on. Idempotent + non-clobbering — it never
259
+ // overwrites a user's own statusLine and never duplicates the awareness hook, so it is
260
+ // safe to run on every update.
261
+ await mergeStatuslineSettings(projectDir);
231
262
  // Update marker version
232
263
  const marker = await readMarker(projectDir);
233
264
  if (marker) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "voidforge-build",
3
- "version": "23.21.0",
3
+ "version": "23.22.0",
4
4
  "description": "From nothing, everything. A methodology framework for building with Claude Code.",
5
5
  "type": "module",
6
6
  "engines": {
@@ -45,7 +45,7 @@
45
45
  "@aws-sdk/client-rds": "^3.700.0",
46
46
  "@aws-sdk/client-s3": "^3.700.0",
47
47
  "@aws-sdk/client-sts": "^3.700.0",
48
- "voidforge-build-methodology": "^23.21.0",
48
+ "voidforge-build-methodology": "^23.22.0",
49
49
  "node-pty": "^1.2.0-beta.12",
50
50
  "ws": "^8.19.0"
51
51
  },