# 架构：多 Shell 解析与策略评估

面向维护者的实现说明：分层、Shell 后端、tree-sitter 运行时与解析失败处理。

- 策略怎么写（yaml 规则语法）：[policy.md](policy.md)
- 当前版本已知、且无法在本项目内解决的限制：[known-issues.md](known-issues.md)
- 共享核心库内部结构：`ast-guard-core` 仓库的 README

## 数据流与分层

Pi 的 `tool_call` 事件 → 宿主工具适配 → 核心引擎评估 → `allow` 放行 / `ask` 弹窗 /
`block` 拦截（`ask` 在 DNA 模式下按 `settings.dna` 自动回答，不弹窗）。

| 层 | 位置 | 职责 |
| --- | --- | --- |
| 宿主接线 | `src/index.ts` | 事件注册、命令（`/ag:dna`、`/ag:forget`、`/ag:status`）、拦截对话框与状态栏、DNA 状态与提醒 |
| 工具适配 | `src/extension/tool-adapter.ts` | `bash` / `powershell` → `engine.checkBash`；`read`/`write`/`edit`/`grep`/`find`/`ls`/`apply_patch` → `evaluateFileAccess` |
| 共享核心 | `ast-guard-core`（独立仓库） | shell 解析、命令/文件意图提取、策略加载与匹配、评估引擎、会话决策数据模型、文案 |
| 内置策略 | `config/default-policy.yaml` + `src/policy/default-policy.ts` | 宿主自带的默认规则与 schema |

宿主只从核心包根 import（`import { ... } from "ast-guard-core"`），不用深路径；默认策略
由宿主注入，核心不含 `.pi/` 等宿主路径。

## Shell 后端与语言检测

bash 与 pwsh 都走 tree-sitter，后端选择只看**脚本内容**，与工具名无关。宿主的
`bash` 与 `powershell`（pi ≥ 0.84.3）两个工具都送进同一条 `engine.checkBash` 路径
（见 `src/extension/tool-adapter.ts` 的 `isToolCallEventType("powershell", …)` 重载）。

- 语言无关模型（核心 `src/shell/types.ts`）：

  ```ts
  interface ShellBackend {
    language: ShellLanguage;              // "bash" | "powershell"
    parse(script: string): ShellAnalysis;
  }
  interface ShellAnalysis {
    language: ShellLanguage;
    commands: CommandInvocation[];
    pipelines: PipelineInvocation[];
    parseError?: string;
  }
  ```

- `CommandInvocation`：argv（`ShellWordValue[]`）、重定向、stdin/stdout、子进程边界链
  （`boundaryPath`：subshell / 命令替换 / 进程替换 / 多命令管道 / 后台 / `bash -c` 各占一层）、
  `dynamic`、来源 `CommandSource`。
- `ShellWordValue { text, dynamic, quoted, alternatives?, numeric? }`：能静态确定的文本原样
  保留；参数展开、命令替换、算术/进程替换、花括号展开等运行期才能确定的部分整体标记
  `dynamic` 且文本留空，避免把动态路径当成静态路径。
- `CommandSource` 覆盖 bash 侧（`top-level`、`compound`、`subshell`、`background`、
  `command-substitution`、`process-substitution`、`bash-c`、`eval`、`exec`、`function`）
  与 pwsh 侧（`call-operator`、`dot-source`、`scriptblock`、`subexpression`、
  `invoke-expression`）。
- 语言检测 `detectShellLanguage`（核心 `src/shell/detect.ts`）是启发式，保守倾向：出现
  cmdlet 动词-名词模式（`Get-ChildItem` 等）、pwsh 专属标记（`$env:`、`Write-Host`、
  `ForEach-Object`、`@(`…）或哈希表字面量（`@{ k = v }`）才判为 `powershell`，其余一律 `bash`。
  纯别名写法（`rm`、`del`、`echo`…）的 pwsh 脚本会被判为 bash，由 bash 规则覆盖。
- `settings.language` 是 **UI 提示语言**（`zh`/`en`/`auto`），与脚本语言无关。

## tree-sitter 运行时资产与初始化

- wasm grammar 随 `ast-guard-core` 发布：`grammars/tree-sitter-bash.wasm`、
  `grammars/tree-sitter-powershell.wasm`。`tree-sitter-bash` / `tree-sitter-powershell`
  只是 wasm 来源（核心仓库的 devDependencies）；运行时依赖只有 `web-tree-sitter`，
  无 node-gyp 原生构建。wasm 路径按产物布局回退（`src/shell`、`lib/shell` 两层深 →
  `../../grammars/`；`dist/` 单文件一层深 → `../grammars/`）。
- 懒加载单例（核心 `src/shell/treesitter.ts`）：`getParser(language)` 只初始化一次
  `Parser.init()`、按语法缓存 `Language.load()`，每次返回新的轻量 `Parser`。
- 预热入口：`warmParsers()`（同时加载 bash + pwsh）、`ensureBashReady()`、
  `ensurePowerShellReady()`。宿主在 `refreshEngine`（会话开始与 `/ag:status`）中调用。
- 初始化是**异步**的：直接调用 `analyzeBash` / `analyzePowerShell` 的脚本必须先
  `await ensureBashReady()` / `ensurePowerShellReady()`（`tests/setup.ts` 就是这么做的），
  否则解析器未就绪会退化成 `parseError`。

## 无类型 CST 的处理策略

tree-sitter 只提供 `SyntaxNode` + 字符串节点类型，没有类型化 AST 的判别联合：

- 字符串分派收敛在类型守卫层（`isX(node)`），业务逻辑保持类型化。
- word 渲染按 CST 节点形态重建（bash 的 `simple_expansion`、`command_substitution` 等；
  pwsh 的 `variable_expression`、`command_expression` 等）。
- CST 不区分 heredoc / here-string / 文件重定向，语义在 walk 中重建。
- 多语言分析采取保守近似（over-approximation）：分支全遍历、未知节点按 `dynamic` 处理，
  与「安全工具多拦截优于漏拦截」一致。
- 出错时 `describeParseErrors()` 从 CST 找第一个 `ERROR` / missing 节点，生成「行、列、
  片段」定位，供 parseFailure 的 reason 展示。

## PowerShell 后端要点

- pwsh 把一切当表达式，CST 比 bash 深得多；walk 只在 command / pipeline / statement 级
  取文本与动态性判定。
- 命令名小写化（pwsh 不区分大小写），规则匹配大小写不敏感。
- `Invoke-Expression` / `iex` 的静态负载递归解析（受最大嵌套深度限制）；`& ./x.ps1`、
  `. ./x.ps1` 外部脚本不内联分析（无源码），命令本身以动态名记录。
- 文件意图覆盖 pwsh 命令：`get-content`、`get-childitem`、`remove-item`、`set-content`、
  `add-content`、`out-file`、`clear-content`、`move-item`、`copy-item`，并由
  `Set-Location` 追踪 cd。
- 内置 pwsh 规则：`pwsh-remove-item`、`pwsh-remove-item-dynamic`、`pwsh-invoke-expression`、
  `pwsh-clear-content`、`pwsh-start-process`（见 `config/default-policy.yaml`）。

## 解析失败与回退

- `ShellAnalysis.parseError` 非空时整脚本不产生 `commands` / `pipelines`，引擎只产出
  parseFailure：动作取 `settings.parseFailure`（默认 `ask`；DNA 模式下
  `settings.dna.parseFailure`，默认 `block`）。命令级规则因此不参与评估。
- pwsh 检测是启发式的，可能把内嵌 PowerShell 文本的 bash 命令（如
  `powershell -Command "Get-X"`、`node -e "...Get-X..."`）误判为 pwsh，而 pwsh 解析器解析
  不了 bash 语法。核心 `evaluateDamageControl` 对判为 pwsh 的脚本做回退：pwsh 解析失败而
  bash 解析成功时改用 bash 结果（避免把合法 bash 命令误报 parseFailure）；两者都失败则保留
  pwsh 结果，仍按 parseFailure 处理。
