@fugood/bricks-project 2.25.0-beta.15 → 2.25.0-beta.17
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/package.json +2 -2
- package/package.json.bak +2 -2
- package/tools/_git-author.ts +10 -2
- package/tools/_last-pushed-commit.ts +28 -0
- package/tools/deploy.ts +7 -0
- package/tools/pull.ts +49 -14
- package/tools/update-config.ts +8 -0
- package/types/generators/Assistant.ts +18 -0
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fugood/bricks-project",
|
|
3
|
-
"version": "2.25.0-beta.
|
|
3
|
+
"version": "2.25.0-beta.17",
|
|
4
4
|
"main": "index.ts",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"typecheck": "tsc --noEmit",
|
|
7
7
|
"build": "bun scripts/build.js"
|
|
8
8
|
},
|
|
9
9
|
"dependencies": {
|
|
10
|
-
"@fugood/bricks-cli": "^2.25.0-beta.
|
|
10
|
+
"@fugood/bricks-cli": "^2.25.0-beta.17",
|
|
11
11
|
"@huggingface/gguf": "^0.3.2",
|
|
12
12
|
"@iarna/toml": "^3.0.0",
|
|
13
13
|
"@modelcontextprotocol/sdk": "^1.15.0",
|
package/package.json.bak
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fugood/bricks-ctor",
|
|
3
|
-
"version": "2.25.0-beta.
|
|
3
|
+
"version": "2.25.0-beta.17",
|
|
4
4
|
"main": "index.ts",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"typecheck": "tsc --noEmit",
|
|
7
7
|
"build": "bun scripts/build.js"
|
|
8
8
|
},
|
|
9
9
|
"dependencies": {
|
|
10
|
-
"@fugood/bricks-cli": "^2.25.0-beta.
|
|
10
|
+
"@fugood/bricks-cli": "^2.25.0-beta.17",
|
|
11
11
|
"@huggingface/gguf": "^0.3.2",
|
|
12
12
|
"@iarna/toml": "^3.0.0",
|
|
13
13
|
"@modelcontextprotocol/sdk": "^1.15.0",
|
package/tools/_git-author.ts
CHANGED
|
@@ -15,7 +15,15 @@ async function hasGitIdentity(cwd: string): Promise<boolean> {
|
|
|
15
15
|
// If no identity is configured (typical for non-tech users), CTOR becomes the
|
|
16
16
|
// main author via per-command `-c user.name/-c user.email` overrides so the
|
|
17
17
|
// user's git config is left untouched.
|
|
18
|
-
|
|
18
|
+
//
|
|
19
|
+
// `extraFlags` are inserted between `commit` and the `-m` args (e.g.
|
|
20
|
+
// `['--no-verify']` to bypass pre-commit hooks for controlled cases like
|
|
21
|
+
// committing merge-conflict markers).
|
|
22
|
+
export async function buildCommitArgs(
|
|
23
|
+
cwd: string,
|
|
24
|
+
messages: string[],
|
|
25
|
+
extraFlags: string[] = [],
|
|
26
|
+
): Promise<string[]> {
|
|
19
27
|
const hasIdentity = await hasGitIdentity(cwd)
|
|
20
28
|
const prefix = hasIdentity
|
|
21
29
|
? []
|
|
@@ -23,7 +31,7 @@ export async function buildCommitArgs(cwd: string, messages: string[]): Promise<
|
|
|
23
31
|
const msgArgs: string[] = []
|
|
24
32
|
for (const m of messages) msgArgs.push('-m', m)
|
|
25
33
|
if (hasIdentity) msgArgs.push('-m', CTOR_COAUTHOR_TRAILER)
|
|
26
|
-
return [...prefix, 'commit', ...msgArgs]
|
|
34
|
+
return [...prefix, 'commit', ...extraFlags, ...msgArgs]
|
|
27
35
|
}
|
|
28
36
|
|
|
29
37
|
export { CTOR_NAME, CTOR_EMAIL, CTOR_COAUTHOR_TRAILER }
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from 'node:fs/promises'
|
|
2
|
+
|
|
3
|
+
// Local sync marker: the git commit hash that was on HEAD when we last
|
|
4
|
+
// successfully pushed to the server (deploy or update-config) or pulled
|
|
5
|
+
// from it. Stored at the `.bricks/` top level (gitignored explicitly) — it's
|
|
6
|
+
// per-clone state, not a build artifact, so it doesn't belong under
|
|
7
|
+
// `.bricks/build/`.
|
|
8
|
+
//
|
|
9
|
+
// Why this exists: server's `bricks_project_last_commit_id` can be an opaque
|
|
10
|
+
// nanoid for content-only edits (config-editor saves), so it isn't a
|
|
11
|
+
// reliable git anchor for `git checkout` during pull. The local marker is
|
|
12
|
+
// always a real reachable commit hash from THIS clone.
|
|
13
|
+
const REL_PATH = '.bricks/.last-pushed-commit'
|
|
14
|
+
|
|
15
|
+
export async function readLastPushedCommit(cwd: string): Promise<string | null> {
|
|
16
|
+
try {
|
|
17
|
+
const content = await readFile(`${cwd}/${REL_PATH}`, 'utf8')
|
|
18
|
+
return content.trim() || null
|
|
19
|
+
} catch {
|
|
20
|
+
return null
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function writeLastPushedCommit(cwd: string, commitId: string): Promise<void> {
|
|
25
|
+
if (!commitId) return
|
|
26
|
+
await mkdir(`${cwd}/.bricks`, { recursive: true })
|
|
27
|
+
await writeFile(`${cwd}/${REL_PATH}`, commitId)
|
|
28
|
+
}
|
package/tools/deploy.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { access, readFile, writeFile } from 'node:fs/promises'
|
|
|
2
2
|
import { parseArgs } from 'util'
|
|
3
3
|
import { sh } from './_shell'
|
|
4
4
|
import { buildCommitArgs } from './_git-author'
|
|
5
|
+
import { writeLastPushedCommit } from './_last-pushed-commit'
|
|
5
6
|
|
|
6
7
|
if (!process.env.BRICKS_RELEASE_SIGN) {
|
|
7
8
|
process.env.BRICKS_RELEASE_SIGN = 'CTOR'
|
|
@@ -170,4 +171,10 @@ if (result.exitCode !== 0) {
|
|
|
170
171
|
}
|
|
171
172
|
|
|
172
173
|
const output = JSON.parse(result.stdout.toString())
|
|
174
|
+
|
|
175
|
+
// Record the commit we just pushed from so a later pull can use it as the
|
|
176
|
+
// merge base regardless of what the server stores in
|
|
177
|
+
// bricks_project_last_commit_id.
|
|
178
|
+
if (commitId) await writeLastPushedCommit(cwd, commitId)
|
|
179
|
+
|
|
173
180
|
console.log(`${isModule ? 'Module' : 'App'} deployed: ${output.name}`)
|
package/tools/pull.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { join, relative } from 'node:path'
|
|
|
4
4
|
import { format } from 'oxfmt'
|
|
5
5
|
import { sh } from './_shell'
|
|
6
6
|
import { buildCommitArgs } from './_git-author'
|
|
7
|
+
import { readLastPushedCommit, writeLastPushedCommit } from './_last-pushed-commit'
|
|
7
8
|
|
|
8
9
|
// Directories whose .ts contents are entirely owned by the generator.
|
|
9
10
|
// Anything under these dirs not present in the freshly pulled file list is an orphan.
|
|
@@ -75,30 +76,40 @@ if (result.exitCode !== 0) {
|
|
|
75
76
|
}
|
|
76
77
|
}
|
|
77
78
|
|
|
78
|
-
const { files, lastCommitId } = JSON.parse(result.stdout.toString())
|
|
79
|
+
const { files, lastCommitId: serverLastCommitId } = JSON.parse(result.stdout.toString())
|
|
80
|
+
|
|
81
|
+
// The locally-saved commit (recorded by deploy/update-config) is the
|
|
82
|
+
// authoritative merge base for THIS clone — the server's value can be an
|
|
83
|
+
// opaque nanoid (config-only updates) or point to a commit other clients
|
|
84
|
+
// produced. Fall back to the server's value only if we have no local
|
|
85
|
+
// record yet.
|
|
86
|
+
const savedLocalCommitId = await readLastPushedCommit(cwd)
|
|
87
|
+
const baseCommitId = savedLocalCommitId || serverLastCommitId
|
|
88
|
+
|
|
89
|
+
const branchName = isModule
|
|
90
|
+
? 'BRICKS_PROJECT_try-pull-module'
|
|
91
|
+
: 'BRICKS_PROJECT_try-pull-application'
|
|
79
92
|
|
|
80
|
-
let useMain = false
|
|
81
93
|
if (isGitRepo && !force) {
|
|
82
|
-
console.log(`Checking commit ${
|
|
83
|
-
const found = (await sh`cd ${cwd} && git rev-list -1 ${
|
|
94
|
+
console.log(`Checking commit ${baseCommitId}...`)
|
|
95
|
+
const found = (await sh`cd ${cwd} && git rev-list -1 ${baseCommitId}`.nothrow().text())
|
|
84
96
|
.trim()
|
|
85
97
|
.match(/^[\da-f]{40}$/)
|
|
86
98
|
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
if (commitId === lastCommitId) throw new Error('Commit not changed')
|
|
99
|
+
const headCommitId = (await sh`cd ${cwd} && git rev-parse HEAD`.text()).trim()
|
|
90
100
|
|
|
91
|
-
|
|
92
|
-
? 'BRICKS_PROJECT_try-pull-module'
|
|
93
|
-
: 'BRICKS_PROJECT_try-pull-application'
|
|
101
|
+
if (headCommitId === serverLastCommitId) throw new Error('Commit not changed')
|
|
94
102
|
|
|
95
103
|
await sh`cd ${cwd} && git branch -D ${branchName}`.nothrow()
|
|
96
104
|
|
|
105
|
+
// When the base commit isn't reachable in this clone (server stored a
|
|
106
|
+
// nanoid, or the commit was pruned), fall back to forking from current
|
|
107
|
+
// HEAD. The downstream merge into main collapses both paths into the
|
|
108
|
+
// same result, just with different merge bases.
|
|
97
109
|
if (found) {
|
|
98
|
-
await sh`cd ${cwd} && git checkout -b ${branchName} ${
|
|
110
|
+
await sh`cd ${cwd} && git checkout -b ${branchName} ${baseCommitId}`.nothrow()
|
|
99
111
|
} else {
|
|
100
112
|
await sh`cd ${cwd} && git checkout -b ${branchName}`
|
|
101
|
-
useMain = true
|
|
102
113
|
}
|
|
103
114
|
}
|
|
104
115
|
|
|
@@ -152,9 +163,33 @@ if (isGitRepo) {
|
|
|
152
163
|
const commitArgs = await buildCommitArgs(cwd, [commitMsg])
|
|
153
164
|
await sh`cd ${cwd} && git ${commitArgs}`
|
|
154
165
|
}
|
|
155
|
-
if (!force
|
|
156
|
-
|
|
166
|
+
if (!force) {
|
|
167
|
+
// Land the pulled commits on main with a single 3-way merge using
|
|
168
|
+
// baseCommit as the merge base. The user doesn't have to manage a side
|
|
169
|
+
// branch, and conflicts (if any) land in the working tree on main where
|
|
170
|
+
// auto-compile surfaces them as typecheck errors to resolve in-place.
|
|
171
|
+
await sh`cd ${cwd} && git checkout main`
|
|
172
|
+
const mergeResult = await sh`cd ${cwd} && git merge ${branchName} --no-edit`.nothrow()
|
|
173
|
+
if (mergeResult.exitCode !== 0) {
|
|
174
|
+
// Conflict markers are in the working tree — commit them so the tree
|
|
175
|
+
// is clean for auto-compile to detect, leaving the resolution to the
|
|
176
|
+
// user. Pre-commit hooks would reject markers (lint/format fail on
|
|
177
|
+
// invalid syntax), so bypass them for this controlled case.
|
|
178
|
+
await sh`cd ${cwd} && git add .`
|
|
179
|
+
const conflictArgs = await buildCommitArgs(
|
|
180
|
+
cwd,
|
|
181
|
+
['chore(project): merge with conflicts (resolve in main)'],
|
|
182
|
+
['--no-verify'],
|
|
183
|
+
)
|
|
184
|
+
await sh`cd ${cwd} && git ${conflictArgs}`
|
|
185
|
+
}
|
|
186
|
+
// The try-pull branch served its purpose; delete it so `git branch`
|
|
187
|
+
// stays tidy. The next pull recreates it anyway (line 103).
|
|
188
|
+
await sh`cd ${cwd} && git branch -D ${branchName}`.nothrow()
|
|
157
189
|
}
|
|
190
|
+
// Record the new sync point so a follow-up pull starts from the right base.
|
|
191
|
+
const newHead = (await sh`cd ${cwd} && git rev-parse HEAD`.nothrow().text()).trim()
|
|
192
|
+
if (newHead) await writeLastPushedCommit(cwd, newHead)
|
|
158
193
|
}
|
|
159
194
|
|
|
160
195
|
console.log(
|
package/tools/update-config.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { readFile, writeFile } from 'node:fs/promises'
|
|
|
2
2
|
import { parseArgs } from 'util'
|
|
3
3
|
import { sh } from './_shell'
|
|
4
4
|
import { buildCommitArgs } from './_git-author'
|
|
5
|
+
import { writeLastPushedCommit } from './_last-pushed-commit'
|
|
5
6
|
|
|
6
7
|
const cwd = process.cwd()
|
|
7
8
|
|
|
@@ -107,4 +108,11 @@ if (result.exitCode !== 0) {
|
|
|
107
108
|
}
|
|
108
109
|
|
|
109
110
|
const output = JSON.parse(result.stdout.toString())
|
|
111
|
+
|
|
112
|
+
// Record the commit we just pushed from so a later pull can use it as the
|
|
113
|
+
// merge base regardless of what the server stores in
|
|
114
|
+
// bricks_project_last_commit_id (which may be an opaque nanoid for
|
|
115
|
+
// content-only edits).
|
|
116
|
+
if (commitId) await writeLastPushedCommit(cwd, commitId)
|
|
117
|
+
|
|
110
118
|
console.log(`${isModule ? 'Module' : 'App'} config updated: ${output.target?.name || app.name}`)
|
|
@@ -511,6 +511,24 @@ Default property:
|
|
|
511
511
|
mcpResourceAnnotationKeyword?: string | DataLink
|
|
512
512
|
/* Fallback text when MCP server or resource is not found. If empty, an error will be thrown. */
|
|
513
513
|
mcpResourceAnnotationFallback?: string | DataLink
|
|
514
|
+
/* Guardrails — safety checks applied at specific pipeline hook points. Each entry runs a `pattern` regex or `llm` verdict check at the specified `on` hook, and fires the configured `action` when triggered. Template slots available in `llmUserPromptTemplate` / `llmSystemPrompt`: `{input}`, `{output}`, `{tool_call}`, `{tool_args}`, `{tool_result}`. For `action: 'redact'` with `kind: 'llm'`, `llmSystemPrompt` is also used as the sanitiser system prompt (defaults to a built-in rewrite instruction when omitted). */
|
|
515
|
+
guardrails?:
|
|
516
|
+
| Array<
|
|
517
|
+
| DataLink
|
|
518
|
+
| {
|
|
519
|
+
id?: string | DataLink
|
|
520
|
+
on?: 'before-llm' | 'after-llm' | 'before-tool' | 'after-tool' | DataLink
|
|
521
|
+
kind?: 'pattern' | 'llm' | DataLink
|
|
522
|
+
patternMatch?: string | DataLink
|
|
523
|
+
llmGeneratorId?: string | DataLink | (() => Generator)
|
|
524
|
+
llmLivePolicy?: 'only-in-use' | 'manual' | DataLink
|
|
525
|
+
llmSystemPrompt?: string | DataLink
|
|
526
|
+
llmUserPromptTemplate?: string | DataLink
|
|
527
|
+
llmResultMatch?: string | DataLink
|
|
528
|
+
action?: 'block' | 'redact' | 'log-only' | DataLink
|
|
529
|
+
}
|
|
530
|
+
>
|
|
531
|
+
| DataLink
|
|
514
532
|
}
|
|
515
533
|
events?: {
|
|
516
534
|
/* Error event */
|