brainclaw 1.9.0 → 1.10.0
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 +631 -499
- package/dist/brainclaw-vscode.vsix +0 -0
- package/dist/cli.js +18 -1
- package/dist/commands/code-map.js +129 -0
- package/dist/commands/codev.js +7 -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 +200 -13
- package/dist/commands/run-profile.js +3 -2
- package/dist/commands/switch.js +125 -93
- package/dist/commands/version.js +1 -1
- package/dist/core/agent-capability.js +19 -4
- package/dist/core/agent-files.js +131 -119
- package/dist/core/code-map/backend.js +123 -0
- package/dist/core/code-map/core.js +81 -0
- package/dist/core/code-map/drafts.js +2 -0
- package/dist/core/code-map/extractor.js +29 -0
- package/dist/core/code-map/finalizer.js +191 -0
- package/dist/core/code-map/freshness.js +108 -0
- package/dist/core/code-map/ids.js +0 -0
- package/dist/core/code-map/importable.js +35 -0
- package/dist/core/code-map/indexes.js +197 -0
- package/dist/core/code-map/lang/java/imports.scm +17 -0
- package/dist/core/code-map/lang/java/index.js +254 -0
- package/dist/core/code-map/lang/java/tags.scm +48 -0
- package/dist/core/code-map/lang/php/imports.scm +21 -0
- package/dist/core/code-map/lang/php/index.js +251 -0
- package/dist/core/code-map/lang/php/tags.scm +44 -0
- package/dist/core/code-map/lang/provider.js +9 -0
- package/dist/core/code-map/lang/providers.js +24 -0
- package/dist/core/code-map/lang/python/imports.scm +90 -0
- package/dist/core/code-map/lang/python/index.js +364 -0
- package/dist/core/code-map/lang/python/tags.scm +81 -0
- package/dist/core/code-map/lang/query-runtime.js +374 -0
- package/dist/core/code-map/lang/registry.js +125 -0
- package/dist/core/code-map/lang/typescript/imports.scm +90 -0
- package/dist/core/code-map/lang/typescript/index.js +306 -0
- package/dist/core/code-map/lang/typescript/tags.js.scm +106 -0
- package/dist/core/code-map/lang/typescript/tags.scm +151 -0
- package/dist/core/code-map/lock.js +210 -0
- package/dist/core/code-map/materialized.js +51 -0
- package/dist/core/code-map/memory-reader.js +59 -0
- package/dist/core/code-map/paths.js +53 -0
- package/dist/core/code-map/query.js +568 -0
- package/dist/core/code-map/refresh.js +0 -0
- package/dist/core/code-map/resolve.js +177 -0
- package/dist/core/code-map/store.js +206 -0
- package/dist/core/code-map/types.js +288 -0
- package/dist/core/code-map/vocabulary.js +57 -0
- package/dist/core/code-map/wasm-loader.js +294 -0
- package/dist/core/code-map/work-section.js +206 -0
- package/dist/core/codev-prompts.js +38 -38
- package/dist/core/codev-rounds.js +4 -0
- 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-adapters.js +11 -10
- package/dist/core/execution-profile.js +58 -0
- package/dist/core/execution.js +1 -1
- package/dist/core/facade-schema.js +9 -0
- package/dist/core/instruction-templates.js +2 -0
- package/dist/core/loops/verbs.js +0 -1
- package/dist/core/mcp-command-resolution.js +3 -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 +67 -11
- package/dist/core/worktree.js +18 -18
- package/dist/facts.js +9 -5
- package/dist/facts.json +8 -4
- package/dist/vendor/web-tree-sitter/tree-sitter.js +3980 -0
- package/dist/vendor/web-tree-sitter/tree-sitter.wasm +0 -0
- package/dist/wasm/tree-sitter-java.wasm +0 -0
- package/dist/wasm/tree-sitter-javascript.wasm +0 -0
- package/dist/wasm/tree-sitter-php.wasm +0 -0
- package/dist/wasm/tree-sitter-python.wasm +0 -0
- package/dist/wasm/tree-sitter-tsx.wasm +0 -0
- package/dist/wasm/tree-sitter-typescript.wasm +0 -0
- package/dist/wasm/tree-sitter.wasm +0 -0
- 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 +2131 -2093
- package/docs/code-map.md +198 -0
- 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 +385 -378
- 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 +364 -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 +86 -66
- 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
|
/**
|
|
@@ -94,14 +94,24 @@ export function resolveTargetStore(cwd = process.cwd(), target = 'local', option
|
|
|
94
94
|
* 2. BRAINCLAW_CWD env var → workspace anchor injected by MCP configs
|
|
95
95
|
* 3. BRAINCLAW_PROJECT env var → resolved by name/path from workspace anchor
|
|
96
96
|
* 4. Session-scoped active project (from .current-session under the anchor)
|
|
97
|
-
* 5.
|
|
98
|
-
*
|
|
97
|
+
* 5. cwd_child — the child project the agent is physically inside, under the anchor
|
|
98
|
+
* 5b. cwd_child (no anchor) — same, ceiling = discovered workspace root (F2)
|
|
99
|
+
* 6. Global active-project.json in workspace root
|
|
100
|
+
* 7. Workspace anchor or process.cwd()
|
|
99
101
|
*/
|
|
100
102
|
export function resolveEffectiveCwd(options = {}) {
|
|
103
|
+
return resolveEffectiveCwdInfo(options).cwd;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Resolve the effective cwd and explain which selector won. Use this for MCP
|
|
107
|
+
* facades that must echo their project scope to avoid silent cross-project reads.
|
|
108
|
+
*/
|
|
109
|
+
export function resolveEffectiveCwdInfo(options = {}) {
|
|
101
110
|
const baseCwd = path.resolve(options.baseCwd ?? process.cwd());
|
|
102
111
|
// 1. Explicit --cwd flag
|
|
103
112
|
if (options.explicitCwd) {
|
|
104
|
-
|
|
113
|
+
const cwd = path.resolve(options.explicitCwd);
|
|
114
|
+
return { cwd, active_source: 'explicit', resolved_project: projectInfo(cwd) };
|
|
105
115
|
}
|
|
106
116
|
// 2. BRAINCLAW_CWD env var — set by MCP configs to anchor resolution to the
|
|
107
117
|
// workspace regardless of the IDE's process.cwd() at launch time. It is a
|
|
@@ -118,26 +128,72 @@ export function resolveEffectiveCwd(options = {}) {
|
|
|
118
128
|
if (envProject) {
|
|
119
129
|
const resolved = resolveProjectRef(envProject, anchorCwd, options.storeChainOptions);
|
|
120
130
|
if (resolved)
|
|
121
|
-
return resolved;
|
|
131
|
+
return { cwd: resolved, active_source: 'env_project', resolved_project: projectInfo(resolved) };
|
|
122
132
|
}
|
|
123
133
|
// 4. Session-scoped active project (per-agent, no cross-agent interference)
|
|
124
|
-
const session =
|
|
134
|
+
const session = options.sessionId
|
|
135
|
+
? loadSessionById(options.sessionId, anchorCwd)
|
|
136
|
+
: loadCurrentSession(anchorCwd);
|
|
125
137
|
if (session?.active_project) {
|
|
126
138
|
const sp = session.active_project;
|
|
127
139
|
if (fs.existsSync(path.join(sp.path, MEMORY_DIR, 'config.yaml'))) {
|
|
128
|
-
return sp.path;
|
|
140
|
+
return { cwd: sp.path, active_source: 'session', resolved_project: { path: sp.path, name: sp.name } };
|
|
129
141
|
}
|
|
130
142
|
}
|
|
131
|
-
// 5.
|
|
143
|
+
// 5. cwd_child — when anchored and the agent is physically inside a child store
|
|
144
|
+
// STRICTLY under the anchor, resolve THAT child rather than the shared global
|
|
145
|
+
// pointer or the anchor root. This is the independence rule: physical location
|
|
146
|
+
// beats a shared/stale global (an agent working in apps/api resolves api, not the
|
|
147
|
+
// monorepo root, and is not hijacked by another agent's global switch).
|
|
148
|
+
//
|
|
149
|
+
// GUARD (Codex review): only fire when baseCwd differs from the anchor AND is
|
|
150
|
+
// at/below it. `findClosestStoreBelow` walks UP to the ceiling but does NOT prove
|
|
151
|
+
// baseCwd sits below it — without the `isAtOrBelow` guard a baseCwd OUTSIDE the
|
|
152
|
+
// anchor could match an unrelated `.brainclaw` before hitting the filesystem root.
|
|
153
|
+
if (baseCwd !== anchorCwd && isAtOrBelow(baseCwd, anchorCwd)) {
|
|
154
|
+
const child = findClosestStoreBelow(baseCwd, anchorCwd);
|
|
155
|
+
if (child && path.resolve(child) !== path.resolve(anchorCwd)) {
|
|
156
|
+
return { cwd: child, active_source: 'cwd_child', resolved_project: projectInfo(child) };
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
// 5b. cwd_child (NO anchor) — F2 [trp_71accb07]: even without a BRAINCLAW_CWD
|
|
160
|
+
// anchor, an agent physically inside a child project must resolve THAT
|
|
161
|
+
// child rather than a stale/shared global pointer set by another agent.
|
|
162
|
+
// Ceiling = the discovered workspace root via resolveWorkspaceRoot(baseCwd)
|
|
163
|
+
// — NOT os.homedir() (that would revive the F6 boundary edge and could let
|
|
164
|
+
// an unrelated home store influence a monorepo worker). Same containment
|
|
165
|
+
// guard as the anchored case (isAtOrBelow + a child strictly below).
|
|
166
|
+
// For a single-project repo this is a strict no-op: findClosestStoreBelow
|
|
167
|
+
// walks UP to the ceiling EXCLUSIVELY, so it can never return the lone root
|
|
168
|
+
// store (Codex cadrage non-regression proof, batch 2).
|
|
169
|
+
if (!hasEnvWorkspace) {
|
|
170
|
+
const physicalRoot = resolveWorkspaceRoot(baseCwd, options.storeChainOptions);
|
|
171
|
+
if (physicalRoot && isAtOrBelow(baseCwd, physicalRoot)) {
|
|
172
|
+
const child = findClosestStoreBelow(baseCwd, physicalRoot);
|
|
173
|
+
if (child && path.resolve(child) !== path.resolve(physicalRoot)) {
|
|
174
|
+
return { cwd: child, active_source: 'cwd_child', resolved_project: projectInfo(child) };
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
// 6. Global active-project.json from workspace root
|
|
132
179
|
const wsRoot = hasEnvWorkspace ? anchorCwd : resolveWorkspaceRoot(anchorCwd, options.storeChainOptions);
|
|
133
180
|
if (wsRoot) {
|
|
134
181
|
const active = loadActiveProject(wsRoot);
|
|
135
182
|
if (active && fs.existsSync(path.join(active.path, MEMORY_DIR, 'config.yaml'))) {
|
|
136
|
-
return active.path;
|
|
183
|
+
return { cwd: active.path, active_source: 'global', resolved_project: { path: active.path, name: active.name } };
|
|
137
184
|
}
|
|
138
185
|
}
|
|
139
|
-
//
|
|
140
|
-
return anchorCwd;
|
|
186
|
+
// 7. Default
|
|
187
|
+
return { cwd: anchorCwd, active_source: 'cwd', resolved_project: projectInfo(anchorCwd) };
|
|
188
|
+
}
|
|
189
|
+
function projectInfo(cwd) {
|
|
190
|
+
try {
|
|
191
|
+
const config = loadConfig(cwd);
|
|
192
|
+
return { path: cwd, name: config.project_name };
|
|
193
|
+
}
|
|
194
|
+
catch {
|
|
195
|
+
return { path: cwd };
|
|
196
|
+
}
|
|
141
197
|
}
|
|
142
198
|
/**
|
|
143
199
|
* 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
|
/**
|