sigit-code 1.4.0__tar.gz → 1.5.0__tar.gz

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 (76) hide show
  1. {sigit_code-1.4.0 → sigit_code-1.5.0}/.agents/AGENTS.md +64 -12
  2. sigit_code-1.5.0/.github/workflows/release-mcp-registry.yml +79 -0
  3. {sigit_code-1.4.0 → sigit_code-1.5.0}/AGENTS.md +64 -12
  4. {sigit_code-1.4.0 → sigit_code-1.5.0}/CHANGELOG.md +72 -0
  5. {sigit_code-1.4.0 → sigit_code-1.5.0}/CLAUDE.md +64 -12
  6. {sigit_code-1.4.0 → sigit_code-1.5.0}/Cargo.lock +1 -1
  7. {sigit_code-1.4.0 → sigit_code-1.5.0}/Cargo.toml +1 -1
  8. {sigit_code-1.4.0 → sigit_code-1.5.0}/PKG-INFO +2 -2
  9. {sigit_code-1.4.0 → sigit_code-1.5.0}/README.md +1 -1
  10. sigit_code-1.5.0/docs/hooks.md +92 -0
  11. sigit_code-1.5.0/docs/mcp.md +68 -0
  12. sigit_code-1.5.0/examples/settings-with-hooks.toml +49 -0
  13. {sigit_code-1.4.0 → sigit_code-1.5.0}/npm/README.md.tmpl +1 -1
  14. {sigit_code-1.4.0 → sigit_code-1.5.0}/npm/sigit/README.md +1 -1
  15. {sigit_code-1.4.0 → sigit_code-1.5.0}/pypi/README.md +1 -1
  16. sigit_code-1.5.0/server.json +28 -0
  17. {sigit_code-1.4.0 → sigit_code-1.5.0}/src/chat.rs +923 -38
  18. sigit_code-1.5.0/src/commands.rs +436 -0
  19. sigit_code-1.5.0/src/frontmatter.rs +200 -0
  20. sigit_code-1.5.0/src/hooks.rs +507 -0
  21. {sigit_code-1.4.0 → sigit_code-1.5.0}/src/main.rs +166 -20
  22. {sigit_code-1.4.0 → sigit_code-1.5.0}/src/mcp.rs +148 -5
  23. {sigit_code-1.4.0 → sigit_code-1.5.0}/src/permissions.rs +179 -18
  24. {sigit_code-1.4.0 → sigit_code-1.5.0}/src/settings.rs +17 -2
  25. {sigit_code-1.4.0 → sigit_code-1.5.0}/src/skills.rs +6 -149
  26. sigit_code-1.5.0/src/subagents.rs +327 -0
  27. {sigit_code-1.4.0 → sigit_code-1.5.0}/src/tools.rs +537 -77
  28. {sigit_code-1.4.0 → sigit_code-1.5.0}/.agents/skills/agent-client-protocol/SKILL.md +0 -0
  29. {sigit_code-1.4.0 → sigit_code-1.5.0}/.agents/skills/ai-assisted-coding/SKILL.md +0 -0
  30. {sigit_code-1.4.0 → sigit_code-1.5.0}/.agents/skills/branding/SKILL.md +0 -0
  31. {sigit_code-1.4.0 → sigit_code-1.5.0}/.agents/skills/run-sigit/SKILL.md +0 -0
  32. {sigit_code-1.4.0 → sigit_code-1.5.0}/.agents/skills/run-sigit/driver.mjs +0 -0
  33. {sigit_code-1.4.0 → sigit_code-1.5.0}/.agents/skills/run-sigit/tui-smoke.sh +0 -0
  34. {sigit_code-1.4.0 → sigit_code-1.5.0}/.agents/skills/sigit-code-release/SKILL.md +0 -0
  35. {sigit_code-1.4.0 → sigit_code-1.5.0}/.agents/skills/tool-calling/SKILL.md +0 -0
  36. {sigit_code-1.4.0 → sigit_code-1.5.0}/.claude/skills/agent-client-protocol/SKILL.md +0 -0
  37. {sigit_code-1.4.0 → sigit_code-1.5.0}/.claude/skills/ai-assisted-coding/SKILL.md +0 -0
  38. {sigit_code-1.4.0 → sigit_code-1.5.0}/.claude/skills/branding/SKILL.md +0 -0
  39. {sigit_code-1.4.0 → sigit_code-1.5.0}/.claude/skills/run-sigit/SKILL.md +0 -0
  40. {sigit_code-1.4.0 → sigit_code-1.5.0}/.claude/skills/run-sigit/driver.mjs +0 -0
  41. {sigit_code-1.4.0 → sigit_code-1.5.0}/.claude/skills/run-sigit/tui-smoke.sh +0 -0
  42. {sigit_code-1.4.0 → sigit_code-1.5.0}/.claude/skills/sigit-code-release/SKILL.md +0 -0
  43. {sigit_code-1.4.0 → sigit_code-1.5.0}/.claude/skills/tool-calling/SKILL.md +0 -0
  44. {sigit_code-1.4.0 → sigit_code-1.5.0}/.github/workflows/ci.yml +0 -0
  45. {sigit_code-1.4.0 → sigit_code-1.5.0}/.github/workflows/release-crates.yml +0 -0
  46. {sigit_code-1.4.0 → sigit_code-1.5.0}/.github/workflows/release-github.yml +0 -0
  47. {sigit_code-1.4.0 → sigit_code-1.5.0}/.github/workflows/release-homebrew.yml +0 -0
  48. {sigit_code-1.4.0 → sigit_code-1.5.0}/.github/workflows/release-npm.yml +0 -0
  49. {sigit_code-1.4.0 → sigit_code-1.5.0}/.github/workflows/release-pypi.yml +0 -0
  50. {sigit_code-1.4.0 → sigit_code-1.5.0}/.gitignore +0 -0
  51. {sigit_code-1.4.0 → sigit_code-1.5.0}/.nvmrc +0 -0
  52. {sigit_code-1.4.0 → sigit_code-1.5.0}/LICENSE +0 -0
  53. {sigit_code-1.4.0 → sigit_code-1.5.0}/examples/skills/README.md +0 -0
  54. {sigit_code-1.4.0 → sigit_code-1.5.0}/examples/skills/commit-message/SKILL.md +0 -0
  55. {sigit_code-1.4.0 → sigit_code-1.5.0}/npm/package-main.json.tmpl +0 -0
  56. {sigit_code-1.4.0 → sigit_code-1.5.0}/npm/package.json.tmpl +0 -0
  57. {sigit_code-1.4.0 → sigit_code-1.5.0}/npm/scripts/render-main-package.cjs +0 -0
  58. {sigit_code-1.4.0 → sigit_code-1.5.0}/npm/scripts/render-platform-package.cjs +0 -0
  59. {sigit_code-1.4.0 → sigit_code-1.5.0}/npm/sigit/.gitignore +0 -0
  60. {sigit_code-1.4.0 → sigit_code-1.5.0}/npm/sigit/package.json +0 -0
  61. {sigit_code-1.4.0 → sigit_code-1.5.0}/npm/sigit/src/index.ts +0 -0
  62. {sigit_code-1.4.0 → sigit_code-1.5.0}/npm/sigit/tsconfig.json +0 -0
  63. {sigit_code-1.4.0 → sigit_code-1.5.0}/pypi/pyproject.toml +0 -0
  64. {sigit_code-1.4.0 → sigit_code-1.5.0}/pyproject.toml +0 -0
  65. {sigit_code-1.4.0 → sigit_code-1.5.0}/rust-toolchain.toml +0 -0
  66. {sigit_code-1.4.0 → sigit_code-1.5.0}/src/account.rs +0 -0
  67. {sigit_code-1.4.0 → sigit_code-1.5.0}/src/backend.rs +0 -0
  68. {sigit_code-1.4.0 → sigit_code-1.5.0}/src/credentials.rs +0 -0
  69. {sigit_code-1.4.0 → sigit_code-1.5.0}/src/headless.rs +0 -0
  70. {sigit_code-1.4.0 → sigit_code-1.5.0}/src/instructions.rs +0 -0
  71. {sigit_code-1.4.0 → sigit_code-1.5.0}/src/models.rs +0 -0
  72. {sigit_code-1.4.0 → sigit_code-1.5.0}/src/provider.rs +0 -0
  73. {sigit_code-1.4.0 → sigit_code-1.5.0}/src/session_store.rs +0 -0
  74. {sigit_code-1.4.0 → sigit_code-1.5.0}/src/setup.rs +0 -0
  75. {sigit_code-1.4.0 → sigit_code-1.5.0}/tests/acp_permissions.rs +0 -0
  76. {sigit_code-1.4.0 → sigit_code-1.5.0}/tests/headless_mode.rs +0 -0
@@ -110,7 +110,23 @@ feeds results back. Neither the loop nor ACP/TUI surfaces depend on a concrete b
110
110
  spec list (`all_tools`) and the execute `match` (`execute_tool`). `run_command` also enforces
111
111
  commit attribution: when a command creates a new commit that lacks the
112
112
  `Co-Authored-By: siGit Code` trailer (`COMMIT_CO_AUTHOR_TRAILER`), it amends the trailer in —
113
- unless the commit already exists on a remote, which is never rewritten.
113
+ unless the commit already exists on a remote, which is never rewritten. Also owns the `task`
114
+ tool: a nested agent loop in a fresh conversation, offered only when `subagent_available()`
115
+ (a subagent factory is registered — see `register_subagent_factory_for` in `main.rs`; on-device
116
+ registers a `None`-returning factory since onde has a single shared history). A subagent's
117
+ toolset is a hard-gated read-only allow-list (`SUBAGENT_TOOL_NAMES`) that is never expanded by a
118
+ configurable subagent type (see `src/subagents.rs`) — only ever narrowed — so a `.sigit/agents/*.md`
119
+ file can't grant itself `edit_file`/`run_command` and bypass the permission system. Also owns
120
+ `web_search`: a thin native wrapper around the official MCP server's Brave-Search-backed
121
+ `web_search` tool (`mcp__sigit__web_search`, implemented server-side in `sigit-si`'s
122
+ `WebSearchService` + `Mcp::Tools::WebSearch`), offered only when that MCP tool was actually
123
+ discovered (i.e. the user is signed in to siGit Code Cloud — see `web_search_available`).
124
+ Wrapping it natively rather than leaving it as a raw `mcp__*` tool matters for two reasons:
125
+ `permissions::classify` treats every `mcp__*` tool as mutating (an "ask" prompt on every call),
126
+ while `web_search` is read-only like `read_website`; and the raw delegate name is filtered out
127
+ of the assembled tool list (`is_web_search_delegate`) so the model sees one clean option, not
128
+ two names for the same tool. Execution still forwards verbatim to `mcp::call_tool` — no second
129
+ HTTP/JSON-RPC implementation.
114
130
  - **`src/skills.rs`** — [Agent Skills](https://agentskills.io) support. Discovers skill
115
131
  folders (each with a `SKILL.md`: YAML frontmatter `name` + `description`, then Markdown
116
132
  instructions) from `.sigit/skills/` and `.claude/skills/` in the cwd, `$SIGIT_CONFIG_DIR/skills/`,
@@ -119,6 +135,31 @@ feeds results back. Neither the loop nor ACP/TUI surfaces depend on a concrete b
119
135
  calls `skill` with a name) loads the full `SKILL.md` body. The `skill` tool is appended in the
120
136
  `*_as_specs`/`build_tool_specs` layer (not in `all_tools()`) so its description can be dynamic,
121
137
  and only when at least one skill exists.
138
+ - **`src/commands.rs`** — user-defined slash commands. Discovers Markdown files (each an
139
+ optional YAML frontmatter block — `description`, `argument-hint` — followed by a prompt-template
140
+ body) from `.sigit/commands/` and `.claude/commands/` in the cwd, `$SIGIT_CONFIG_DIR/commands/`,
141
+ and `~/.claude/commands/`. A subdirectory namespaces the command with `:`
142
+ (`.sigit/commands/git/commit.md` → `/git:commit`). Unlike skills there's no tool-call
143
+ indirection: invoking one works exactly like the built-in `/init` — `commands::render`
144
+ substitutes `$ARGUMENTS`/`$1..$9` in the body against whatever followed the command on the
145
+ input line, and the result is fed to the model as a normal turn through the ordinary tools and
146
+ permission checks. Resolution happens where each surface (`main.rs`/`chat.rs`) already
147
+ special-cases `/init`: an unrecognized slash command is tried against `commands::resolve_command`
148
+ before falling back to "unknown command". A custom command sharing a name with a built-in is
149
+ unreachable (built-ins match first in `parse_slash`) and is skipped when advertised to ACP
150
+ clients, with a warning logged.
151
+ - **`src/subagents.rs`** — configurable subagent types for the `task` tool. Discovers Markdown
152
+ files (YAML frontmatter `name` + `description`, optional comma-separated `tools:` allow-list,
153
+ then a Markdown body that becomes the subagent's system prompt) from `.sigit/agents/` and
154
+ `.claude/agents/` in the cwd, `$SIGIT_CONFIG_DIR/agents/`, and `~/.claude/agents/`. Passing a
155
+ type's `name` as `task`'s `subagent_type` argument swaps in that system prompt and, if `tools:`
156
+ is set, narrows the offered toolset to its *intersection* with `SUBAGENT_TOOL_NAMES` — the
157
+ security-relevant narrowing logic lives in `tools.rs` next to that constant, not here; this
158
+ module only discovers and parses files. `SubagentFactory` (in `tools.rs`) takes the resolved
159
+ system prompt per call rather than baking one in at registration, so a single registered
160
+ factory serves both the default research subagent and every configured type.
161
+ - **`src/frontmatter.rs`** — shared "YAML frontmatter + Markdown body" parsing used by both
162
+ `src/skills.rs` (`SKILL.md`) and `src/commands.rs` (`.sigit/commands/*.md`).
122
163
  - **`src/mcp.rs`** — [Model Context Protocol](https://modelcontextprotocol.io) *client*. Two
123
164
  transports: **Streamable HTTP** (one JSON-RPC POST endpoint, `url` in `mcp.toml`; replies are
124
165
  `application/json` or SSE) and **stdio** (`command` + optional `args`/`[server.env]` in
@@ -131,12 +172,20 @@ feeds results back. Neither the loop nor ACP/TUI surfaces depend on a concrete b
131
172
  both read it; `/reload` does *not* re-run it, so config changes need a restart. stdio children
132
173
  live for the process; a dead child fails calls with an in-band error string (no auto-restart).
133
174
  Tools are namespaced `mcp__<server>__<tool>`, appended in the `*_as_specs`/`build_tool_specs`
134
- layer and routed in `tools::execute_tool` via `mcp::is_mcp_tool`. The official server
135
- (`<cloud>/mcp`, default `https://sigit.si/api/v1/mcp`) is baked in (always HTTP) and authed
136
- with the cloud session token; extra servers live in `mcp.toml` (global
175
+ layer and routed in `tools::execute_tool` via `mcp::is_mcp_tool`. Two servers are baked in:
176
+ the official server (`<cloud>/mcp`, default `https://sigit.si/api/v1/mcp`, always HTTP, authed
177
+ with the cloud session token) and the smbCloud CLI server (`smb --mcp`, stdio, added only when
178
+ the `smb` binary is on `PATH`; opt out with `smbcloud = false` in `mcp.toml` or
179
+ `SIGIT_MCP_SMBCLOUD=off`). A user-defined entry named `sigit` or `smbcloud` overrides the
180
+ corresponding baked-in one. Extra servers live in `mcp.toml` (global
137
181
  `$SIGIT_CONFIG_DIR/mcp.toml` and project-local `.sigit/mcp.toml`). The stdio path is covered by
138
182
  `tests/mcp_stdio.rs`, driven by the test-only `src/bin/mcp_stdio_stub.rs` helper binary
139
- (excluded from the published crate via `exclude` in `Cargo.toml`).
183
+ (excluded from the published crate via `exclude` in `Cargo.toml`). The baked-in official
184
+ server is *also* listed in the public MCP Registry as `si.sigit/sigit` — a **remote**
185
+ Streamable-HTTP listing (`server.json` at the repo root, published by
186
+ `release-mcp-registry.yml`). Because it's a remote server, the registry's URL-match rule
187
+ forces a domain namespace (`si.sigit` ↔ `sigit.si`) verified by a DNS TXT record, not the
188
+ GitHub-OIDC scheme `smbcloud-cli` uses for its package listing.
140
189
  - **`src/permissions.rs`** — tool permission policy. Every tool call passes through
141
190
  `decision_for` before executing: read-only tools always run; mutating tools (and all
142
191
  `mcp__*`/unknown tools) are governed by, in order: per-session plan mode (`/plan` — deny all
@@ -165,12 +214,14 @@ feeds results back. Neither the loop nor ACP/TUI surfaces depend on a concrete b
165
214
  - **`src/credentials.rs`** — local session-token store (TOML, `0600` on Unix).
166
215
  - **`src/models.rs`** — model-picker types shared across platforms.
167
216
 
168
- Slash commands (`/help`, `/models`, `/skills`, `/mcp`, `/login`, `/logout`, `/whoami`, `/reload`,
169
- `/plan`, `/permissions`, `/init`, `/clear`, `/status`) are advertised via `advertise_commands` in
170
- `main.rs` and handled in both the TUI and ACP sessions. `/init` is special: instead of replying
171
- directly it substitutes `instructions::INIT_PROMPT` for the user text and runs a normal agent
172
- turn that explores the repo and writes (or improves) `AGENTS.md` through the ordinary tools and
173
- permission checks.
217
+ Slash commands (`/help`, `/models`, `/skills`, `/agents`, `/commands`, `/mcp`, `/login`, `/logout`,
218
+ `/whoami`, `/reload`, `/plan`, `/permissions`, `/init`, `/clear`, `/status`) are advertised via
219
+ `advertise_commands` in `main.rs` and handled in both the TUI and ACP sessions. `/init` is
220
+ special: instead of replying directly it substitutes `instructions::INIT_PROMPT` for the user
221
+ text and runs a normal agent turn that explores the repo and writes (or improves) `AGENTS.md`
222
+ through the ordinary tools and permission checks. User-defined commands (see `src/commands.rs`)
223
+ get the same treatment via the `SlashCommand::Unknown` fallback path, so anyone can add their own
224
+ `/name` commands without touching the built-in command list.
174
225
 
175
226
  ## Model cache (macOS)
176
227
 
@@ -190,7 +241,8 @@ verbosity with `RUST_LOG`.
190
241
 
191
242
  `OPENAI_BASE_URL` / `OPENAI_API_KEY` (provider override), `SIGIT_API_URL` (account API base,
192
243
  default `https://sigit.si`), `SIGIT_CLOUD_URL`, `SIGIT_CONFIG_DIR` (default `~/.config/sigit`),
193
- `SIGIT_MODEL`, `SIGIT_MCP` (`off` disables MCP), `SIGIT_MCP_OFFICIAL` (`off` drops the baked-in
244
+ `SIGIT_MODEL`, `SIGIT_MCP` (`off` disables MCP), `SIGIT_MCP_SMBCLOUD` (`off` drops the baked-in
245
+ smbCloud CLI server), `SIGIT_MCP_OFFICIAL` (`off` drops the baked-in
194
246
  server), `SIGIT_PERMISSIONS` (`allow`/`ask`/`deny` — overrides the default permission mode for
195
247
  mutating tools; the escape hatch for clients without permission-request support),
196
248
  `HF_HOME` / `HF_HUB_CACHE`, `RUST_LOG`.
@@ -0,0 +1,79 @@
1
+ name: MCP Registry Release
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ inputs:
6
+ version:
7
+ description: "Override the version in server.json (e.g. 0.2.0); leave blank to publish as-is"
8
+ required: false
9
+
10
+ # A remote server proves namespace ownership through domain (DNS) verification,
11
+ # not GitHub OIDC: the registry checks a v=MCPv1 TXT record on sigit.si against
12
+ # a request signed with the matching Ed25519 private key. See the MCP Registry
13
+ # doc in the sigit-si repo for the one-time key/DNS setup.
14
+ permissions:
15
+ contents: read
16
+
17
+ jobs:
18
+ publish:
19
+ name: Publish server.json to the MCP Registry
20
+ runs-on: ubuntu-latest
21
+ steps:
22
+ - name: Checkout
23
+ uses: actions/checkout@v6
24
+
25
+ - name: Optionally override the version in server.json
26
+ shell: bash
27
+ run: |
28
+ version="${{ github.event.inputs.version }}"
29
+ if [ -n "${version}" ]; then
30
+ jq --arg v "${version}" '.version = $v' server.json > server.tmp
31
+ mv server.tmp server.json
32
+ fi
33
+ cat server.json
34
+
35
+ - name: Check the remote URL matches the namespace domain
36
+ shell: bash
37
+ run: |
38
+ # The registry rejects a remote server whose URL isn't under the
39
+ # namespace's domain. si.sigit -> sigit.si, so every listed remote
40
+ # must live on sigit.si (or a subdomain).
41
+ server_name="$(jq -r '.name' server.json)"
42
+ domain="$(echo "${server_name%%/*}" | awk -F. '{ for (i=NF; i>=1; i--) printf "%s%s", $i, (i>1 ? "." : "") }')"
43
+ echo "Server: ${server_name} Domain: ${domain}"
44
+
45
+ bad="$(jq -r --arg d "${domain}" '
46
+ .remotes // []
47
+ | map(.url | sub("^[a-z]+://"; "") | sub("/.*$"; ""))
48
+ | map(select(. != $d and (endswith("." + $d) | not)))
49
+ | .[]' server.json)"
50
+ if [ -n "${bad}" ]; then
51
+ echo "Remote URL host(s) not under ${domain}: ${bad}" >&2
52
+ exit 1
53
+ fi
54
+
55
+ - name: Install mcp-publisher
56
+ shell: bash
57
+ run: |
58
+ curl -L "https://github.com/modelcontextprotocol/registry/releases/latest/download/mcp-publisher_$(uname -s | tr '[:upper:]' '[:lower:]')_$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/').tar.gz" | tar xz mcp-publisher
59
+
60
+ - name: Authenticate to the MCP Registry
61
+ shell: bash
62
+ env:
63
+ MCP_REGISTRY_DNS_PRIVATE_KEY: ${{ secrets.MCP_REGISTRY_DNS_PRIVATE_KEY }}
64
+ run: |
65
+ if [ -z "${MCP_REGISTRY_DNS_PRIVATE_KEY}" ]; then
66
+ echo "MCP_REGISTRY_DNS_PRIVATE_KEY secret is not set. See the MCP Registry doc in the sigit-si repo." >&2
67
+ exit 1
68
+ fi
69
+ ./mcp-publisher login dns --domain=sigit.si --private-key="${MCP_REGISTRY_DNS_PRIVATE_KEY}"
70
+
71
+ - name: Publish
72
+ shell: bash
73
+ run: ./mcp-publisher publish
74
+
75
+ - name: Verify the server is listed
76
+ shell: bash
77
+ run: |
78
+ server_name="$(jq -r '.name' server.json)"
79
+ curl -fsSL "https://registry.modelcontextprotocol.io/v0.1/servers?search=${server_name}" | jq .
@@ -110,7 +110,23 @@ feeds results back. Neither the loop nor ACP/TUI surfaces depend on a concrete b
110
110
  spec list (`all_tools`) and the execute `match` (`execute_tool`). `run_command` also enforces
111
111
  commit attribution: when a command creates a new commit that lacks the
112
112
  `Co-Authored-By: siGit Code` trailer (`COMMIT_CO_AUTHOR_TRAILER`), it amends the trailer in —
113
- unless the commit already exists on a remote, which is never rewritten.
113
+ unless the commit already exists on a remote, which is never rewritten. Also owns the `task`
114
+ tool: a nested agent loop in a fresh conversation, offered only when `subagent_available()`
115
+ (a subagent factory is registered — see `register_subagent_factory_for` in `main.rs`; on-device
116
+ registers a `None`-returning factory since onde has a single shared history). A subagent's
117
+ toolset is a hard-gated read-only allow-list (`SUBAGENT_TOOL_NAMES`) that is never expanded by a
118
+ configurable subagent type (see `src/subagents.rs`) — only ever narrowed — so a `.sigit/agents/*.md`
119
+ file can't grant itself `edit_file`/`run_command` and bypass the permission system. Also owns
120
+ `web_search`: a thin native wrapper around the official MCP server's Brave-Search-backed
121
+ `web_search` tool (`mcp__sigit__web_search`, implemented server-side in `sigit-si`'s
122
+ `WebSearchService` + `Mcp::Tools::WebSearch`), offered only when that MCP tool was actually
123
+ discovered (i.e. the user is signed in to siGit Code Cloud — see `web_search_available`).
124
+ Wrapping it natively rather than leaving it as a raw `mcp__*` tool matters for two reasons:
125
+ `permissions::classify` treats every `mcp__*` tool as mutating (an "ask" prompt on every call),
126
+ while `web_search` is read-only like `read_website`; and the raw delegate name is filtered out
127
+ of the assembled tool list (`is_web_search_delegate`) so the model sees one clean option, not
128
+ two names for the same tool. Execution still forwards verbatim to `mcp::call_tool` — no second
129
+ HTTP/JSON-RPC implementation.
114
130
  - **`src/skills.rs`** — [Agent Skills](https://agentskills.io) support. Discovers skill
115
131
  folders (each with a `SKILL.md`: YAML frontmatter `name` + `description`, then Markdown
116
132
  instructions) from `.sigit/skills/` and `.claude/skills/` in the cwd, `$SIGIT_CONFIG_DIR/skills/`,
@@ -119,6 +135,31 @@ feeds results back. Neither the loop nor ACP/TUI surfaces depend on a concrete b
119
135
  calls `skill` with a name) loads the full `SKILL.md` body. The `skill` tool is appended in the
120
136
  `*_as_specs`/`build_tool_specs` layer (not in `all_tools()`) so its description can be dynamic,
121
137
  and only when at least one skill exists.
138
+ - **`src/commands.rs`** — user-defined slash commands. Discovers Markdown files (each an
139
+ optional YAML frontmatter block — `description`, `argument-hint` — followed by a prompt-template
140
+ body) from `.sigit/commands/` and `.claude/commands/` in the cwd, `$SIGIT_CONFIG_DIR/commands/`,
141
+ and `~/.claude/commands/`. A subdirectory namespaces the command with `:`
142
+ (`.sigit/commands/git/commit.md` → `/git:commit`). Unlike skills there's no tool-call
143
+ indirection: invoking one works exactly like the built-in `/init` — `commands::render`
144
+ substitutes `$ARGUMENTS`/`$1..$9` in the body against whatever followed the command on the
145
+ input line, and the result is fed to the model as a normal turn through the ordinary tools and
146
+ permission checks. Resolution happens where each surface (`main.rs`/`chat.rs`) already
147
+ special-cases `/init`: an unrecognized slash command is tried against `commands::resolve_command`
148
+ before falling back to "unknown command". A custom command sharing a name with a built-in is
149
+ unreachable (built-ins match first in `parse_slash`) and is skipped when advertised to ACP
150
+ clients, with a warning logged.
151
+ - **`src/subagents.rs`** — configurable subagent types for the `task` tool. Discovers Markdown
152
+ files (YAML frontmatter `name` + `description`, optional comma-separated `tools:` allow-list,
153
+ then a Markdown body that becomes the subagent's system prompt) from `.sigit/agents/` and
154
+ `.claude/agents/` in the cwd, `$SIGIT_CONFIG_DIR/agents/`, and `~/.claude/agents/`. Passing a
155
+ type's `name` as `task`'s `subagent_type` argument swaps in that system prompt and, if `tools:`
156
+ is set, narrows the offered toolset to its *intersection* with `SUBAGENT_TOOL_NAMES` — the
157
+ security-relevant narrowing logic lives in `tools.rs` next to that constant, not here; this
158
+ module only discovers and parses files. `SubagentFactory` (in `tools.rs`) takes the resolved
159
+ system prompt per call rather than baking one in at registration, so a single registered
160
+ factory serves both the default research subagent and every configured type.
161
+ - **`src/frontmatter.rs`** — shared "YAML frontmatter + Markdown body" parsing used by both
162
+ `src/skills.rs` (`SKILL.md`) and `src/commands.rs` (`.sigit/commands/*.md`).
122
163
  - **`src/mcp.rs`** — [Model Context Protocol](https://modelcontextprotocol.io) *client*. Two
123
164
  transports: **Streamable HTTP** (one JSON-RPC POST endpoint, `url` in `mcp.toml`; replies are
124
165
  `application/json` or SSE) and **stdio** (`command` + optional `args`/`[server.env]` in
@@ -131,12 +172,20 @@ feeds results back. Neither the loop nor ACP/TUI surfaces depend on a concrete b
131
172
  both read it; `/reload` does *not* re-run it, so config changes need a restart. stdio children
132
173
  live for the process; a dead child fails calls with an in-band error string (no auto-restart).
133
174
  Tools are namespaced `mcp__<server>__<tool>`, appended in the `*_as_specs`/`build_tool_specs`
134
- layer and routed in `tools::execute_tool` via `mcp::is_mcp_tool`. The official server
135
- (`<cloud>/mcp`, default `https://sigit.si/api/v1/mcp`) is baked in (always HTTP) and authed
136
- with the cloud session token; extra servers live in `mcp.toml` (global
175
+ layer and routed in `tools::execute_tool` via `mcp::is_mcp_tool`. Two servers are baked in:
176
+ the official server (`<cloud>/mcp`, default `https://sigit.si/api/v1/mcp`, always HTTP, authed
177
+ with the cloud session token) and the smbCloud CLI server (`smb --mcp`, stdio, added only when
178
+ the `smb` binary is on `PATH`; opt out with `smbcloud = false` in `mcp.toml` or
179
+ `SIGIT_MCP_SMBCLOUD=off`). A user-defined entry named `sigit` or `smbcloud` overrides the
180
+ corresponding baked-in one. Extra servers live in `mcp.toml` (global
137
181
  `$SIGIT_CONFIG_DIR/mcp.toml` and project-local `.sigit/mcp.toml`). The stdio path is covered by
138
182
  `tests/mcp_stdio.rs`, driven by the test-only `src/bin/mcp_stdio_stub.rs` helper binary
139
- (excluded from the published crate via `exclude` in `Cargo.toml`).
183
+ (excluded from the published crate via `exclude` in `Cargo.toml`). The baked-in official
184
+ server is *also* listed in the public MCP Registry as `si.sigit/sigit` — a **remote**
185
+ Streamable-HTTP listing (`server.json` at the repo root, published by
186
+ `release-mcp-registry.yml`). Because it's a remote server, the registry's URL-match rule
187
+ forces a domain namespace (`si.sigit` ↔ `sigit.si`) verified by a DNS TXT record, not the
188
+ GitHub-OIDC scheme `smbcloud-cli` uses for its package listing.
140
189
  - **`src/permissions.rs`** — tool permission policy. Every tool call passes through
141
190
  `decision_for` before executing: read-only tools always run; mutating tools (and all
142
191
  `mcp__*`/unknown tools) are governed by, in order: per-session plan mode (`/plan` — deny all
@@ -165,12 +214,14 @@ feeds results back. Neither the loop nor ACP/TUI surfaces depend on a concrete b
165
214
  - **`src/credentials.rs`** — local session-token store (TOML, `0600` on Unix).
166
215
  - **`src/models.rs`** — model-picker types shared across platforms.
167
216
 
168
- Slash commands (`/help`, `/models`, `/skills`, `/mcp`, `/login`, `/logout`, `/whoami`, `/reload`,
169
- `/plan`, `/permissions`, `/init`, `/clear`, `/status`) are advertised via `advertise_commands` in
170
- `main.rs` and handled in both the TUI and ACP sessions. `/init` is special: instead of replying
171
- directly it substitutes `instructions::INIT_PROMPT` for the user text and runs a normal agent
172
- turn that explores the repo and writes (or improves) `AGENTS.md` through the ordinary tools and
173
- permission checks.
217
+ Slash commands (`/help`, `/models`, `/skills`, `/agents`, `/commands`, `/mcp`, `/login`, `/logout`,
218
+ `/whoami`, `/reload`, `/plan`, `/permissions`, `/init`, `/clear`, `/status`) are advertised via
219
+ `advertise_commands` in `main.rs` and handled in both the TUI and ACP sessions. `/init` is
220
+ special: instead of replying directly it substitutes `instructions::INIT_PROMPT` for the user
221
+ text and runs a normal agent turn that explores the repo and writes (or improves) `AGENTS.md`
222
+ through the ordinary tools and permission checks. User-defined commands (see `src/commands.rs`)
223
+ get the same treatment via the `SlashCommand::Unknown` fallback path, so anyone can add their own
224
+ `/name` commands without touching the built-in command list.
174
225
 
175
226
  ## Model cache (macOS)
176
227
 
@@ -190,7 +241,8 @@ verbosity with `RUST_LOG`.
190
241
 
191
242
  `OPENAI_BASE_URL` / `OPENAI_API_KEY` (provider override), `SIGIT_API_URL` (account API base,
192
243
  default `https://sigit.si`), `SIGIT_CLOUD_URL`, `SIGIT_CONFIG_DIR` (default `~/.config/sigit`),
193
- `SIGIT_MODEL`, `SIGIT_MCP` (`off` disables MCP), `SIGIT_MCP_OFFICIAL` (`off` drops the baked-in
244
+ `SIGIT_MODEL`, `SIGIT_MCP` (`off` disables MCP), `SIGIT_MCP_SMBCLOUD` (`off` drops the baked-in
245
+ smbCloud CLI server), `SIGIT_MCP_OFFICIAL` (`off` drops the baked-in
194
246
  server), `SIGIT_PERMISSIONS` (`allow`/`ask`/`deny` — overrides the default permission mode for
195
247
  mutating tools; the escape hatch for clients without permission-request support),
196
248
  `HF_HOME` / `HF_HUB_CACHE`, `RUST_LOG`.
@@ -1,5 +1,77 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.5.0
4
+
5
+ Opens the agent up to user extension: lifecycle hooks, custom slash commands,
6
+ and configurable subagent types. Also adds web search, a Repo tab in the TUI,
7
+ and a baked-in smbCloud MCP server.
8
+
9
+ ### What changed
10
+
11
+ - Hooks let you run shell commands at three points in the agent's lifecycle:
12
+ `session_start`, `pre_tool_use`, and `post_tool_use`, configured under
13
+ `[hooks]` in `settings.toml`. Commands get context through variable
14
+ substitution (`{cwd}`, `{tool_name}`, `{tool_result_len}`) and run in the
15
+ session working directory. A hook that fails is logged and the session
16
+ continues; a hook that hangs is killed on a timeout, along with its whole
17
+ process group
18
+ - User-defined slash commands: drop a Markdown file in `.sigit/commands/` or
19
+ `.claude/commands/` (or the personal `$SIGIT_CONFIG_DIR/commands/` and
20
+ `~/.claude/commands/`) with optional `description` and `argument-hint`
21
+ frontmatter, and the body becomes a prompt template. `$ARGUMENTS` takes the
22
+ whole argument string and `$1`..`$9` the positional words. A subdirectory
23
+ namespaces the command with `:`, so `.sigit/commands/git/commit.md` becomes
24
+ `/git:commit`. They run as ordinary agent turns through the usual tools and
25
+ permission checks, and are advertised to ACP clients like Zed. `/commands`
26
+ lists what was discovered
27
+ - Configurable subagent types for the `task` tool: a Markdown file in
28
+ `.sigit/agents/` or `.claude/agents/` with `name`, `description`, and an
29
+ optional `tools:` allow-list, whose body becomes that subagent's system
30
+ prompt. Pass its name as `subagent_type` to swap the prompt in. The
31
+ `tools:` list can only narrow the subagent's read-only ceiling, never widen
32
+ it, so a config file cannot grant itself `edit_file` or `run_command` and
33
+ route around the permission system. `/agents` lists the discovered types
34
+ - New `web_search` tool, backed by the Brave-Search-backed search on siGit
35
+ Code Cloud's MCP server. It is offered only when you are signed in, and it
36
+ is classified read-only, so searching never triggers a permission prompt
37
+ - The TUI gains a Repo tab, shown when the session's `origin` remote points at
38
+ the sigit.si host. It lists issues and pull requests fetched through the
39
+ official MCP server: Up and Down select, Enter opens a scrollable detail,
40
+ `i`, `p`, Left and Right switch sections, `r` refreshes. The tab is hidden
41
+ and skipped in the cycle for any other remote
42
+ - The smbCloud CLI's stdio MCP server (`smb --mcp`) is now baked in, so
43
+ smbCloud project and deployment tools work with no `mcp.toml` setup. It is
44
+ added only when the `smb` binary is on `PATH`, its read-only tools (`me`,
45
+ `deployments`, `project_list`, `project_show`) skip permission prompts, and
46
+ you can opt out with `smbcloud = false` in `mcp.toml` or
47
+ `SIGIT_MCP_SMBCLOUD=off`
48
+ - siGit Code Cloud's MCP server is now listed in the public MCP Registry as
49
+ `si.sigit/sigit`, published from `server.json` at the repository root
50
+
51
+ ### Fixes
52
+
53
+ - An explicit `deny` rule now applies to read-only first-party tools instead
54
+ of being skipped
55
+ - A subagent type whose `tools:` list resolves to an empty set is rejected
56
+ rather than producing a subagent with no tools
57
+ - Command templates render in a single substitution pass, so an argument that
58
+ contains something like `$1` is no longer re-substituted
59
+ - Frontmatter is stripped with the same leniency it is parsed with
60
+ - The Repo tab's detail view no longer shows stale data after a refresh
61
+
62
+ ## 1.4.1
63
+
64
+ Two small fixes: correct co-author attribution and a Homebrew tap fix.
65
+
66
+ ### What changed
67
+
68
+ - The `Co-Authored-By` trailer siGit Code adds to commits now uses the GitHub
69
+ noreply address for the [sigitc](https://github.com/sigitc) account instead
70
+ of `sigit@sigit.si`, so GitHub reliably attributes co-authored commits to
71
+ the siGit Code profile
72
+ - Fixed the Homebrew tap install docs to trust the tap with `brew trust`
73
+ instead of a `brew tap --force` flag
74
+
3
75
  ## 1.4.0
4
76
 
5
77
  Adds a headless one-shot mode, fine-grained permission rules, stdio transport
@@ -110,7 +110,23 @@ feeds results back. Neither the loop nor ACP/TUI surfaces depend on a concrete b
110
110
  spec list (`all_tools`) and the execute `match` (`execute_tool`). `run_command` also enforces
111
111
  commit attribution: when a command creates a new commit that lacks the
112
112
  `Co-Authored-By: siGit Code` trailer (`COMMIT_CO_AUTHOR_TRAILER`), it amends the trailer in —
113
- unless the commit already exists on a remote, which is never rewritten.
113
+ unless the commit already exists on a remote, which is never rewritten. Also owns the `task`
114
+ tool: a nested agent loop in a fresh conversation, offered only when `subagent_available()`
115
+ (a subagent factory is registered — see `register_subagent_factory_for` in `main.rs`; on-device
116
+ registers a `None`-returning factory since onde has a single shared history). A subagent's
117
+ toolset is a hard-gated read-only allow-list (`SUBAGENT_TOOL_NAMES`) that is never expanded by a
118
+ configurable subagent type (see `src/subagents.rs`) — only ever narrowed — so a `.sigit/agents/*.md`
119
+ file can't grant itself `edit_file`/`run_command` and bypass the permission system. Also owns
120
+ `web_search`: a thin native wrapper around the official MCP server's Brave-Search-backed
121
+ `web_search` tool (`mcp__sigit__web_search`, implemented server-side in `sigit-si`'s
122
+ `WebSearchService` + `Mcp::Tools::WebSearch`), offered only when that MCP tool was actually
123
+ discovered (i.e. the user is signed in to siGit Code Cloud — see `web_search_available`).
124
+ Wrapping it natively rather than leaving it as a raw `mcp__*` tool matters for two reasons:
125
+ `permissions::classify` treats every `mcp__*` tool as mutating (an "ask" prompt on every call),
126
+ while `web_search` is read-only like `read_website`; and the raw delegate name is filtered out
127
+ of the assembled tool list (`is_web_search_delegate`) so the model sees one clean option, not
128
+ two names for the same tool. Execution still forwards verbatim to `mcp::call_tool` — no second
129
+ HTTP/JSON-RPC implementation.
114
130
  - **`src/skills.rs`** — [Agent Skills](https://agentskills.io) support. Discovers skill
115
131
  folders (each with a `SKILL.md`: YAML frontmatter `name` + `description`, then Markdown
116
132
  instructions) from `.sigit/skills/` and `.claude/skills/` in the cwd, `$SIGIT_CONFIG_DIR/skills/`,
@@ -119,6 +135,31 @@ feeds results back. Neither the loop nor ACP/TUI surfaces depend on a concrete b
119
135
  calls `skill` with a name) loads the full `SKILL.md` body. The `skill` tool is appended in the
120
136
  `*_as_specs`/`build_tool_specs` layer (not in `all_tools()`) so its description can be dynamic,
121
137
  and only when at least one skill exists.
138
+ - **`src/commands.rs`** — user-defined slash commands. Discovers Markdown files (each an
139
+ optional YAML frontmatter block — `description`, `argument-hint` — followed by a prompt-template
140
+ body) from `.sigit/commands/` and `.claude/commands/` in the cwd, `$SIGIT_CONFIG_DIR/commands/`,
141
+ and `~/.claude/commands/`. A subdirectory namespaces the command with `:`
142
+ (`.sigit/commands/git/commit.md` → `/git:commit`). Unlike skills there's no tool-call
143
+ indirection: invoking one works exactly like the built-in `/init` — `commands::render`
144
+ substitutes `$ARGUMENTS`/`$1..$9` in the body against whatever followed the command on the
145
+ input line, and the result is fed to the model as a normal turn through the ordinary tools and
146
+ permission checks. Resolution happens where each surface (`main.rs`/`chat.rs`) already
147
+ special-cases `/init`: an unrecognized slash command is tried against `commands::resolve_command`
148
+ before falling back to "unknown command". A custom command sharing a name with a built-in is
149
+ unreachable (built-ins match first in `parse_slash`) and is skipped when advertised to ACP
150
+ clients, with a warning logged.
151
+ - **`src/subagents.rs`** — configurable subagent types for the `task` tool. Discovers Markdown
152
+ files (YAML frontmatter `name` + `description`, optional comma-separated `tools:` allow-list,
153
+ then a Markdown body that becomes the subagent's system prompt) from `.sigit/agents/` and
154
+ `.claude/agents/` in the cwd, `$SIGIT_CONFIG_DIR/agents/`, and `~/.claude/agents/`. Passing a
155
+ type's `name` as `task`'s `subagent_type` argument swaps in that system prompt and, if `tools:`
156
+ is set, narrows the offered toolset to its *intersection* with `SUBAGENT_TOOL_NAMES` — the
157
+ security-relevant narrowing logic lives in `tools.rs` next to that constant, not here; this
158
+ module only discovers and parses files. `SubagentFactory` (in `tools.rs`) takes the resolved
159
+ system prompt per call rather than baking one in at registration, so a single registered
160
+ factory serves both the default research subagent and every configured type.
161
+ - **`src/frontmatter.rs`** — shared "YAML frontmatter + Markdown body" parsing used by both
162
+ `src/skills.rs` (`SKILL.md`) and `src/commands.rs` (`.sigit/commands/*.md`).
122
163
  - **`src/mcp.rs`** — [Model Context Protocol](https://modelcontextprotocol.io) *client*. Two
123
164
  transports: **Streamable HTTP** (one JSON-RPC POST endpoint, `url` in `mcp.toml`; replies are
124
165
  `application/json` or SSE) and **stdio** (`command` + optional `args`/`[server.env]` in
@@ -131,12 +172,20 @@ feeds results back. Neither the loop nor ACP/TUI surfaces depend on a concrete b
131
172
  both read it; `/reload` does *not* re-run it, so config changes need a restart. stdio children
132
173
  live for the process; a dead child fails calls with an in-band error string (no auto-restart).
133
174
  Tools are namespaced `mcp__<server>__<tool>`, appended in the `*_as_specs`/`build_tool_specs`
134
- layer and routed in `tools::execute_tool` via `mcp::is_mcp_tool`. The official server
135
- (`<cloud>/mcp`, default `https://sigit.si/api/v1/mcp`) is baked in (always HTTP) and authed
136
- with the cloud session token; extra servers live in `mcp.toml` (global
175
+ layer and routed in `tools::execute_tool` via `mcp::is_mcp_tool`. Two servers are baked in:
176
+ the official server (`<cloud>/mcp`, default `https://sigit.si/api/v1/mcp`, always HTTP, authed
177
+ with the cloud session token) and the smbCloud CLI server (`smb --mcp`, stdio, added only when
178
+ the `smb` binary is on `PATH`; opt out with `smbcloud = false` in `mcp.toml` or
179
+ `SIGIT_MCP_SMBCLOUD=off`). A user-defined entry named `sigit` or `smbcloud` overrides the
180
+ corresponding baked-in one. Extra servers live in `mcp.toml` (global
137
181
  `$SIGIT_CONFIG_DIR/mcp.toml` and project-local `.sigit/mcp.toml`). The stdio path is covered by
138
182
  `tests/mcp_stdio.rs`, driven by the test-only `src/bin/mcp_stdio_stub.rs` helper binary
139
- (excluded from the published crate via `exclude` in `Cargo.toml`).
183
+ (excluded from the published crate via `exclude` in `Cargo.toml`). The baked-in official
184
+ server is *also* listed in the public MCP Registry as `si.sigit/sigit` — a **remote**
185
+ Streamable-HTTP listing (`server.json` at the repo root, published by
186
+ `release-mcp-registry.yml`). Because it's a remote server, the registry's URL-match rule
187
+ forces a domain namespace (`si.sigit` ↔ `sigit.si`) verified by a DNS TXT record, not the
188
+ GitHub-OIDC scheme `smbcloud-cli` uses for its package listing.
140
189
  - **`src/permissions.rs`** — tool permission policy. Every tool call passes through
141
190
  `decision_for` before executing: read-only tools always run; mutating tools (and all
142
191
  `mcp__*`/unknown tools) are governed by, in order: per-session plan mode (`/plan` — deny all
@@ -165,12 +214,14 @@ feeds results back. Neither the loop nor ACP/TUI surfaces depend on a concrete b
165
214
  - **`src/credentials.rs`** — local session-token store (TOML, `0600` on Unix).
166
215
  - **`src/models.rs`** — model-picker types shared across platforms.
167
216
 
168
- Slash commands (`/help`, `/models`, `/skills`, `/mcp`, `/login`, `/logout`, `/whoami`, `/reload`,
169
- `/plan`, `/permissions`, `/init`, `/clear`, `/status`) are advertised via `advertise_commands` in
170
- `main.rs` and handled in both the TUI and ACP sessions. `/init` is special: instead of replying
171
- directly it substitutes `instructions::INIT_PROMPT` for the user text and runs a normal agent
172
- turn that explores the repo and writes (or improves) `AGENTS.md` through the ordinary tools and
173
- permission checks.
217
+ Slash commands (`/help`, `/models`, `/skills`, `/agents`, `/commands`, `/mcp`, `/login`, `/logout`,
218
+ `/whoami`, `/reload`, `/plan`, `/permissions`, `/init`, `/clear`, `/status`) are advertised via
219
+ `advertise_commands` in `main.rs` and handled in both the TUI and ACP sessions. `/init` is
220
+ special: instead of replying directly it substitutes `instructions::INIT_PROMPT` for the user
221
+ text and runs a normal agent turn that explores the repo and writes (or improves) `AGENTS.md`
222
+ through the ordinary tools and permission checks. User-defined commands (see `src/commands.rs`)
223
+ get the same treatment via the `SlashCommand::Unknown` fallback path, so anyone can add their own
224
+ `/name` commands without touching the built-in command list.
174
225
 
175
226
  ## Model cache (macOS)
176
227
 
@@ -190,7 +241,8 @@ verbosity with `RUST_LOG`.
190
241
 
191
242
  `OPENAI_BASE_URL` / `OPENAI_API_KEY` (provider override), `SIGIT_API_URL` (account API base,
192
243
  default `https://sigit.si`), `SIGIT_CLOUD_URL`, `SIGIT_CONFIG_DIR` (default `~/.config/sigit`),
193
- `SIGIT_MODEL`, `SIGIT_MCP` (`off` disables MCP), `SIGIT_MCP_OFFICIAL` (`off` drops the baked-in
244
+ `SIGIT_MODEL`, `SIGIT_MCP` (`off` disables MCP), `SIGIT_MCP_SMBCLOUD` (`off` drops the baked-in
245
+ smbCloud CLI server), `SIGIT_MCP_OFFICIAL` (`off` drops the baked-in
194
246
  server), `SIGIT_PERMISSIONS` (`allow`/`ask`/`deny` — overrides the default permission mode for
195
247
  mutating tools; the escape hatch for clients without permission-request support),
196
248
  `HF_HOME` / `HF_HUB_CACHE`, `RUST_LOG`.
@@ -5200,7 +5200,7 @@ checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
5200
5200
 
5201
5201
  [[package]]
5202
5202
  name = "sigit"
5203
- version = "1.4.0"
5203
+ version = "1.5.0"
5204
5204
  dependencies = [
5205
5205
  "agent-client-protocol",
5206
5206
  "anyhow",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "sigit"
3
- version = "1.4.0"
3
+ version = "1.5.0"
4
4
  edition = "2024"
5
5
  description = "siGit Code — ACP-compatible AI coding agent. Sí, git."
6
6
  documentation = "https://github.com/getsigit/sigit"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sigit-code
3
- Version: 1.4.0
3
+ Version: 1.5.0
4
4
  Classifier: Development Status :: 4 - Beta
5
5
  Classifier: Environment :: Console
6
6
  Classifier: Intended Audience :: Developers
@@ -81,7 +81,7 @@ Then pick **siGit Code** in the assistant panel.
81
81
  | Method | Command |
82
82
  |--------|---------|
83
83
  | npm | `npm install -g @smbcloud/sigit` |
84
- | Homebrew | `brew tap getsigit/tap && brew install sigit` |
84
+ | Homebrew | `brew tap getsigit/tap && brew trust --tap getsigit/tap && brew install sigit` |
85
85
  | Cargo | `cargo install sigit` |
86
86
 
87
87
  ### From source
@@ -33,7 +33,7 @@ cargo install sigit
33
33
 
34
34
  | Method | Command |
35
35
  |--------|---------|
36
- | Homebrew | `brew tap getsigit/tap && brew install sigit` |
36
+ | Homebrew | `brew tap getsigit/tap && brew trust --tap getsigit/tap && brew install sigit` |
37
37
  | pip | `pip install sigit-code` |
38
38
  | uv | `uvx --from sigit-code sigit` |
39
39
  | npm | `npm install -g @smbcloud/sigit` |
@@ -0,0 +1,92 @@
1
+ # siGit Code Hooks
2
+
3
+ Hooks allow you to run custom scripts at key moments in the agent lifecycle:
4
+ - **SessionStart**: When a session begins (new/load/fork)
5
+ - **PreToolUse**: Before a tool is executed
6
+ - **PostToolUse**: After a tool is executed
7
+
8
+ ## Configuration
9
+
10
+ Hooks are configured in `~/.config/sigit/settings.toml` under the `[hooks]` section:
11
+
12
+ ```toml
13
+ [hooks]
14
+ session_start = ["echo 'Starting siGit in {cwd}'"]
15
+ pre_tool_use = ["echo 'About to run {tool_name} with {tool_args_len} bytes of args'"]
16
+ post_tool_use = ["echo 'Finished {tool_name}, result was {tool_result_len} bytes'"]
17
+ ```
18
+
19
+ Each hook is a list of shell commands. Commands run in the session working directory and inherit the environment.
20
+
21
+ ## Variable Substitution
22
+
23
+ Hooks support substitution for context variables:
24
+
25
+ ### SessionStart
26
+ - `{cwd}` - The session working directory
27
+ - `{session_id}` - The unique session identifier
28
+
29
+ ### PreToolUse
30
+ - `{tool_name}` - The name of the tool being called
31
+ - `{tool_args_len}` - Length of the tool arguments in bytes
32
+ - `{cwd}` - The session working directory
33
+
34
+ ### PostToolUse
35
+ - `{tool_name}` - The name of the tool that ran
36
+ - `{tool_result_len}` - Length of the tool result in bytes
37
+ - `{cwd}` - The session working directory
38
+
39
+ Every `{var}` value is quote-escaped for the platform shell before it's spliced
40
+ into the command line, so a directory name or an MCP-server-supplied tool name
41
+ that happens to contain shell metacharacters is always treated as literal text,
42
+ never executed. Windows `cmd.exe` quoting is best-effort (`cmd` has no fully
43
+ safe quoting story); the same values are also exported as environment
44
+ variables — `SIGIT_HOOK_CWD`, `SIGIT_HOOK_SESSION_ID`, `SIGIT_HOOK_TOOL_NAME`,
45
+ `SIGIT_HOOK_TOOL_ARGS_LEN`, `SIGIT_HOOK_TOOL_RESULT_LEN` — and reading those
46
+ in your script instead of `{var}` sidesteps quoting entirely on any platform.
47
+
48
+ ## Examples
49
+
50
+ ### Log all tool usage to a file
51
+
52
+ ```toml
53
+ [hooks]
54
+ pre_tool_use = ["echo '[{tool_name}]' >> /tmp/sigit-tools.log"]
55
+ post_tool_use = ["echo ' → {tool_result_len} bytes' >> /tmp/sigit-tools.log"]
56
+ ```
57
+
58
+ ### Send notifications
59
+
60
+ ```toml
61
+ [hooks]
62
+ session_start = ["notify-send 'siGit started in {cwd}'"]
63
+ ```
64
+
65
+ ### Build on session start
66
+
67
+ ```toml
68
+ [hooks]
69
+ session_start = ["cd {cwd} && cargo build"]
70
+ ```
71
+
72
+ ### Track metrics
73
+
74
+ ```toml
75
+ [hooks]
76
+ pre_tool_use = ["echo {tool_name} | tee -a ~/.sigit/tool_usage.txt"]
77
+ post_tool_use = ["wc -c <<< {tool_result_len} >> ~/.sigit/result_sizes.txt"]
78
+ ```
79
+
80
+ ## Notes
81
+
82
+ - Hooks are optional. When none are configured, each tool call still does one
83
+ lightweight settings-file read to check for that; there is no shell process
84
+ spawned and no other measurable overhead.
85
+ - Hook failures (non-zero exit codes) are logged as warnings but do not interrupt the session.
86
+ - Hooks are run synchronously, so slow hooks will impact agent responsiveness — this applies
87
+ in the interactive TUI too, not just ACP/headless mode.
88
+ - `PreToolUse` and `PostToolUse` hooks fire in every mode (TUI, ACP, headless) and for every
89
+ call to the shared tool-execution path — including tool calls made by a `task` subagent, not
90
+ just the top-level agent.
91
+ - `SessionStart` hooks currently only fire on ACP session lifecycle events (new/load/fork
92
+ session). The interactive TUI does not have an equivalent session-start hook point yet.