@fugood/bricks-project 2.25.0-beta.50 → 2.25.0-beta.51

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/compile/index.ts CHANGED
@@ -584,7 +584,7 @@ function compileRunArray(run: unknown[]): unknown[] {
584
584
  /**
585
585
  * Compile typed TestCase to raw format (strips __typename)
586
586
  */
587
- const compileTestCase = (testCase: TestCase) => ({
587
+ export const compileTestCase = (testCase: TestCase) => ({
588
588
  id: testCase.id,
589
589
  name: testCase.name,
590
590
  hide_short_ref: testCase.hideShortRef,
@@ -606,7 +606,10 @@ const compileTestCase = (testCase: TestCase) => ({
606
606
  variable: cond.variable,
607
607
  operator: cond.operator,
608
608
  value: cond.value,
609
- jump_to: cond.jump_to,
609
+ // `jump_to` may be a getter (() => TestCase) for dynamic case ids (the project generator
610
+ // emits this form). Resolve it to its id like the `run` array does — otherwise the function
611
+ // is JSON-serialized to nothing and the conditional jump silently vanishes from the config.
612
+ jump_to: compileRunElement(cond.jump_to),
610
613
  }
611
614
  }),
612
615
  })
package/compile/util.ts CHANGED
@@ -18,13 +18,18 @@ const errorMsg = 'Not allow duplicate set property id between inputs / outputs /
18
18
  export const validateConfig = (config: ScriptConfig) => {
19
19
  // Skip input/output overlap validation in manual mode (allows same data node in both)
20
20
  if (config.trigger_mode === 'manual') return
21
- if (config.error && config.inputs[config.error]) {
21
+ // `inputs` is keyed by data-node id with the OBJECT_SET path as the *value*. The overlap
22
+ // check is "is this id also an input?" — a key-existence question — so test key presence,
23
+ // not the path's truthiness. An empty path ('' = OBJECT_SET's default "set whole object")
24
+ // is a legitimate input value that would otherwise slip the overlap guard.
25
+ const isInput = (id: string) => Object.prototype.hasOwnProperty.call(config.inputs, id)
26
+ if (config.error && isInput(config.error)) {
22
27
  throw new Error(`${errorMsg}. key: error`)
23
28
  }
24
- if (config.output && config.inputs[config.output]) {
29
+ if (config.output && isInput(config.output)) {
25
30
  throw new Error(`${errorMsg}. key: output`)
26
31
  }
27
- if (Object.values(config.outputs).some((value) => value.some((id) => config.inputs[id]))) {
32
+ if (Object.values(config.outputs).some((value) => value.some(isInput))) {
28
33
  throw new Error(`${errorMsg}. key: outputs`)
29
34
  }
30
35
  // The same data-node id reused across the output-side targets (output / error / outputs)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fugood/bricks-project",
3
- "version": "2.25.0-beta.50",
3
+ "version": "2.25.0-beta.51",
4
4
  "main": "index.ts",
5
5
  "scripts": {
6
6
  "typecheck": "tsc --noEmit",
@@ -11,7 +11,7 @@
11
11
  "@babel/parser": "7.28.5",
12
12
  "@babel/traverse": "7.28.5",
13
13
  "@babel/types": "7.28.5",
14
- "@fugood/bricks-cli": "^2.25.0-beta.50",
14
+ "@fugood/bricks-cli": "^2.25.0-beta.51",
15
15
  "@huggingface/gguf": "^0.3.2",
16
16
  "@iarna/toml": "^3.0.0",
17
17
  "@modelcontextprotocol/sdk": "^1.15.0",
package/package.json.bak CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fugood/bricks-ctor",
3
- "version": "2.25.0-beta.50",
3
+ "version": "2.25.0-beta.51",
4
4
  "main": "index.ts",
5
5
  "scripts": {
6
6
  "typecheck": "tsc --noEmit",
@@ -11,7 +11,7 @@
11
11
  "@babel/parser": "7.28.5",
12
12
  "@babel/traverse": "7.28.5",
13
13
  "@babel/types": "7.28.5",
14
- "@fugood/bricks-cli": "^2.25.0-beta.50",
14
+ "@fugood/bricks-cli": "^2.25.0-beta.51",
15
15
  "@huggingface/gguf": "^0.3.2",
16
16
  "@iarna/toml": "^3.0.0",
17
17
  "@modelcontextprotocol/sdk": "^1.15.0",
@@ -0,0 +1,42 @@
1
+ import { readFile, writeFile, stat } from 'fs/promises'
2
+
3
+ const exists = async (f: string) => {
4
+ try {
5
+ await stat(f)
6
+ return true
7
+ } catch {
8
+ return false
9
+ }
10
+ }
11
+
12
+ // Refresh the `bricks-ctor` entry in a project's `.mcp.json` while preserving every other MCP
13
+ // server the user configured. A malformed `.mcp.json` is left untouched (with a warning)
14
+ // rather than overwritten with the default — clobbering it would silently delete the user's
15
+ // other server entries. This mirrors `setupClaudeAutoMode`'s handling of a malformed
16
+ // `settings.local.json` in postinstall.ts.
17
+ export const handleMcpConfigOverride = async (mcpConfigPath: string, projectMcpServer: object) => {
18
+ let mcpConfig: { mcpServers: Record<string, unknown> }
19
+ if (await exists(mcpConfigPath)) {
20
+ let parsed: unknown
21
+ try {
22
+ parsed = JSON.parse(await readFile(mcpConfigPath, 'utf-8'))
23
+ } catch {
24
+ console.warn(`Skipping .mcp.json update; ${mcpConfigPath} is not valid JSON`)
25
+ return
26
+ }
27
+ mcpConfig =
28
+ parsed && typeof parsed === 'object'
29
+ ? (parsed as { mcpServers: Record<string, unknown> })
30
+ : { mcpServers: {} }
31
+ if (!mcpConfig.mcpServers || typeof mcpConfig.mcpServers !== 'object') {
32
+ mcpConfig.mcpServers = {}
33
+ }
34
+ mcpConfig.mcpServers['bricks-ctor'] = projectMcpServer
35
+ delete mcpConfig.mcpServers['bricks-project']
36
+ } else {
37
+ mcpConfig = { mcpServers: { 'bricks-ctor': projectMcpServer } }
38
+ }
39
+
40
+ await writeFile(mcpConfigPath, `${JSON.stringify(mcpConfig, null, 2)}\n`)
41
+ console.log(`Updated ${mcpConfigPath}`)
42
+ }
@@ -12,6 +12,7 @@ import {
12
12
  } from 'fs/promises'
13
13
  import * as path from 'path'
14
14
  import TOML from '@iarna/toml'
15
+ import { handleMcpConfigOverride } from './_mcp-config'
15
16
 
16
17
  const cwd = process.cwd()
17
18
  const projectSkillsDir = path.join(cwd, '.bricks', 'skills')
@@ -80,41 +81,13 @@ type CodexMcpConfig = {
80
81
  mcp_servers: Record<string, typeof codexProjectMcpServer | typeof projectMcpServer>
81
82
  }
82
83
 
83
- // Claude Code and AGENTS.md projects both use the shared project .mcp.json file.
84
- const defaultMcpConfig = {
85
- mcpServers: {
86
- 'bricks-ctor': projectMcpServer,
87
- },
88
- }
89
-
90
- const handleMcpConfigOverride = async (mcpConfigPath: string) => {
91
- let mcpConfig: { mcpServers: Record<string, typeof projectMcpServer> } | null = null
92
- if (await exists(mcpConfigPath)) {
93
- const configStr = await readFile(mcpConfigPath, 'utf-8')
94
- try {
95
- mcpConfig = JSON.parse(configStr)
96
- if (!mcpConfig?.mcpServers) throw new Error('mcpServers is not defined')
97
- mcpConfig.mcpServers['bricks-ctor'] = projectMcpServer
98
- delete mcpConfig.mcpServers['bricks-project']
99
- } catch {
100
- mcpConfig = defaultMcpConfig
101
- }
102
- } else {
103
- mcpConfig = defaultMcpConfig
104
- }
105
-
106
- await writeFile(mcpConfigPath, `${JSON.stringify(mcpConfig, null, 2)}\n`)
107
-
108
- console.log(`Updated ${mcpConfigPath}`)
109
- }
110
-
111
84
  const hasClaudeCode = await exists(`${cwd}/CLAUDE.md`)
112
85
  const hasAgentsMd = await exists(`${cwd}/AGENTS.md`)
113
86
 
114
87
  if (hasClaudeCode || hasAgentsMd) {
115
88
  // Keep the workspace-level JSON MCP config aligned for tools that read .mcp.json.
116
89
  const mcpConfigPath = `${cwd}/.mcp.json`
117
- await handleMcpConfigOverride(mcpConfigPath)
90
+ await handleMcpConfigOverride(mcpConfigPath, projectMcpServer)
118
91
  }
119
92
 
120
93
  const copyMissingSkills = async (sourceDir: string, targetDir: string) => {
@@ -282,18 +255,25 @@ if (hasAgentsMd) {
282
255
  }
283
256
 
284
257
  const handleCodexMcpConfigOverride = async (mcpConfigPath: string) => {
285
- let mcpConfig: CodexMcpConfig | null = null
258
+ let mcpConfig: CodexMcpConfig
286
259
  if (await exists(mcpConfigPath)) {
287
- const configStr = await readFile(mcpConfigPath, 'utf-8')
260
+ let parsed: unknown
288
261
  try {
289
- const parsed = TOML.parse(configStr) as Partial<CodexMcpConfig>
290
- if (!parsed?.mcp_servers) throw new Error('mcp_servers is not defined')
291
- mcpConfig = { mcp_servers: parsed.mcp_servers }
292
- mcpConfig.mcp_servers['bricks-ctor'] = codexProjectMcpServer
293
- delete mcpConfig.mcp_servers['bricks-project']
262
+ parsed = TOML.parse(await readFile(mcpConfigPath, 'utf-8'))
294
263
  } catch {
295
- mcpConfig = defaultCodexMcpConfig
264
+ // A malformed config is left untouched (with a warning) rather than overwritten with
265
+ // the default — clobbering it would silently delete the user's other server entries.
266
+ // Mirrors handleMcpConfigOverride's handling of a malformed .mcp.json.
267
+ console.warn(`Skipping .codex/config.toml update; ${mcpConfigPath} is not valid TOML`)
268
+ return
269
+ }
270
+ mcpConfig =
271
+ parsed && typeof parsed === 'object' ? (parsed as CodexMcpConfig) : { mcp_servers: {} }
272
+ if (!mcpConfig.mcp_servers || typeof mcpConfig.mcp_servers !== 'object') {
273
+ mcpConfig.mcp_servers = {}
296
274
  }
275
+ mcpConfig.mcp_servers['bricks-ctor'] = codexProjectMcpServer
276
+ delete mcpConfig.mcp_servers['bricks-project']
297
277
  } else {
298
278
  mcpConfig = defaultCodexMcpConfig
299
279
  }