@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.
- package/compile/config-diff.ts +102 -0
- package/compile/index.ts +39 -7
- package/compile/util.ts +10 -7
- package/package.json +6 -2
- package/package.json.bak +6 -2
- package/skills/bricks-ctor/SKILL.md +2 -0
- package/skills/bricks-ctor/references/architecture-patterns.md +6 -0
- package/skills/bricks-ctor/references/source-editing-tools.md +81 -0
- package/skills/bricks-ctor/references/verification-toolchain.md +2 -0
- package/tools/_edits-log.ts +41 -0
- package/tools/mcp-env.ts +13 -0
- package/tools/mcp-server.ts +8 -0
- package/tools/mcp-tools/_verify.ts +50 -0
- package/tools/mcp-tools/compile.ts +2 -0
- package/tools/mcp-tools/data-calc-editing.ts +1395 -0
- package/tools/mcp-tools/entry-editing.ts +2368 -0
- package/tools/postinstall.ts +80 -3
- package/types/data-calc-command/color.d.ts +1 -1
- package/types/data-calc-command/datetime.d.ts +2 -2
- package/utils/calc.ts +5 -1
- package/utils/data.ts +3 -5
- package/utils/id.ts +39 -37
package/tools/postinstall.ts
CHANGED
|
@@ -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':
|
|
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'] =
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
85
|
-
break
|
|
75
|
+
return 'ANIMATION_'
|
|
86
76
|
case 'brick':
|
|
87
|
-
|
|
88
|
-
break
|
|
77
|
+
return 'BRICK_'
|
|
89
78
|
case 'dynamic-brick':
|
|
90
|
-
|
|
91
|
-
break
|
|
79
|
+
return 'DYNAMIC_BRICK_'
|
|
92
80
|
case 'canvas':
|
|
93
|
-
|
|
94
|
-
break
|
|
81
|
+
return 'CANVAS_'
|
|
95
82
|
case 'generator':
|
|
96
|
-
|
|
97
|
-
break
|
|
83
|
+
return 'GENERATOR_'
|
|
98
84
|
case 'data':
|
|
99
|
-
|
|
100
|
-
break
|
|
85
|
+
return 'PROPERTY_BANK_DATA_NODE_'
|
|
101
86
|
case 'switch':
|
|
102
|
-
|
|
103
|
-
break
|
|
87
|
+
return 'BRICK_STATE_GROUP_'
|
|
104
88
|
case 'property_bank_command':
|
|
105
|
-
|
|
106
|
-
break
|
|
89
|
+
return 'PROPERTY_BANK_COMMAND_NODE_'
|
|
107
90
|
case 'property_bank_calc':
|
|
108
|
-
|
|
109
|
-
break
|
|
91
|
+
return 'PROPERTY_BANK_COMMAND_MAP_'
|
|
110
92
|
case 'automation_map':
|
|
111
|
-
|
|
112
|
-
break
|
|
93
|
+
return 'AUTOMATION_MAP_'
|
|
113
94
|
case 'test':
|
|
114
|
-
|
|
115
|
-
break
|
|
95
|
+
return 'TEST_'
|
|
116
96
|
case 'test_case':
|
|
117
|
-
|
|
118
|
-
break
|
|
97
|
+
return 'TEST_CASE_'
|
|
119
98
|
case 'test_var':
|
|
120
|
-
|
|
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 `${
|
|
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
|
}
|