brainclaw 1.9.0 → 1.9.1
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/README.md +585 -499
- package/dist/brainclaw-vscode.vsix +0 -0
- package/dist/commands/harvest.js +1 -1
- package/dist/commands/hooks.js +73 -73
- package/dist/commands/init.js +1 -1
- package/dist/commands/install-hooks.js +78 -78
- package/dist/commands/mcp-read-handlers.js +57 -14
- package/dist/commands/mcp.js +79 -13
- package/dist/commands/switch.js +26 -5
- package/dist/commands/version.js +1 -1
- package/dist/core/agent-capability.js +19 -4
- package/dist/core/agent-files.js +119 -119
- package/dist/core/codev-prompts.js +38 -38
- package/dist/core/default-profiles/doctor.yaml +11 -11
- package/dist/core/default-profiles/janitor.yaml +11 -11
- package/dist/core/default-profiles/onboarder.yaml +11 -11
- package/dist/core/default-profiles/reviewer.yaml +13 -13
- package/dist/core/dispatcher.js +1 -1
- package/dist/core/entity-operations.js +29 -3
- package/dist/core/execution.js +1 -1
- package/dist/core/loops/verbs.js +0 -1
- package/dist/core/messaging.js +2 -2
- package/dist/core/protocol-skills.js +164 -164
- package/dist/core/runtime-signals.js +1 -1
- package/dist/core/search.js +19 -2
- package/dist/core/security-guard.js +207 -207
- package/dist/core/spawn-check.js +16 -2
- package/dist/core/staleness.js +1 -1
- package/dist/core/store-resolution.js +26 -7
- package/dist/core/worktree.js +18 -18
- package/dist/facts.js +3 -3
- package/dist/facts.json +2 -2
- package/docs/PROTOCOL.md +1 -1
- package/docs/adapters/openclaw.md +43 -43
- package/docs/architecture/project-refs.md +328 -328
- package/docs/cli.md +2093 -2093
- package/docs/concepts/coordination.md +52 -52
- package/docs/concepts/coordinator-runbook.md +129 -129
- package/docs/concepts/dispatch-lifecycle.md +245 -245
- package/docs/concepts/event-log-store.md +928 -928
- package/docs/concepts/ideation-loop.md +317 -317
- package/docs/concepts/loop-engine.md +520 -511
- package/docs/concepts/mcp-governance.md +268 -268
- package/docs/concepts/memory.md +84 -84
- package/docs/concepts/multi-agent-workflows.md +167 -167
- package/docs/concepts/observer-protocol.md +361 -361
- package/docs/concepts/plans-and-claims.md +217 -217
- package/docs/concepts/project-md-convention.md +35 -35
- package/docs/concepts/runtime-notes.md +38 -38
- package/docs/concepts/troubleshooting.md +254 -254
- package/docs/concepts/workspace-bootstrapping.md +142 -142
- package/docs/context-format-changelog.md +35 -35
- package/docs/context-format.md +48 -48
- package/docs/index.md +65 -65
- package/docs/integrations/agents.md +158 -158
- package/docs/integrations/claude-code.md +23 -23
- package/docs/integrations/cline.md +77 -77
- package/docs/integrations/continue.md +55 -55
- package/docs/integrations/copilot.md +68 -68
- package/docs/integrations/cursor.md +23 -23
- package/docs/integrations/kilocode.md +72 -72
- package/docs/integrations/mcp.md +377 -377
- package/docs/integrations/mistral-vibe.md +122 -122
- package/docs/integrations/openclaw.md +92 -92
- package/docs/integrations/opencode.md +84 -84
- package/docs/integrations/overview.md +115 -115
- package/docs/integrations/roo.md +71 -71
- package/docs/integrations/windsurf.md +77 -77
- package/docs/mcp-schema-changelog.md +360 -356
- package/docs/playbooks/integration/index.md +121 -121
- package/docs/playbooks/orchestration.md +37 -0
- package/docs/playbooks/productivity/index.md +99 -99
- package/docs/playbooks/team/index.md +117 -117
- package/docs/product/agent-first-model.md +184 -184
- package/docs/product/entity-model-audit.md +462 -462
- package/docs/product/positioning.md +86 -86
- package/docs/quickstart-existing-project.md +107 -107
- package/docs/quickstart.md +183 -183
- package/docs/release-maintenance.md +79 -79
- package/docs/reputation.md +52 -52
- package/docs/review.md +45 -45
- package/docs/security.md +212 -212
- package/docs/server-operations.md +118 -118
- package/docs/storage.md +106 -106
- package/package.json +80 -65
- package/docs/concepts/event-log-store-critique-A.md +0 -333
- package/docs/concepts/event-log-store-critique-B.md +0 -353
- package/docs/concepts/event-log-store-phase0-measurements.md +0 -58
- package/docs/concepts/event-log-store-proposal-A.md +0 -365
- package/docs/concepts/event-log-store-proposal-B.md +0 -404
- package/docs/concepts/identity-model-proposal.md +0 -371
|
@@ -12,216 +12,216 @@
|
|
|
12
12
|
* a config change, no regeneration required.
|
|
13
13
|
*/
|
|
14
14
|
export function generateBashGuard(brainclawBin) {
|
|
15
|
-
return `#!/usr/bin/env bash
|
|
16
|
-
# brainclaw-guard — preinstall security gate
|
|
17
|
-
# Generated by: brainclaw setup-security
|
|
18
|
-
# Do not edit manually — regenerate with brainclaw setup-security
|
|
19
|
-
|
|
20
|
-
BRAINCLAW_BIN="${brainclawBin}"
|
|
21
|
-
ORIGINAL_CMD="\${BRAINCLAW_GUARD_ORIGINAL_CMD:-npm}"
|
|
22
|
-
|
|
23
|
-
is_install_command() {
|
|
24
|
-
for arg in "$@"; do
|
|
25
|
-
case "$arg" in
|
|
26
|
-
install|add|i) return 0 ;;
|
|
27
|
-
-*) continue ;;
|
|
28
|
-
*) break ;;
|
|
29
|
-
esac
|
|
30
|
-
done
|
|
31
|
-
return 1
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
is_local_or_url() {
|
|
35
|
-
case "$1" in
|
|
36
|
-
.|..|./*|../*|/*|*:/*|git+*|git@*|*.tgz|*.tar.gz|*.whl) return 0 ;;
|
|
37
|
-
esac
|
|
38
|
-
case "$1" in
|
|
39
|
-
[A-Za-z]:[\\\\/]*) return 0 ;;
|
|
40
|
-
esac
|
|
41
|
-
return 1
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
extract_packages() {
|
|
45
|
-
local packages=""
|
|
46
|
-
local req_file=""
|
|
47
|
-
local skip_next=false
|
|
48
|
-
local past_command=false
|
|
49
|
-
for arg in "$@"; do
|
|
50
|
-
if [ "$skip_next" = true ]; then
|
|
51
|
-
# The flag's value lands here; capture it for -r/--requirement.
|
|
52
|
-
case "$prev_flag" in
|
|
53
|
-
-r|--requirement) req_file="$arg" ;;
|
|
54
|
-
esac
|
|
55
|
-
skip_next=false
|
|
56
|
-
prev_flag=""
|
|
57
|
-
continue
|
|
58
|
-
fi
|
|
59
|
-
case "$arg" in
|
|
60
|
-
install|add|i)
|
|
61
|
-
past_command=true
|
|
62
|
-
continue
|
|
63
|
-
;;
|
|
64
|
-
--save-dev|--save-peer|--save-optional|--save-exact|--save-prod|-D|-P|-O|-E|-S|-g|--global|--no-save|--user|--upgrade|-U|--break-system-packages|--no-deps|--pre|--force-reinstall|--ignore-installed)
|
|
65
|
-
continue
|
|
66
|
-
;;
|
|
67
|
-
--registry|--prefix|--tag|--registry-url|--cache|--index-url|--extra-index-url|--find-links|--proxy|--cert|--client-cert|--trusted-host|--target|--root|--python|--platform|--abi|--implementation)
|
|
68
|
-
prev_flag="$arg"
|
|
69
|
-
skip_next=true
|
|
70
|
-
continue
|
|
71
|
-
;;
|
|
72
|
-
-r|--requirement|-e|--editable|-c|--constraint)
|
|
73
|
-
prev_flag="$arg"
|
|
74
|
-
skip_next=true
|
|
75
|
-
continue
|
|
76
|
-
;;
|
|
77
|
-
-*)
|
|
78
|
-
continue
|
|
79
|
-
;;
|
|
80
|
-
*)
|
|
81
|
-
if [ "$past_command" = true ]; then
|
|
82
|
-
if is_local_or_url "$arg"; then
|
|
83
|
-
continue
|
|
84
|
-
fi
|
|
85
|
-
if [ -n "$packages" ]; then
|
|
86
|
-
packages="$packages,$arg"
|
|
87
|
-
else
|
|
88
|
-
packages="$arg"
|
|
89
|
-
fi
|
|
90
|
-
fi
|
|
91
|
-
;;
|
|
92
|
-
esac
|
|
93
|
-
done
|
|
94
|
-
printf '%s\\t%s' "$packages" "$req_file"
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
# Main logic
|
|
98
|
-
if is_install_command "$@"; then
|
|
99
|
-
extracted=$(extract_packages "$@")
|
|
100
|
-
packages="\${extracted%% *}"
|
|
101
|
-
req_file="\${extracted##* }"
|
|
102
|
-
|
|
103
|
-
if [ -n "$packages" ] || [ -n "$req_file" ]; then
|
|
104
|
-
ecosystem="npm"
|
|
105
|
-
if [ "$ORIGINAL_CMD" = "pip" ] || [ "$ORIGINAL_CMD" = "pip3" ]; then
|
|
106
|
-
ecosystem="pypi"
|
|
107
|
-
fi
|
|
108
|
-
|
|
109
|
-
args=(check-security --ecosystem "$ecosystem" --json)
|
|
110
|
-
if [ -n "$packages" ]; then args+=(--packages "$packages"); fi
|
|
111
|
-
if [ -n "$req_file" ]; then args+=(--requirements "$req_file"); fi
|
|
112
|
-
|
|
113
|
-
"$BRAINCLAW_BIN" "\${args[@]}" >/dev/null 2>&1
|
|
114
|
-
exit_code=$?
|
|
115
|
-
|
|
116
|
-
if [ $exit_code -eq 2 ]; then
|
|
117
|
-
echo "[brainclaw-guard] BLOCKED — supply chain risk detected (enforced mode)" >&2
|
|
118
|
-
details_pkgs="\${packages:-$req_file}"
|
|
119
|
-
echo "[brainclaw-guard] Details: $BRAINCLAW_BIN check-security --ecosystem $ecosystem --packages \\"$details_pkgs\\"" >&2
|
|
120
|
-
exit 1
|
|
121
|
-
elif [ $exit_code -eq 1 ]; then
|
|
122
|
-
echo "[brainclaw-guard] WARNING — potential supply chain risk (advisory or warn-threshold verdict)" >&2
|
|
123
|
-
details_pkgs="\${packages:-$req_file}"
|
|
124
|
-
echo "[brainclaw-guard] Details: $BRAINCLAW_BIN check-security --ecosystem $ecosystem --packages \\"$details_pkgs\\"" >&2
|
|
125
|
-
fi
|
|
126
|
-
# exit_code 0 = pass, continue silently
|
|
127
|
-
fi
|
|
128
|
-
fi
|
|
129
|
-
|
|
130
|
-
exec "$ORIGINAL_CMD" "$@"
|
|
15
|
+
return `#!/usr/bin/env bash
|
|
16
|
+
# brainclaw-guard — preinstall security gate
|
|
17
|
+
# Generated by: brainclaw setup-security
|
|
18
|
+
# Do not edit manually — regenerate with brainclaw setup-security
|
|
19
|
+
|
|
20
|
+
BRAINCLAW_BIN="${brainclawBin}"
|
|
21
|
+
ORIGINAL_CMD="\${BRAINCLAW_GUARD_ORIGINAL_CMD:-npm}"
|
|
22
|
+
|
|
23
|
+
is_install_command() {
|
|
24
|
+
for arg in "$@"; do
|
|
25
|
+
case "$arg" in
|
|
26
|
+
install|add|i) return 0 ;;
|
|
27
|
+
-*) continue ;;
|
|
28
|
+
*) break ;;
|
|
29
|
+
esac
|
|
30
|
+
done
|
|
31
|
+
return 1
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
is_local_or_url() {
|
|
35
|
+
case "$1" in
|
|
36
|
+
.|..|./*|../*|/*|*:/*|git+*|git@*|*.tgz|*.tar.gz|*.whl) return 0 ;;
|
|
37
|
+
esac
|
|
38
|
+
case "$1" in
|
|
39
|
+
[A-Za-z]:[\\\\/]*) return 0 ;;
|
|
40
|
+
esac
|
|
41
|
+
return 1
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
extract_packages() {
|
|
45
|
+
local packages=""
|
|
46
|
+
local req_file=""
|
|
47
|
+
local skip_next=false
|
|
48
|
+
local past_command=false
|
|
49
|
+
for arg in "$@"; do
|
|
50
|
+
if [ "$skip_next" = true ]; then
|
|
51
|
+
# The flag's value lands here; capture it for -r/--requirement.
|
|
52
|
+
case "$prev_flag" in
|
|
53
|
+
-r|--requirement) req_file="$arg" ;;
|
|
54
|
+
esac
|
|
55
|
+
skip_next=false
|
|
56
|
+
prev_flag=""
|
|
57
|
+
continue
|
|
58
|
+
fi
|
|
59
|
+
case "$arg" in
|
|
60
|
+
install|add|i)
|
|
61
|
+
past_command=true
|
|
62
|
+
continue
|
|
63
|
+
;;
|
|
64
|
+
--save-dev|--save-peer|--save-optional|--save-exact|--save-prod|-D|-P|-O|-E|-S|-g|--global|--no-save|--user|--upgrade|-U|--break-system-packages|--no-deps|--pre|--force-reinstall|--ignore-installed)
|
|
65
|
+
continue
|
|
66
|
+
;;
|
|
67
|
+
--registry|--prefix|--tag|--registry-url|--cache|--index-url|--extra-index-url|--find-links|--proxy|--cert|--client-cert|--trusted-host|--target|--root|--python|--platform|--abi|--implementation)
|
|
68
|
+
prev_flag="$arg"
|
|
69
|
+
skip_next=true
|
|
70
|
+
continue
|
|
71
|
+
;;
|
|
72
|
+
-r|--requirement|-e|--editable|-c|--constraint)
|
|
73
|
+
prev_flag="$arg"
|
|
74
|
+
skip_next=true
|
|
75
|
+
continue
|
|
76
|
+
;;
|
|
77
|
+
-*)
|
|
78
|
+
continue
|
|
79
|
+
;;
|
|
80
|
+
*)
|
|
81
|
+
if [ "$past_command" = true ]; then
|
|
82
|
+
if is_local_or_url "$arg"; then
|
|
83
|
+
continue
|
|
84
|
+
fi
|
|
85
|
+
if [ -n "$packages" ]; then
|
|
86
|
+
packages="$packages,$arg"
|
|
87
|
+
else
|
|
88
|
+
packages="$arg"
|
|
89
|
+
fi
|
|
90
|
+
fi
|
|
91
|
+
;;
|
|
92
|
+
esac
|
|
93
|
+
done
|
|
94
|
+
printf '%s\\t%s' "$packages" "$req_file"
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
# Main logic
|
|
98
|
+
if is_install_command "$@"; then
|
|
99
|
+
extracted=$(extract_packages "$@")
|
|
100
|
+
packages="\${extracted%% *}"
|
|
101
|
+
req_file="\${extracted##* }"
|
|
102
|
+
|
|
103
|
+
if [ -n "$packages" ] || [ -n "$req_file" ]; then
|
|
104
|
+
ecosystem="npm"
|
|
105
|
+
if [ "$ORIGINAL_CMD" = "pip" ] || [ "$ORIGINAL_CMD" = "pip3" ]; then
|
|
106
|
+
ecosystem="pypi"
|
|
107
|
+
fi
|
|
108
|
+
|
|
109
|
+
args=(check-security --ecosystem "$ecosystem" --json)
|
|
110
|
+
if [ -n "$packages" ]; then args+=(--packages "$packages"); fi
|
|
111
|
+
if [ -n "$req_file" ]; then args+=(--requirements "$req_file"); fi
|
|
112
|
+
|
|
113
|
+
"$BRAINCLAW_BIN" "\${args[@]}" >/dev/null 2>&1
|
|
114
|
+
exit_code=$?
|
|
115
|
+
|
|
116
|
+
if [ $exit_code -eq 2 ]; then
|
|
117
|
+
echo "[brainclaw-guard] BLOCKED — supply chain risk detected (enforced mode)" >&2
|
|
118
|
+
details_pkgs="\${packages:-$req_file}"
|
|
119
|
+
echo "[brainclaw-guard] Details: $BRAINCLAW_BIN check-security --ecosystem $ecosystem --packages \\"$details_pkgs\\"" >&2
|
|
120
|
+
exit 1
|
|
121
|
+
elif [ $exit_code -eq 1 ]; then
|
|
122
|
+
echo "[brainclaw-guard] WARNING — potential supply chain risk (advisory or warn-threshold verdict)" >&2
|
|
123
|
+
details_pkgs="\${packages:-$req_file}"
|
|
124
|
+
echo "[brainclaw-guard] Details: $BRAINCLAW_BIN check-security --ecosystem $ecosystem --packages \\"$details_pkgs\\"" >&2
|
|
125
|
+
fi
|
|
126
|
+
# exit_code 0 = pass, continue silently
|
|
127
|
+
fi
|
|
128
|
+
fi
|
|
129
|
+
|
|
130
|
+
exec "$ORIGINAL_CMD" "$@"
|
|
131
131
|
`;
|
|
132
132
|
}
|
|
133
133
|
export function generatePowerShellGuard(brainclawBin) {
|
|
134
|
-
return `# brainclaw-guard — preinstall security gate (PowerShell)
|
|
135
|
-
# Generated by: brainclaw setup-security
|
|
136
|
-
# Do not edit manually — regenerate with brainclaw setup-security
|
|
137
|
-
|
|
138
|
-
param([Parameter(ValueFromRemainingArguments)]$Args)
|
|
139
|
-
|
|
140
|
-
$BrainclawBin = "${brainclawBin}"
|
|
141
|
-
$OriginalCmd = if ($env:BRAINCLAW_GUARD_ORIGINAL_CMD) { $env:BRAINCLAW_GUARD_ORIGINAL_CMD } else { "npm" }
|
|
142
|
-
|
|
143
|
-
function Is-InstallCommand($arguments) {
|
|
144
|
-
foreach ($arg in $arguments) {
|
|
145
|
-
if ($arg -match "^(install|add|i)$") { return $true }
|
|
146
|
-
if ($arg -notmatch "^-") { break }
|
|
147
|
-
}
|
|
148
|
-
return $false
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
function Is-LocalOrUrl($arg) {
|
|
152
|
-
if ($arg -eq "." -or $arg -eq "..") { return $true }
|
|
153
|
-
if ($arg -match "^(\\./|\\.\\./|/)") { return $true }
|
|
154
|
-
if ($arg -match "^[A-Za-z]:[\\\\/]") { return $true }
|
|
155
|
-
if ($arg -match "^[a-z]+://") { return $true }
|
|
156
|
-
if ($arg -match "^(git\\+|git@)") { return $true }
|
|
157
|
-
if ($arg -match "\\.(tgz|tar\\.gz|whl)$") { return $true }
|
|
158
|
-
return $false
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
function Extract-Packages($arguments) {
|
|
162
|
-
$packages = @()
|
|
163
|
-
$reqFile = ""
|
|
164
|
-
$pastCommand = $false
|
|
165
|
-
$skipNext = $false
|
|
166
|
-
$prevFlag = ""
|
|
167
|
-
foreach ($arg in $arguments) {
|
|
168
|
-
if ($skipNext) {
|
|
169
|
-
if ($prevFlag -match "^(-r|--requirement)$") { $reqFile = $arg }
|
|
170
|
-
$skipNext = $false
|
|
171
|
-
$prevFlag = ""
|
|
172
|
-
continue
|
|
173
|
-
}
|
|
174
|
-
if ($arg -match "^(install|add|i)$") { $pastCommand = $true; continue }
|
|
175
|
-
if ($arg -match "^(--save-dev|--save-peer|--save-optional|--save-exact|--save-prod|-D|-P|-O|-E|-S|-g|--global|--no-save|--user|--upgrade|-U|--break-system-packages|--no-deps|--pre|--force-reinstall|--ignore-installed)$") { continue }
|
|
176
|
-
if ($arg -match "^(--registry|--prefix|--tag|--registry-url|--cache|--index-url|--extra-index-url|--find-links|--proxy|--cert|--client-cert|--trusted-host|--target|--root|--python|--platform|--abi|--implementation)$") {
|
|
177
|
-
$prevFlag = $arg; $skipNext = $true; continue
|
|
178
|
-
}
|
|
179
|
-
if ($arg -match "^(-r|--requirement|-e|--editable|-c|--constraint)$") {
|
|
180
|
-
$prevFlag = $arg; $skipNext = $true; continue
|
|
181
|
-
}
|
|
182
|
-
if ($arg -match "^-") { continue }
|
|
183
|
-
if ($pastCommand) {
|
|
184
|
-
if (Is-LocalOrUrl $arg) { continue }
|
|
185
|
-
$packages += $arg
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
return [PSCustomObject]@{ Packages = ($packages -join ","); RequirementsFile = $reqFile }
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
if (Is-InstallCommand $Args) {
|
|
192
|
-
$extracted = Extract-Packages $Args
|
|
193
|
-
$packages = $extracted.Packages
|
|
194
|
-
$reqFile = $extracted.RequirementsFile
|
|
195
|
-
|
|
196
|
-
if ($packages -or $reqFile) {
|
|
197
|
-
$ecosystem = "npm"
|
|
198
|
-
if ($OriginalCmd -match "^pip") { $ecosystem = "pypi" }
|
|
199
|
-
|
|
200
|
-
$cliArgs = @("check-security", "--ecosystem", $ecosystem, "--json")
|
|
201
|
-
if ($packages) { $cliArgs += @("--packages", $packages) }
|
|
202
|
-
if ($reqFile) { $cliArgs += @("--requirements", $reqFile) }
|
|
203
|
-
|
|
204
|
-
try {
|
|
205
|
-
& $BrainclawBin @cliArgs *> $null
|
|
206
|
-
$exitCode = $LASTEXITCODE
|
|
207
|
-
} catch {
|
|
208
|
-
$exitCode = 0
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
$detailsPkgs = if ($packages) { $packages } else { $reqFile }
|
|
212
|
-
if ($exitCode -eq 2) {
|
|
213
|
-
Write-Error "[brainclaw-guard] BLOCKED - supply chain risk detected (enforced mode)"
|
|
214
|
-
Write-Error "[brainclaw-guard] Details: $BrainclawBin check-security --ecosystem $ecosystem --packages \`"$detailsPkgs\`""
|
|
215
|
-
exit 1
|
|
216
|
-
} elseif ($exitCode -eq 1) {
|
|
217
|
-
Write-Warning "[brainclaw-guard] WARNING - potential supply chain risk (advisory or warn-threshold verdict)"
|
|
218
|
-
Write-Warning "[brainclaw-guard] Details: $BrainclawBin check-security --ecosystem $ecosystem --packages \`"$detailsPkgs\`""
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
& $OriginalCmd @Args
|
|
224
|
-
exit $LASTEXITCODE
|
|
134
|
+
return `# brainclaw-guard — preinstall security gate (PowerShell)
|
|
135
|
+
# Generated by: brainclaw setup-security
|
|
136
|
+
# Do not edit manually — regenerate with brainclaw setup-security
|
|
137
|
+
|
|
138
|
+
param([Parameter(ValueFromRemainingArguments)]$Args)
|
|
139
|
+
|
|
140
|
+
$BrainclawBin = "${brainclawBin}"
|
|
141
|
+
$OriginalCmd = if ($env:BRAINCLAW_GUARD_ORIGINAL_CMD) { $env:BRAINCLAW_GUARD_ORIGINAL_CMD } else { "npm" }
|
|
142
|
+
|
|
143
|
+
function Is-InstallCommand($arguments) {
|
|
144
|
+
foreach ($arg in $arguments) {
|
|
145
|
+
if ($arg -match "^(install|add|i)$") { return $true }
|
|
146
|
+
if ($arg -notmatch "^-") { break }
|
|
147
|
+
}
|
|
148
|
+
return $false
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function Is-LocalOrUrl($arg) {
|
|
152
|
+
if ($arg -eq "." -or $arg -eq "..") { return $true }
|
|
153
|
+
if ($arg -match "^(\\./|\\.\\./|/)") { return $true }
|
|
154
|
+
if ($arg -match "^[A-Za-z]:[\\\\/]") { return $true }
|
|
155
|
+
if ($arg -match "^[a-z]+://") { return $true }
|
|
156
|
+
if ($arg -match "^(git\\+|git@)") { return $true }
|
|
157
|
+
if ($arg -match "\\.(tgz|tar\\.gz|whl)$") { return $true }
|
|
158
|
+
return $false
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function Extract-Packages($arguments) {
|
|
162
|
+
$packages = @()
|
|
163
|
+
$reqFile = ""
|
|
164
|
+
$pastCommand = $false
|
|
165
|
+
$skipNext = $false
|
|
166
|
+
$prevFlag = ""
|
|
167
|
+
foreach ($arg in $arguments) {
|
|
168
|
+
if ($skipNext) {
|
|
169
|
+
if ($prevFlag -match "^(-r|--requirement)$") { $reqFile = $arg }
|
|
170
|
+
$skipNext = $false
|
|
171
|
+
$prevFlag = ""
|
|
172
|
+
continue
|
|
173
|
+
}
|
|
174
|
+
if ($arg -match "^(install|add|i)$") { $pastCommand = $true; continue }
|
|
175
|
+
if ($arg -match "^(--save-dev|--save-peer|--save-optional|--save-exact|--save-prod|-D|-P|-O|-E|-S|-g|--global|--no-save|--user|--upgrade|-U|--break-system-packages|--no-deps|--pre|--force-reinstall|--ignore-installed)$") { continue }
|
|
176
|
+
if ($arg -match "^(--registry|--prefix|--tag|--registry-url|--cache|--index-url|--extra-index-url|--find-links|--proxy|--cert|--client-cert|--trusted-host|--target|--root|--python|--platform|--abi|--implementation)$") {
|
|
177
|
+
$prevFlag = $arg; $skipNext = $true; continue
|
|
178
|
+
}
|
|
179
|
+
if ($arg -match "^(-r|--requirement|-e|--editable|-c|--constraint)$") {
|
|
180
|
+
$prevFlag = $arg; $skipNext = $true; continue
|
|
181
|
+
}
|
|
182
|
+
if ($arg -match "^-") { continue }
|
|
183
|
+
if ($pastCommand) {
|
|
184
|
+
if (Is-LocalOrUrl $arg) { continue }
|
|
185
|
+
$packages += $arg
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return [PSCustomObject]@{ Packages = ($packages -join ","); RequirementsFile = $reqFile }
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (Is-InstallCommand $Args) {
|
|
192
|
+
$extracted = Extract-Packages $Args
|
|
193
|
+
$packages = $extracted.Packages
|
|
194
|
+
$reqFile = $extracted.RequirementsFile
|
|
195
|
+
|
|
196
|
+
if ($packages -or $reqFile) {
|
|
197
|
+
$ecosystem = "npm"
|
|
198
|
+
if ($OriginalCmd -match "^pip") { $ecosystem = "pypi" }
|
|
199
|
+
|
|
200
|
+
$cliArgs = @("check-security", "--ecosystem", $ecosystem, "--json")
|
|
201
|
+
if ($packages) { $cliArgs += @("--packages", $packages) }
|
|
202
|
+
if ($reqFile) { $cliArgs += @("--requirements", $reqFile) }
|
|
203
|
+
|
|
204
|
+
try {
|
|
205
|
+
& $BrainclawBin @cliArgs *> $null
|
|
206
|
+
$exitCode = $LASTEXITCODE
|
|
207
|
+
} catch {
|
|
208
|
+
$exitCode = 0
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
$detailsPkgs = if ($packages) { $packages } else { $reqFile }
|
|
212
|
+
if ($exitCode -eq 2) {
|
|
213
|
+
Write-Error "[brainclaw-guard] BLOCKED - supply chain risk detected (enforced mode)"
|
|
214
|
+
Write-Error "[brainclaw-guard] Details: $BrainclawBin check-security --ecosystem $ecosystem --packages \`"$detailsPkgs\`""
|
|
215
|
+
exit 1
|
|
216
|
+
} elseif ($exitCode -eq 1) {
|
|
217
|
+
Write-Warning "[brainclaw-guard] WARNING - potential supply chain risk (advisory or warn-threshold verdict)"
|
|
218
|
+
Write-Warning "[brainclaw-guard] Details: $BrainclawBin check-security --ecosystem $ecosystem --packages \`"$detailsPkgs\`""
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
& $OriginalCmd @Args
|
|
224
|
+
exit $LASTEXITCODE
|
|
225
225
|
`;
|
|
226
226
|
}
|
|
227
227
|
export function generatePipBashGuard(brainclawBin) {
|
package/dist/core/spawn-check.js
CHANGED
|
@@ -48,7 +48,14 @@ function initProbeGitRepo(root) {
|
|
|
48
48
|
export async function checkAgentSpawn(agent, options = {}) {
|
|
49
49
|
const start = Date.now();
|
|
50
50
|
const profile = getCapabilityProfile(agent);
|
|
51
|
-
if (!profile
|
|
51
|
+
if (!profile) {
|
|
52
|
+
// Distinct from no_template: the name didn't resolve to any profile at all
|
|
53
|
+
// (resolution is case-insensitive, so this is a genuine typo/unknown agent,
|
|
54
|
+
// not a casing slip). Reported separately so the pre-flight reason points at
|
|
55
|
+
// the spelling instead of the misleading "IDE-only?" template message.
|
|
56
|
+
return { agent, status: 'unknown_agent', delivered: false, completed: false, duration_ms: 0, detail: `unknown agent '${agent}' — not a registered brainclaw profile` };
|
|
57
|
+
}
|
|
58
|
+
if (!profile.invoke_template || !profile.invoke_binary || !profile.runtime.canBeSpawnedCli) {
|
|
52
59
|
return { agent, status: 'no_template', delivered: false, completed: false, duration_ms: 0, detail: 'no CLI invoke template' };
|
|
53
60
|
}
|
|
54
61
|
const binary = resolveBinaryOnPath(profile.invoke_binary);
|
|
@@ -120,7 +127,7 @@ export async function runSpawnCheck(options = {}) {
|
|
|
120
127
|
});
|
|
121
128
|
}
|
|
122
129
|
}
|
|
123
|
-
const installed = entries.filter((e) => e.status !== 'not_installed' && e.status !== 'no_template');
|
|
130
|
+
const installed = entries.filter((e) => e.status !== 'not_installed' && e.status !== 'no_template' && e.status !== 'unknown_agent');
|
|
124
131
|
const ok = installed.filter((e) => e.status === 'ok').length;
|
|
125
132
|
const failures = installed.filter((e) => e.status === 'failed' || e.status === 'delivered_no_completion').length;
|
|
126
133
|
const not_installed = entries.filter((e) => e.status === 'not_installed').length;
|
|
@@ -172,6 +179,13 @@ export function preflightResultFromEntry(entry) {
|
|
|
172
179
|
if (entry.status === 'ok' || entry.status === 'delivered_no_completion') {
|
|
173
180
|
return { agent, ok: true, status: entry.status, reason: entry.detail };
|
|
174
181
|
}
|
|
182
|
+
if (entry.status === 'unknown_agent') {
|
|
183
|
+
return {
|
|
184
|
+
agent, ok: false, status: entry.status,
|
|
185
|
+
reason: `unknown agent '${agent}' — not a registered brainclaw profile (check spelling/case)`,
|
|
186
|
+
recommended_next_action: `Use a registered agent name (e.g. codex, claude-code, github-copilot). Names are case-insensitive — list installed agents with \`brainclaw doctor --spawn-check\`.`,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
175
189
|
if (entry.status === 'not_installed') {
|
|
176
190
|
return {
|
|
177
191
|
agent, ok: false, status: entry.status,
|
package/dist/core/staleness.js
CHANGED
|
@@ -108,7 +108,7 @@ const VERIFIED_STALE_DAYS = 30;
|
|
|
108
108
|
* pln#530 — flag perishable memories (traps that opted in by carrying a
|
|
109
109
|
* `verify_cmd` and/or `verified_at`) whose last empirical verification is stale
|
|
110
110
|
* or never happened, so an agent re-probes the live system instead of trusting a
|
|
111
|
-
* value that may have drifted (the
|
|
111
|
+
* value that may have drifted (the `service_tier` trap that the API later
|
|
112
112
|
* rejected is the motivating case). Only traps with these fields are considered —
|
|
113
113
|
* durable facts are untouched.
|
|
114
114
|
*/
|
|
@@ -3,7 +3,7 @@ import os from 'node:os';
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { loadActiveProject } from './active-project.js';
|
|
5
5
|
import { loadConfig } from './config.js';
|
|
6
|
-
import { loadCurrentSession } from './identity.js';
|
|
6
|
+
import { loadCurrentSession, loadSessionById } from './identity.js';
|
|
7
7
|
import { MEMORY_DIR } from './io.js';
|
|
8
8
|
import { summarizeWorkspaceProjects } from './workspace-projects.js';
|
|
9
9
|
/**
|
|
@@ -98,10 +98,18 @@ export function resolveTargetStore(cwd = process.cwd(), target = 'local', option
|
|
|
98
98
|
* 6. Workspace anchor or process.cwd()
|
|
99
99
|
*/
|
|
100
100
|
export function resolveEffectiveCwd(options = {}) {
|
|
101
|
+
return resolveEffectiveCwdInfo(options).cwd;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Resolve the effective cwd and explain which selector won. Use this for MCP
|
|
105
|
+
* facades that must echo their project scope to avoid silent cross-project reads.
|
|
106
|
+
*/
|
|
107
|
+
export function resolveEffectiveCwdInfo(options = {}) {
|
|
101
108
|
const baseCwd = path.resolve(options.baseCwd ?? process.cwd());
|
|
102
109
|
// 1. Explicit --cwd flag
|
|
103
110
|
if (options.explicitCwd) {
|
|
104
|
-
|
|
111
|
+
const cwd = path.resolve(options.explicitCwd);
|
|
112
|
+
return { cwd, active_source: 'explicit', resolved_project: projectInfo(cwd) };
|
|
105
113
|
}
|
|
106
114
|
// 2. BRAINCLAW_CWD env var — set by MCP configs to anchor resolution to the
|
|
107
115
|
// workspace regardless of the IDE's process.cwd() at launch time. It is a
|
|
@@ -118,14 +126,16 @@ export function resolveEffectiveCwd(options = {}) {
|
|
|
118
126
|
if (envProject) {
|
|
119
127
|
const resolved = resolveProjectRef(envProject, anchorCwd, options.storeChainOptions);
|
|
120
128
|
if (resolved)
|
|
121
|
-
return resolved;
|
|
129
|
+
return { cwd: resolved, active_source: 'env_project', resolved_project: projectInfo(resolved) };
|
|
122
130
|
}
|
|
123
131
|
// 4. Session-scoped active project (per-agent, no cross-agent interference)
|
|
124
|
-
const session =
|
|
132
|
+
const session = options.sessionId
|
|
133
|
+
? loadSessionById(options.sessionId, anchorCwd)
|
|
134
|
+
: loadCurrentSession(anchorCwd);
|
|
125
135
|
if (session?.active_project) {
|
|
126
136
|
const sp = session.active_project;
|
|
127
137
|
if (fs.existsSync(path.join(sp.path, MEMORY_DIR, 'config.yaml'))) {
|
|
128
|
-
return sp.path;
|
|
138
|
+
return { cwd: sp.path, active_source: 'session', resolved_project: { path: sp.path, name: sp.name } };
|
|
129
139
|
}
|
|
130
140
|
}
|
|
131
141
|
// 5. Global active-project.json from workspace root
|
|
@@ -133,11 +143,20 @@ export function resolveEffectiveCwd(options = {}) {
|
|
|
133
143
|
if (wsRoot) {
|
|
134
144
|
const active = loadActiveProject(wsRoot);
|
|
135
145
|
if (active && fs.existsSync(path.join(active.path, MEMORY_DIR, 'config.yaml'))) {
|
|
136
|
-
return active.path;
|
|
146
|
+
return { cwd: active.path, active_source: 'global', resolved_project: { path: active.path, name: active.name } };
|
|
137
147
|
}
|
|
138
148
|
}
|
|
139
149
|
// 6. Default
|
|
140
|
-
return anchorCwd;
|
|
150
|
+
return { cwd: anchorCwd, active_source: 'cwd', resolved_project: projectInfo(anchorCwd) };
|
|
151
|
+
}
|
|
152
|
+
function projectInfo(cwd) {
|
|
153
|
+
try {
|
|
154
|
+
const config = loadConfig(cwd);
|
|
155
|
+
return { path: cwd, name: config.project_name };
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
return { path: cwd };
|
|
159
|
+
}
|
|
141
160
|
}
|
|
142
161
|
/**
|
|
143
162
|
* Find the workspace root (farthest store in the chain, or the one with
|
package/dist/core/worktree.js
CHANGED
|
@@ -573,24 +573,24 @@ export const WORKTREE_HOOKS_DIRNAME = '.brainclaw-hooks';
|
|
|
573
573
|
* blocking — a tooling gap must not trap a worker.
|
|
574
574
|
*/
|
|
575
575
|
export function buildTypecheckPreCommitScript() {
|
|
576
|
-
return `#!/bin/sh
|
|
577
|
-
# brainclaw worktree typecheck gate (pln#479) — do not edit manually.
|
|
578
|
-
# Blocks the commit when 'tsc --noEmit' fails. Bypass: git commit --no-verify.
|
|
579
|
-
exec node -e "
|
|
580
|
-
const fs = require('fs');
|
|
581
|
-
const { execSync } = require('child_process');
|
|
582
|
-
if (!fs.existsSync('tsconfig.json')) process.exit(0);
|
|
583
|
-
if (!fs.existsSync('node_modules/typescript/bin/tsc')) {
|
|
584
|
-
process.stderr.write('\\\\n[brainclaw] typecheck gate: typescript not found in worktree node_modules — skipping (commit allowed).\\\\n');
|
|
585
|
-
process.exit(0);
|
|
586
|
-
}
|
|
587
|
-
try {
|
|
588
|
-
execSync('node node_modules/typescript/bin/tsc --noEmit', { stdio: 'inherit' });
|
|
589
|
-
} catch (e) {
|
|
590
|
-
process.stderr.write('\\\\n[brainclaw] commit blocked: tsc --noEmit reported type errors (above). Fix them, or bypass with: git commit --no-verify\\\\n\\\\n');
|
|
591
|
-
process.exit(1);
|
|
592
|
-
}
|
|
593
|
-
" 2>&1 || exit $?
|
|
576
|
+
return `#!/bin/sh
|
|
577
|
+
# brainclaw worktree typecheck gate (pln#479) — do not edit manually.
|
|
578
|
+
# Blocks the commit when 'tsc --noEmit' fails. Bypass: git commit --no-verify.
|
|
579
|
+
exec node -e "
|
|
580
|
+
const fs = require('fs');
|
|
581
|
+
const { execSync } = require('child_process');
|
|
582
|
+
if (!fs.existsSync('tsconfig.json')) process.exit(0);
|
|
583
|
+
if (!fs.existsSync('node_modules/typescript/bin/tsc')) {
|
|
584
|
+
process.stderr.write('\\\\n[brainclaw] typecheck gate: typescript not found in worktree node_modules — skipping (commit allowed).\\\\n');
|
|
585
|
+
process.exit(0);
|
|
586
|
+
}
|
|
587
|
+
try {
|
|
588
|
+
execSync('node node_modules/typescript/bin/tsc --noEmit', { stdio: 'inherit' });
|
|
589
|
+
} catch (e) {
|
|
590
|
+
process.stderr.write('\\\\n[brainclaw] commit blocked: tsc --noEmit reported type errors (above). Fix them, or bypass with: git commit --no-verify\\\\n\\\\n');
|
|
591
|
+
process.exit(1);
|
|
592
|
+
}
|
|
593
|
+
" 2>&1 || exit $?
|
|
594
594
|
`;
|
|
595
595
|
}
|
|
596
596
|
/**
|
package/dist/facts.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// Generated by scripts/emit-site-facts.mjs at build time. Do not edit manually.
|
|
2
|
-
// Source: brainclaw v1.9.
|
|
2
|
+
// Source: brainclaw v1.9.1 on 2026-06-18T15:15:04.496Z
|
|
3
3
|
export const FACTS = {
|
|
4
|
-
"version": "1.9.
|
|
5
|
-
"generated_at": "2026-06-
|
|
4
|
+
"version": "1.9.1",
|
|
5
|
+
"generated_at": "2026-06-18T15:15:04.496Z",
|
|
6
6
|
"tools": {
|
|
7
7
|
"count": 62,
|
|
8
8
|
"published_count": 61,
|
package/dist/facts.json
CHANGED
package/docs/PROTOCOL.md
CHANGED
|
@@ -219,5 +219,5 @@ integrators before declaring any of them stable:
|
|
|
219
219
|
dependency) lower the adoption bar for OSS agents that don't speak MCP
|
|
220
220
|
yet?
|
|
221
221
|
|
|
222
|
-
Open an issue, ping `
|
|
222
|
+
Open an issue, ping `support@brainclaw.dev`, or comment on the brainclaw plan
|
|
223
223
|
tracking this draft (pln#546).
|