voidforge-build 23.19.0 → 23.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/dist/.claude/agents/celebrimbor-forge-artist.md +1 -0
  2. package/dist/.claude/agents/ducem-token-economics.md +1 -0
  3. package/dist/.claude/agents/galadriel-frontend.md +1 -0
  4. package/dist/.claude/agents/romanoff-integrations.md +4 -0
  5. package/dist/.claude/agents/silver-surfer-herald.md +19 -4
  6. package/dist/.claude/commands/architect.md +4 -3
  7. package/dist/.claude/commands/assemble.md +12 -0
  8. package/dist/.claude/commands/assess.md +1 -0
  9. package/dist/.claude/commands/build.md +8 -0
  10. package/dist/.claude/commands/contextmeter.md +56 -0
  11. package/dist/.claude/commands/debrief.md +10 -0
  12. package/dist/.claude/commands/engage.md +5 -0
  13. package/dist/.claude/commands/git.md +13 -1
  14. package/dist/.claude/commands/imagine.md +1 -1
  15. package/dist/.claude/commands/seal.md +80 -0
  16. package/dist/.claude/commands/ux.md +13 -0
  17. package/dist/.claude/workflows/gauntlet.workflow.js +13 -1
  18. package/dist/CHANGELOG.md +38 -0
  19. package/dist/CLAUDE.md +8 -0
  20. package/dist/HOLOCRON.md +16 -2
  21. package/dist/VERSION.md +2 -1
  22. package/dist/docs/methods/AI_INTELLIGENCE.md +3 -0
  23. package/dist/docs/methods/ASSEMBLER.md +12 -0
  24. package/dist/docs/methods/BUILD_PROTOCOL.md +7 -0
  25. package/dist/docs/methods/CAMPAIGN.md +11 -0
  26. package/dist/docs/methods/DEVOPS_ENGINEER.md +56 -0
  27. package/dist/docs/methods/FIELD_MEDIC.md +1 -0
  28. package/dist/docs/methods/FORGE_ARTIST.md +3 -4
  29. package/dist/docs/methods/GAUNTLET.md +6 -0
  30. package/dist/docs/methods/MUSTER.md +2 -0
  31. package/dist/docs/methods/PRODUCT_DESIGN_FRONTEND.md +18 -0
  32. package/dist/docs/methods/QA_ENGINEER.md +17 -1
  33. package/dist/docs/methods/RELEASE_MANAGER.md +27 -0
  34. package/dist/docs/methods/SECURITY_AUDITOR.md +11 -1
  35. package/dist/docs/methods/SUB_AGENTS.md +31 -0
  36. package/dist/docs/methods/SYSTEMS_ARCHITECT.md +15 -0
  37. package/dist/docs/methods/TESTING.md +2 -0
  38. package/dist/docs/methods/TROUBLESHOOTING.md +2 -2
  39. package/dist/docs/methods/WORKFLOWS.md +14 -0
  40. package/dist/docs/patterns/ai-prompt-safety.ts +85 -0
  41. package/dist/docs/patterns/data-pipeline.ts +59 -1
  42. package/dist/docs/patterns/exclusion-set-invariant.md +62 -0
  43. package/dist/docs/patterns/multi-tenant-property-test.ts +64 -0
  44. package/dist/docs/patterns/oauth-token-lifecycle.ts +21 -0
  45. package/dist/scripts/statusline/README.md +38 -0
  46. package/dist/scripts/statusline/context-awareness-hook.sh +53 -0
  47. package/dist/scripts/statusline/settings-snippet.json +17 -0
  48. package/dist/scripts/statusline/voidforge-statusline.sh +91 -0
  49. package/dist/scripts/voidforge.js +69 -6
  50. package/dist/wizard/lib/claude-md-strategy.d.ts +87 -0
  51. package/dist/wizard/lib/claude-md-strategy.js +198 -0
  52. package/dist/wizard/lib/marker.d.ts +48 -1
  53. package/dist/wizard/lib/marker.js +58 -2
  54. package/dist/wizard/lib/patterns/oauth-token-lifecycle.d.ts +14 -0
  55. package/dist/wizard/lib/patterns/oauth-token-lifecycle.js +21 -0
  56. package/dist/wizard/lib/project-init.js +59 -0
  57. package/dist/wizard/lib/updater.d.ts +19 -0
  58. package/dist/wizard/lib/updater.js +84 -33
  59. package/package.json +2 -2
@@ -141,6 +141,16 @@ async function copyMethodology(methodologyRoot, projectDir, core) {
141
141
  await chmodShellScripts(join(projectDir, 'scripts', 'surfer-gate'));
142
142
  await mergeSettingsHook(projectDir);
143
143
  }
144
+ // Context-meter status line (/contextmeter) — default-on. Ship the scripts AND wire
145
+ // the statusLine + UserPromptSubmit awareness hook into settings.json, the same way the
146
+ // surfer-gate hook is wired. Defaults: warn 80% / crit 92% (baked into the scripts).
147
+ // Opt out per project with `/contextmeter --uninstall`.
148
+ const statuslineSrc = join(methodologyRoot, 'scripts', 'statusline');
149
+ if (existsSync(statuslineSrc)) {
150
+ count += await copyDir(statuslineSrc, join(projectDir, 'scripts', 'statusline'));
151
+ await chmodShellScripts(join(projectDir, 'scripts', 'statusline'));
152
+ await mergeStatuslineSettings(projectDir);
153
+ }
144
154
  return count;
145
155
  }
146
156
  async function chmodShellScripts(dir) {
@@ -190,6 +200,55 @@ async function mergeSettingsHook(projectDir) {
190
200
  };
191
201
  await writeFile(settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf-8');
192
202
  }
203
+ /**
204
+ * Wire the /contextmeter status line + awareness hook into settings.json (default-on).
205
+ * Mirrors mergeSettingsHook: set `statusLine` only when the project doesn't already have
206
+ * one (never clobber a user's), and append the UserPromptSubmit awareness hook unless an
207
+ * equivalent is already present (idempotent). Defaults (warn 80 / crit 92) live in the
208
+ * scripts, so the wired commands carry no env prefix.
209
+ */
210
+ async function mergeStatuslineSettings(projectDir) {
211
+ const snippetPath = join(projectDir, 'scripts', 'statusline', 'settings-snippet.json');
212
+ const settingsPath = join(projectDir, '.claude', 'settings.json');
213
+ if (!existsSync(snippetPath))
214
+ return;
215
+ const snippet = JSON.parse(await readFile(snippetPath, 'utf-8'));
216
+ const snippetStatusLine = snippet?.statusLine;
217
+ const snippetUserPrompt = (snippet?.hooks?.UserPromptSubmit ?? []);
218
+ if (!snippetStatusLine && snippetUserPrompt.length === 0)
219
+ return;
220
+ let settings = {};
221
+ if (existsSync(settingsPath)) {
222
+ try {
223
+ settings = JSON.parse(await readFile(settingsPath, 'utf-8'));
224
+ }
225
+ catch {
226
+ // Existing settings.json is unreadable — leave it alone.
227
+ return;
228
+ }
229
+ }
230
+ else {
231
+ await mkdir(join(projectDir, '.claude'), { recursive: true });
232
+ }
233
+ // statusLine: never clobber a project's existing one.
234
+ if (snippetStatusLine && !settings.statusLine) {
235
+ settings.statusLine = snippetStatusLine;
236
+ }
237
+ // UserPromptSubmit: append the awareness hook unless an equivalent is already present.
238
+ const existingHooks = (settings.hooks ?? {});
239
+ const existingUserPrompt = (existingHooks.UserPromptSubmit ?? []);
240
+ const alreadyHasMeter = existingUserPrompt.some((entry) => {
241
+ const hooks = (entry?.hooks ?? []);
242
+ return hooks.some((h) => typeof h?.command === 'string' && h.command.includes('context-awareness-hook'));
243
+ });
244
+ if (!alreadyHasMeter && snippetUserPrompt.length > 0) {
245
+ settings.hooks = {
246
+ ...existingHooks,
247
+ UserPromptSubmit: [...existingUserPrompt, ...snippetUserPrompt],
248
+ };
249
+ }
250
+ await writeFile(settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf-8');
251
+ }
193
252
  // ── Identity Injection ───────────────────────────────────
194
253
  async function injectIdentity(projectDir, config) {
195
254
  const claudePath = join(projectDir, 'CLAUDE.md');
@@ -2,17 +2,36 @@
2
2
  * Update mechanisms — methodology update (replaces /void git-fetch),
3
3
  * self-update, and extension update.
4
4
  */
5
+ import type { ClaudeMdAction } from './claude-md-strategy.js';
5
6
  export interface UpdatePlan {
6
7
  added: string[];
7
8
  modified: string[];
8
9
  removed: string[];
9
10
  unchanged: number;
11
+ /** Non-destructive CLAUDE.md handling (issue #368). */
12
+ claudeMd?: {
13
+ action: ClaudeMdAction;
14
+ droppedSections: string[];
15
+ warnings: string[];
16
+ /** Side file written instead of CLAUDE.md, relative to project root. */
17
+ sideFile?: string;
18
+ };
10
19
  }
11
20
  export interface UpdateResult {
12
21
  applied: boolean;
13
22
  plan: UpdatePlan;
14
23
  newVersion: string;
15
24
  }
25
+ export type UpdateMode = 'help' | 'self' | 'extensions' | 'methodology';
26
+ /**
27
+ * Decide which `update` mode the given argv selects. Pure — no I/O, no exit.
28
+ *
29
+ * Help MUST win over every action flag (issue #368): `update --help` printed
30
+ * usage but the OLD router fell through and EXECUTED the (destructive) update.
31
+ * Centralizing the precedence here makes that ordering testable and keeps the
32
+ * CLI from re-introducing the bug.
33
+ */
34
+ export declare function resolveUpdateMode(args: string[]): UpdateMode;
16
35
  /**
17
36
  * Diff methodology source against project files.
18
37
  * Returns a plan showing what would change.
@@ -2,11 +2,29 @@
2
2
  * Update mechanisms — methodology update (replaces /void git-fetch),
3
3
  * self-update, and extension update.
4
4
  */
5
- import { readFile, readdir, cp } from 'node:fs/promises';
5
+ import { readFile, readdir, cp, writeFile } from 'node:fs/promises';
6
6
  import { existsSync } from 'node:fs';
7
7
  import { join } from 'node:path';
8
8
  import { execSync } from 'node:child_process';
9
- import { readMarker, writeMarker } from './marker.js';
9
+ import { readMarker, writeMarker, DEFAULT_CLAUDE_MD_STRATEGY } from './marker.js';
10
+ import { planClaudeMdUpdate, UPSTREAM_SUFFIX } from './claude-md-strategy.js';
11
+ /**
12
+ * Decide which `update` mode the given argv selects. Pure — no I/O, no exit.
13
+ *
14
+ * Help MUST win over every action flag (issue #368): `update --help` printed
15
+ * usage but the OLD router fell through and EXECUTED the (destructive) update.
16
+ * Centralizing the precedence here makes that ordering testable and keeps the
17
+ * CLI from re-introducing the bug.
18
+ */
19
+ export function resolveUpdateMode(args) {
20
+ if (args.includes('--help') || args.includes('-h'))
21
+ return 'help';
22
+ if (args.includes('--self'))
23
+ return 'self';
24
+ if (args.includes('--extensions'))
25
+ return 'extensions';
26
+ return 'methodology';
27
+ }
10
28
  // ── Methodology Source Resolution ────────────────────────
11
29
  async function resolveMethodologySource() {
12
30
  const dir = import.meta.dirname ?? new URL('.', import.meta.url).pathname;
@@ -47,6 +65,23 @@ async function collectFiles(dir, base = '') {
47
65
  }
48
66
  return files;
49
67
  }
68
+ /**
69
+ * Compute the non-destructive CLAUDE.md plan for a project (issue #368).
70
+ * Reads the marker's `claudeMd` strategy (default 'preserve') and delegates the
71
+ * decision to the pure planner. Returns null only when there is no upstream
72
+ * CLAUDE.md to apply.
73
+ */
74
+ async function planClaudeMd(sourceRoot, projectDir) {
75
+ const srcPath = join(sourceRoot, 'CLAUDE.md');
76
+ if (!existsSync(srcPath))
77
+ return null;
78
+ const upstream = await readFile(srcPath, 'utf-8');
79
+ const destPath = join(projectDir, 'CLAUDE.md');
80
+ const current = existsSync(destPath) ? await readFile(destPath, 'utf-8') : null;
81
+ const marker = await readMarker(projectDir);
82
+ const strategy = marker?.claudeMd ?? DEFAULT_CLAUDE_MD_STRATEGY;
83
+ return planClaudeMdUpdate(current, upstream, strategy);
84
+ }
50
85
  /**
51
86
  * Diff methodology source against project files.
52
87
  * Returns a plan showing what would change.
@@ -68,9 +103,33 @@ export async function diffMethodology(projectDir) {
68
103
  { src: 'docs/patterns', dest: 'docs/patterns' },
69
104
  { src: 'scripts/thumper', dest: 'scripts/thumper' },
70
105
  { 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.
108
+ { src: 'scripts/statusline', dest: 'scripts/statusline' },
71
109
  ];
72
- // Single files to compare
73
- const singleFiles = ['CLAUDE.md', 'HOLOCRON.md', 'VERSION.md'];
110
+ // CLAUDE.md is handled via the non-destructive strategy mechanism (issue #368)
111
+ // never the old "preserve first 10 lines, overwrite the rest" clobber.
112
+ const claudeMdPlan = await planClaudeMd(sourceRoot, projectDir);
113
+ if (claudeMdPlan) {
114
+ plan.claudeMd = {
115
+ action: claudeMdPlan.action,
116
+ droppedSections: claudeMdPlan.droppedSections,
117
+ warnings: claudeMdPlan.warnings,
118
+ sideFile: claudeMdPlan.sideFileContent !== null ? `CLAUDE.md${UPSTREAM_SUFFIX}` : undefined,
119
+ };
120
+ if (claudeMdPlan.action === 'unchanged' || claudeMdPlan.action === 'skip') {
121
+ plan.unchanged++;
122
+ }
123
+ else if (claudeMdPlan.action === 'side-file') {
124
+ // The side file is the only thing that changes; CLAUDE.md itself is untouched.
125
+ plan.modified.push(`CLAUDE.md${UPSTREAM_SUFFIX}`);
126
+ }
127
+ else {
128
+ plan.modified.push('CLAUDE.md');
129
+ }
130
+ }
131
+ // Other single files compare/copy verbatim (no special preservation needed).
132
+ const singleFiles = ['HOLOCRON.md', 'VERSION.md'];
74
133
  // Check single files
75
134
  for (const file of singleFiles) {
76
135
  const srcPath = join(sourceRoot, file);
@@ -83,21 +142,10 @@ export async function diffMethodology(projectDir) {
83
142
  else {
84
143
  const srcContent = await readFile(srcPath, 'utf-8');
85
144
  const destContent = await readFile(destPath, 'utf-8');
86
- // Skip CLAUDE.md first 10 lines (project identity) when comparing
87
- if (file === 'CLAUDE.md') {
88
- const srcLines = srcContent.split('\n').slice(10).join('\n');
89
- const destLines = destContent.split('\n').slice(10).join('\n');
90
- if (srcLines !== destLines)
91
- plan.modified.push(file);
92
- else
93
- plan.unchanged++;
94
- }
95
- else {
96
- if (srcContent !== destContent)
97
- plan.modified.push(file);
98
- else
99
- plan.unchanged++;
100
- }
145
+ if (srcContent !== destContent)
146
+ plan.modified.push(file);
147
+ else
148
+ plan.unchanged++;
101
149
  }
102
150
  }
103
151
  // Check directories
@@ -153,24 +201,27 @@ export async function applyUpdate(projectDir) {
153
201
  if (plan.added.length === 0 && plan.modified.length === 0) {
154
202
  return { applied: false, plan, newVersion };
155
203
  }
204
+ // CLAUDE.md: apply the non-destructive strategy decision (issue #368).
205
+ // Never overwrite a customized CLAUDE.md in place. `preserve` writes a side
206
+ // file; `merge` replaces only the fenced block; `skip` does nothing.
207
+ const claudeMdPlan = await planClaudeMd(sourceRoot, projectDir);
208
+ const claudeMdDestPath = join(projectDir, 'CLAUDE.md');
209
+ if (claudeMdPlan) {
210
+ if (claudeMdPlan.claudeMdContent !== null) {
211
+ await writeFile(claudeMdDestPath, claudeMdPlan.claudeMdContent, 'utf-8');
212
+ }
213
+ if (claudeMdPlan.sideFileContent !== null) {
214
+ await writeFile(`${claudeMdDestPath}${UPSTREAM_SUFFIX}`, claudeMdPlan.sideFileContent, 'utf-8');
215
+ }
216
+ }
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}`]);
156
220
  // Copy added + modified files
157
221
  const { mkdir } = await import('node:fs/promises');
158
222
  for (const file of [...plan.added, ...plan.modified]) {
159
- // Special handling for CLAUDE.md: preserve project identity (first 10 lines)
160
- if (file === 'CLAUDE.md') {
161
- const srcContent = await readFile(join(sourceRoot, file), 'utf-8');
162
- const destPath = join(projectDir, file);
163
- if (existsSync(destPath)) {
164
- const destContent = await readFile(destPath, 'utf-8');
165
- const destIdentity = destContent.split('\n').slice(0, 10).join('\n');
166
- const srcBody = srcContent.split('\n').slice(10).join('\n');
167
- await import('node:fs/promises').then(fs => fs.writeFile(destPath, destIdentity + '\n' + srcBody, 'utf-8'));
168
- }
169
- else {
170
- await cp(join(sourceRoot, file), destPath);
171
- }
223
+ if (claudeMdEntries.has(file))
172
224
  continue;
173
- }
174
225
  const srcPath = join(sourceRoot, file);
175
226
  const destPath = join(projectDir, file);
176
227
  const destDir = join(destPath, '..');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "voidforge-build",
3
- "version": "23.19.0",
3
+ "version": "23.20.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.19.0",
48
+ "voidforge-build-methodology": "^23.20.0",
49
49
  "node-pty": "^1.2.0-beta.12",
50
50
  "ws": "^8.19.0"
51
51
  },