@fugood/bricks-project 2.25.0-beta.42 → 2.25.0-beta.45

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.
@@ -69,8 +69,15 @@ const projectMcpServer = {
69
69
  args: [`${cwd}/node_modules/@fugood/bricks-ctor/tools/mcp-server.ts`],
70
70
  }
71
71
 
72
+ // Codex cancels MCP tool calls it cannot prompt approval for (e.g. `codex exec`),
73
+ // so the project-local server's tools must be pre-approved in its config entry.
74
+ const codexProjectMcpServer = {
75
+ ...projectMcpServer,
76
+ default_tools_approval_mode: 'approve',
77
+ }
78
+
72
79
  type CodexMcpConfig = {
73
- mcp_servers: Record<string, typeof projectMcpServer>
80
+ mcp_servers: Record<string, typeof codexProjectMcpServer | typeof projectMcpServer>
74
81
  }
75
82
 
76
83
  // Claude Code and AGENTS.md projects both use the shared project .mcp.json file.
@@ -196,11 +203,81 @@ if (hasClaudeCode || hasAgentsMd) {
196
203
  await setupSkills()
197
204
  }
198
205
 
206
+ type ClaudeSettings = {
207
+ autoMode?: {
208
+ environment?: string[]
209
+ allow?: string[]
210
+ soft_deny?: string[]
211
+ hard_deny?: string[]
212
+ }
213
+ [key: string]: unknown
214
+ }
215
+
216
+ // Trusted infrastructure for auto mode's classifier. `$defaults` keeps the
217
+ // built-in environment (the working repo and its git remotes); the extra
218
+ // entries stop routine syncs to the BRICKS backend from being treated as
219
+ // external exfiltration. See https://code.claude.com/docs/en/auto-mode-config
220
+ const autoModeEnvironment = [
221
+ '$defaults',
222
+ 'Organization: BRICKS (bricks.tools). Primary use: building BRICKS apps/modules with the bricks CLI and the local bricks-ctor MCP server.',
223
+ 'Trusted internal domains: all *.bricks.tools services — api.bricks.tools (project GraphQL API), bank.bricks.tools (config & asset Bank API), cdn.bricks.tools (asset CDN), plus the control/display/activity services. This project syncs its config and assets to these endpoints.',
224
+ ]
225
+
226
+ // `.claude/settings.local.json` is per-developer local config; keep it untracked.
227
+ const ensureSettingsLocalGitignored = async () => {
228
+ const gitignorePath = path.join(cwd, '.gitignore')
229
+ const entry = '.claude/settings.local.json'
230
+ const coveredBy = new Set([entry, '.claude', '.claude/', '.claude/*', '*.local.json'])
231
+
232
+ let content = ''
233
+ if (await exists(gitignorePath)) {
234
+ content = await readFile(gitignorePath, 'utf-8')
235
+ if (content.split('\n').some((line) => coveredBy.has(line.trim()))) return
236
+ }
237
+
238
+ const separator = content.length === 0 ? '' : content.endsWith('\n') ? '\n' : '\n\n'
239
+ await writeFile(gitignorePath, `${content}${separator}# Claude Code local settings\n${entry}\n`)
240
+ console.log(`Added ${entry} to .gitignore`)
241
+ }
242
+
243
+ // Pre-configure auto mode once, on initial setup. We only seed the classifier's
244
+ // trusted infrastructure — not `permissions.defaultMode: 'auto'`, which Claude
245
+ // Code ignores from project/local settings (a repo can't grant itself auto mode;
246
+ // it only takes effect from ~/.claude/settings.json). An existing autoMode block
247
+ // is left untouched so reinstalls never clobber a developer's customizations.
248
+ const setupClaudeAutoMode = async () => {
249
+ const settingsPath = path.join(cwd, '.claude', 'settings.local.json')
250
+
251
+ let settings: ClaudeSettings = {}
252
+ if (await exists(settingsPath)) {
253
+ try {
254
+ settings = JSON.parse(await readFile(settingsPath, 'utf-8'))
255
+ } catch {
256
+ console.warn(`Skipping auto mode setup; ${settingsPath} is not valid JSON`)
257
+ return
258
+ }
259
+ if (settings.autoMode) return
260
+ }
261
+
262
+ settings.autoMode = { environment: autoModeEnvironment }
263
+
264
+ await mkdir(path.dirname(settingsPath), { recursive: true })
265
+ await writeFile(settingsPath, `${JSON.stringify(settings, null, 2)}\n`)
266
+ console.log(`Set up auto mode in ${settingsPath}`)
267
+
268
+ await ensureSettingsLocalGitignored()
269
+ }
270
+
271
+ if (hasClaudeCode) {
272
+ // Pre-configure auto mode's trusted infrastructure for Claude Code projects.
273
+ await setupClaudeAutoMode()
274
+ }
275
+
199
276
  if (hasAgentsMd) {
200
277
  // Codex stores its project-local MCP config in .codex/config.toml.
201
278
  const defaultCodexMcpConfig = {
202
279
  mcp_servers: {
203
- 'bricks-ctor': projectMcpServer,
280
+ 'bricks-ctor': codexProjectMcpServer,
204
281
  },
205
282
  }
206
283
 
@@ -212,7 +289,7 @@ if (hasAgentsMd) {
212
289
  const parsed = TOML.parse(configStr) as Partial<CodexMcpConfig>
213
290
  if (!parsed?.mcp_servers) throw new Error('mcp_servers is not defined')
214
291
  mcpConfig = { mcp_servers: parsed.mcp_servers }
215
- mcpConfig.mcp_servers['bricks-ctor'] = projectMcpServer
292
+ mcpConfig.mcp_servers['bricks-ctor'] = codexProjectMcpServer
216
293
  delete mcpConfig.mcp_servers['bricks-project']
217
294
  } catch {
218
295
  mcpConfig = defaultCodexMcpConfig
@@ -250,7 +250,7 @@ export type DataCommandColorRandom = DataCommand & {
250
250
  outputs?: Array<DataCalcOutput<'result'> /* target: string */>
251
251
  }
252
252
 
253
- /* RGBA — Generate color string RGBA values */
253
+ /* RGBA — Generate a color from RGBA channels (red/green/blue: 0-255, alpha: 0-1) */
254
254
  export type DataCommandColorRgba = DataCommand & {
255
255
  __commandName: 'COLOR_RGBA'
256
256
  inputs?: Array<
@@ -22,7 +22,7 @@ export type DataCommandDatetimeDate = DataCommand & {
22
22
  outputs?: Array<DataCalcOutput<'result'> /* target: number */>
23
23
  }
24
24
 
25
- /* Day — Get day (Sunday is 7) */
25
+ /* Day — Get day of week (0-based: Sunday is 0, Saturday is 6) */
26
26
  export type DataCommandDatetimeDay = DataCommand & {
27
27
  __commandName: 'DATETIME_DAY'
28
28
  inputs?: Array<
@@ -64,7 +64,7 @@ export type DataCommandDatetimeMinute = DataCommand & {
64
64
  outputs?: Array<DataCalcOutput<'result'> /* target: number */>
65
65
  }
66
66
 
67
- /* Month — Get month (January is 1) */
67
+ /* Month — Get month (0-based: January is 0, December is 11) */
68
68
  export type DataCommandDatetimeMonth = DataCommand & {
69
69
  __commandName: 'DATETIME_MONTH'
70
70
  inputs?: Array<
package/utils/calc.ts CHANGED
@@ -28,6 +28,10 @@ export const generateDataCalculationMapEditorInfo = (
28
28
  DataCalculationData | DataCommand,
29
29
  Set<DataCalculationData | DataCommand>
30
30
  >()
31
+ const nodeById = new Map<string, DataCalculationData | DataCommand>()
32
+ for (const node of nodes) {
33
+ if ('id' in node) nodeById.set(node.id, node)
34
+ }
31
35
 
32
36
  // Analyze node connections
33
37
  nodes.forEach((node) => {
@@ -48,7 +52,7 @@ export const generateDataCalculationMapEditorInfo = (
48
52
  if (!connectedTo.has(node)) {
49
53
  connectedTo.set(node, new Set())
50
54
  }
51
- const sourceNode = nodes.find((n) => 'id' in n && n.id === conn.id)
55
+ const sourceNode = nodeById.get(conn.id)
52
56
  if (sourceNode) {
53
57
  connectedTo.get(node)!.add(sourceNode)
54
58
  }
package/utils/data.ts CHANGED
@@ -7,14 +7,12 @@ export const linkData: (dataGetter: () => Data) => DataLink = (dataGetter) => ({
7
7
  data: dataGetter,
8
8
  })
9
9
 
10
- const idOpts = {
11
- snapshotMode: process.env.BRICKS_SNAPSHOT_MODE === '1',
12
- }
13
-
14
10
  export const createCanvasIdRef: (canvasGetter: () => Canvas) => Data<string> = (canvasGetter) => {
15
11
  const data: Data<string> = {
16
12
  __typename: 'Data',
17
- id: makeId('data', idOpts),
13
+ // Stable by default (utils/id.ts) so recompiling unchanged source yields an
14
+ // identical config; the legacy snapshotMode opt-out made this id churn per compile.
15
+ id: makeId('data'),
18
16
  type: 'string',
19
17
  routing: 'read-only',
20
18
  kind: {
package/utils/id.ts CHANGED
@@ -69,63 +69,65 @@ const makeStableUuid = (type: string, alias?: string) => {
69
69
  })
70
70
  }
71
71
 
72
- // Make stable ids by default; explicit snapshotMode: false preserves the random escape hatch.
73
- export const makeId = (type: IdType, aliasOrOpts?: string | IdOptions, opts?: IdOptions) => {
74
- if (type === 'subspace') {
75
- throw new Error('Currently subspace is not supported for ID generation, please use a fixed ID')
76
- }
77
-
78
- const alias = typeof aliasOrOpts === 'string' ? aliasOrOpts : undefined
79
- const options = typeof aliasOrOpts === 'string' ? opts : (aliasOrOpts ?? opts)
80
-
81
- let prefix = ''
72
+ const idPrefix = (type: IdType): string => {
82
73
  switch (type) {
83
74
  case 'animation':
84
- prefix = 'ANIMATION_'
85
- break
75
+ return 'ANIMATION_'
86
76
  case 'brick':
87
- prefix = 'BRICK_'
88
- break
77
+ return 'BRICK_'
89
78
  case 'dynamic-brick':
90
- prefix = 'DYNAMIC_BRICK_'
91
- break
79
+ return 'DYNAMIC_BRICK_'
92
80
  case 'canvas':
93
- prefix = 'CANVAS_'
94
- break
81
+ return 'CANVAS_'
95
82
  case 'generator':
96
- prefix = 'GENERATOR_'
97
- break
83
+ return 'GENERATOR_'
98
84
  case 'data':
99
- prefix = 'PROPERTY_BANK_DATA_NODE_'
100
- break
85
+ return 'PROPERTY_BANK_DATA_NODE_'
101
86
  case 'switch':
102
- prefix = 'BRICK_STATE_GROUP_'
103
- break
87
+ return 'BRICK_STATE_GROUP_'
104
88
  case 'property_bank_command':
105
- prefix = 'PROPERTY_BANK_COMMAND_NODE_'
106
- break
89
+ return 'PROPERTY_BANK_COMMAND_NODE_'
107
90
  case 'property_bank_calc':
108
- prefix = 'PROPERTY_BANK_COMMAND_MAP_'
109
- break
91
+ return 'PROPERTY_BANK_COMMAND_MAP_'
110
92
  case 'automation_map':
111
- prefix = 'AUTOMATION_MAP_'
112
- break
93
+ return 'AUTOMATION_MAP_'
113
94
  case 'test':
114
- prefix = 'TEST_'
115
- break
95
+ return 'TEST_'
116
96
  case 'test_case':
117
- prefix = 'TEST_CASE_'
118
- break
97
+ return 'TEST_CASE_'
119
98
  case 'test_var':
120
- prefix = 'TEST_VAR_'
121
- break
99
+ return 'TEST_VAR_'
122
100
  default:
101
+ return ''
102
+ }
103
+ }
104
+
105
+ // Make stable ids by default; explicit snapshotMode: false preserves the random escape hatch.
106
+ export const makeId = (type: IdType, aliasOrOpts?: string | IdOptions, opts?: IdOptions) => {
107
+ if (type === 'subspace') {
108
+ throw new Error('Currently subspace is not supported for ID generation, please use a fixed ID')
123
109
  }
124
110
 
111
+ const alias = typeof aliasOrOpts === 'string' ? aliasOrOpts : undefined
112
+ const options = typeof aliasOrOpts === 'string' ? opts : (aliasOrOpts ?? opts)
113
+
125
114
  const useCountFallback = aliasOrOpts === undefined && opts === undefined
126
115
  const id =
127
116
  alias !== undefined || options?.snapshotMode || useCountFallback
128
117
  ? makeStableUuid(type, alias)
129
118
  : uuid()
130
- return `${prefix}${id}`
119
+ return `${idPrefix(type)}${id}`
120
+ }
121
+
122
+ // Deterministic id derived solely from a caller-supplied seed. Unlike makeId it keeps no
123
+ // global state — no incrementing counter, no alias registry — so the same (type, seed)
124
+ // always maps to the same id on every compile and in any process. Compiled artifacts seed
125
+ // their ids this way (e.g. generateCalulationMap's command nodes, seeded by their calc id +
126
+ // structural role) so unchanged source recompiles byte-identically and editing one calc
127
+ // never shifts another's ids. The seed must be unique within a single config.
128
+ export const makeSeededId = (type: IdType, seed: string) => {
129
+ if (type === 'subspace') {
130
+ throw new Error('Currently subspace is not supported for ID generation, please use a fixed ID')
131
+ }
132
+ return `${idPrefix(type)}${uuid({ random: hashToRandomBytes([readApplicationId(), type, seed]) })}`
131
133
  }