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.
Files changed (149) hide show
  1. package/README.md +631 -499
  2. package/dist/brainclaw-vscode.vsix +0 -0
  3. package/dist/cli.js +18 -1
  4. package/dist/commands/code-map.js +129 -0
  5. package/dist/commands/codev.js +7 -0
  6. package/dist/commands/harvest.js +1 -1
  7. package/dist/commands/hooks.js +73 -73
  8. package/dist/commands/init.js +1 -1
  9. package/dist/commands/install-hooks.js +78 -78
  10. package/dist/commands/mcp-read-handlers.js +57 -14
  11. package/dist/commands/mcp.js +200 -13
  12. package/dist/commands/run-profile.js +3 -2
  13. package/dist/commands/switch.js +125 -93
  14. package/dist/commands/version.js +1 -1
  15. package/dist/core/agent-capability.js +19 -4
  16. package/dist/core/agent-files.js +131 -119
  17. package/dist/core/code-map/backend.js +123 -0
  18. package/dist/core/code-map/core.js +81 -0
  19. package/dist/core/code-map/drafts.js +2 -0
  20. package/dist/core/code-map/extractor.js +29 -0
  21. package/dist/core/code-map/finalizer.js +191 -0
  22. package/dist/core/code-map/freshness.js +108 -0
  23. package/dist/core/code-map/ids.js +0 -0
  24. package/dist/core/code-map/importable.js +35 -0
  25. package/dist/core/code-map/indexes.js +197 -0
  26. package/dist/core/code-map/lang/java/imports.scm +17 -0
  27. package/dist/core/code-map/lang/java/index.js +254 -0
  28. package/dist/core/code-map/lang/java/tags.scm +48 -0
  29. package/dist/core/code-map/lang/php/imports.scm +21 -0
  30. package/dist/core/code-map/lang/php/index.js +251 -0
  31. package/dist/core/code-map/lang/php/tags.scm +44 -0
  32. package/dist/core/code-map/lang/provider.js +9 -0
  33. package/dist/core/code-map/lang/providers.js +24 -0
  34. package/dist/core/code-map/lang/python/imports.scm +90 -0
  35. package/dist/core/code-map/lang/python/index.js +364 -0
  36. package/dist/core/code-map/lang/python/tags.scm +81 -0
  37. package/dist/core/code-map/lang/query-runtime.js +374 -0
  38. package/dist/core/code-map/lang/registry.js +125 -0
  39. package/dist/core/code-map/lang/typescript/imports.scm +90 -0
  40. package/dist/core/code-map/lang/typescript/index.js +306 -0
  41. package/dist/core/code-map/lang/typescript/tags.js.scm +106 -0
  42. package/dist/core/code-map/lang/typescript/tags.scm +151 -0
  43. package/dist/core/code-map/lock.js +210 -0
  44. package/dist/core/code-map/materialized.js +51 -0
  45. package/dist/core/code-map/memory-reader.js +59 -0
  46. package/dist/core/code-map/paths.js +53 -0
  47. package/dist/core/code-map/query.js +568 -0
  48. package/dist/core/code-map/refresh.js +0 -0
  49. package/dist/core/code-map/resolve.js +177 -0
  50. package/dist/core/code-map/store.js +206 -0
  51. package/dist/core/code-map/types.js +288 -0
  52. package/dist/core/code-map/vocabulary.js +57 -0
  53. package/dist/core/code-map/wasm-loader.js +294 -0
  54. package/dist/core/code-map/work-section.js +206 -0
  55. package/dist/core/codev-prompts.js +38 -38
  56. package/dist/core/codev-rounds.js +4 -0
  57. package/dist/core/default-profiles/doctor.yaml +11 -11
  58. package/dist/core/default-profiles/janitor.yaml +11 -11
  59. package/dist/core/default-profiles/onboarder.yaml +11 -11
  60. package/dist/core/default-profiles/reviewer.yaml +13 -13
  61. package/dist/core/dispatcher.js +1 -1
  62. package/dist/core/entity-operations.js +29 -3
  63. package/dist/core/execution-adapters.js +11 -10
  64. package/dist/core/execution-profile.js +58 -0
  65. package/dist/core/execution.js +1 -1
  66. package/dist/core/facade-schema.js +9 -0
  67. package/dist/core/instruction-templates.js +2 -0
  68. package/dist/core/loops/verbs.js +0 -1
  69. package/dist/core/mcp-command-resolution.js +3 -1
  70. package/dist/core/messaging.js +2 -2
  71. package/dist/core/protocol-skills.js +164 -164
  72. package/dist/core/runtime-signals.js +1 -1
  73. package/dist/core/search.js +19 -2
  74. package/dist/core/security-guard.js +207 -207
  75. package/dist/core/spawn-check.js +16 -2
  76. package/dist/core/staleness.js +1 -1
  77. package/dist/core/store-resolution.js +67 -11
  78. package/dist/core/worktree.js +18 -18
  79. package/dist/facts.js +9 -5
  80. package/dist/facts.json +8 -4
  81. package/dist/vendor/web-tree-sitter/tree-sitter.js +3980 -0
  82. package/dist/vendor/web-tree-sitter/tree-sitter.wasm +0 -0
  83. package/dist/wasm/tree-sitter-java.wasm +0 -0
  84. package/dist/wasm/tree-sitter-javascript.wasm +0 -0
  85. package/dist/wasm/tree-sitter-php.wasm +0 -0
  86. package/dist/wasm/tree-sitter-python.wasm +0 -0
  87. package/dist/wasm/tree-sitter-tsx.wasm +0 -0
  88. package/dist/wasm/tree-sitter-typescript.wasm +0 -0
  89. package/dist/wasm/tree-sitter.wasm +0 -0
  90. package/docs/PROTOCOL.md +1 -1
  91. package/docs/adapters/openclaw.md +43 -43
  92. package/docs/architecture/project-refs.md +328 -328
  93. package/docs/cli.md +2131 -2093
  94. package/docs/code-map.md +198 -0
  95. package/docs/concepts/coordination.md +52 -52
  96. package/docs/concepts/coordinator-runbook.md +129 -129
  97. package/docs/concepts/dispatch-lifecycle.md +245 -245
  98. package/docs/concepts/event-log-store.md +928 -928
  99. package/docs/concepts/ideation-loop.md +317 -317
  100. package/docs/concepts/loop-engine.md +520 -511
  101. package/docs/concepts/mcp-governance.md +268 -268
  102. package/docs/concepts/memory.md +84 -84
  103. package/docs/concepts/multi-agent-workflows.md +167 -167
  104. package/docs/concepts/observer-protocol.md +361 -361
  105. package/docs/concepts/plans-and-claims.md +217 -217
  106. package/docs/concepts/project-md-convention.md +35 -35
  107. package/docs/concepts/runtime-notes.md +38 -38
  108. package/docs/concepts/troubleshooting.md +254 -254
  109. package/docs/concepts/workspace-bootstrapping.md +142 -142
  110. package/docs/context-format-changelog.md +35 -35
  111. package/docs/context-format.md +48 -48
  112. package/docs/index.md +65 -65
  113. package/docs/integrations/agents.md +158 -158
  114. package/docs/integrations/claude-code.md +23 -23
  115. package/docs/integrations/cline.md +77 -77
  116. package/docs/integrations/continue.md +55 -55
  117. package/docs/integrations/copilot.md +68 -68
  118. package/docs/integrations/cursor.md +23 -23
  119. package/docs/integrations/kilocode.md +72 -72
  120. package/docs/integrations/mcp.md +385 -378
  121. package/docs/integrations/mistral-vibe.md +122 -122
  122. package/docs/integrations/openclaw.md +92 -92
  123. package/docs/integrations/opencode.md +84 -84
  124. package/docs/integrations/overview.md +115 -115
  125. package/docs/integrations/roo.md +71 -71
  126. package/docs/integrations/windsurf.md +77 -77
  127. package/docs/mcp-schema-changelog.md +364 -356
  128. package/docs/playbooks/integration/index.md +121 -121
  129. package/docs/playbooks/orchestration.md +37 -0
  130. package/docs/playbooks/productivity/index.md +99 -99
  131. package/docs/playbooks/team/index.md +117 -117
  132. package/docs/product/agent-first-model.md +184 -184
  133. package/docs/product/entity-model-audit.md +462 -462
  134. package/docs/product/positioning.md +86 -86
  135. package/docs/quickstart-existing-project.md +107 -107
  136. package/docs/quickstart.md +183 -183
  137. package/docs/release-maintenance.md +79 -79
  138. package/docs/reputation.md +52 -52
  139. package/docs/review.md +45 -45
  140. package/docs/security.md +212 -212
  141. package/docs/server-operations.md +118 -118
  142. package/docs/storage.md +106 -106
  143. package/package.json +86 -66
  144. package/docs/concepts/event-log-store-critique-A.md +0 -333
  145. package/docs/concepts/event-log-store-critique-B.md +0 -353
  146. package/docs/concepts/event-log-store-phase0-measurements.md +0 -58
  147. package/docs/concepts/event-log-store-proposal-A.md +0 -365
  148. package/docs/concepts/event-log-store-proposal-B.md +0 -404
  149. 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) {
@@ -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?.invoke_template || !profile?.invoke_binary || !profile.runtime.canBeSpawnedCli) {
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,
@@ -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 LeaseUp `service_tier` trap that the API later
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. Global active-project.json in workspace root
98
- * 6. Workspace anchor or process.cwd()
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
- return path.resolve(options.explicitCwd);
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 = loadCurrentSession(anchorCwd);
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. Global active-project.json from workspace root
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
- // 6. Default
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
@@ -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
  /**