sigit-code 1.5.6__tar.gz → 1.5.8__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 (96) hide show
  1. {sigit_code-1.5.6 → sigit_code-1.5.8}/.agents/AGENTS.md +42 -8
  2. {sigit_code-1.5.6/.claude → sigit_code-1.5.8/.agents}/skills/agent-client-protocol/SKILL.md +19 -4
  3. {sigit_code-1.5.6 → sigit_code-1.5.8}/.agents/skills/sigit-code-release/SKILL.md +3 -0
  4. {sigit_code-1.5.6/.agents → sigit_code-1.5.8/.claude}/skills/agent-client-protocol/SKILL.md +19 -4
  5. {sigit_code-1.5.6 → sigit_code-1.5.8}/.claude/skills/sigit-code-release/SKILL.md +3 -0
  6. {sigit_code-1.5.6 → sigit_code-1.5.8}/.github/workflows/release-aur.yml +8 -0
  7. {sigit_code-1.5.6 → sigit_code-1.5.8}/.github/workflows/release-crates.yml +34 -6
  8. {sigit_code-1.5.6 → sigit_code-1.5.8}/.github/workflows/release-github.yml +8 -0
  9. {sigit_code-1.5.6 → sigit_code-1.5.8}/.github/workflows/release-homebrew.yml +8 -0
  10. {sigit_code-1.5.6 → sigit_code-1.5.8}/.github/workflows/release-npm.yml +75 -32
  11. {sigit_code-1.5.6 → sigit_code-1.5.8}/.github/workflows/release-nuget.yml +37 -6
  12. {sigit_code-1.5.6 → sigit_code-1.5.8}/.github/workflows/release-pypi.yml +29 -6
  13. {sigit_code-1.5.6 → sigit_code-1.5.8}/.github/workflows/release-scoop.yml +8 -0
  14. {sigit_code-1.5.6 → sigit_code-1.5.8}/.github/workflows/release-winget.yml +8 -0
  15. {sigit_code-1.5.6 → sigit_code-1.5.8}/.gitignore +1 -0
  16. {sigit_code-1.5.6 → sigit_code-1.5.8}/AGENTS.md +42 -8
  17. {sigit_code-1.5.6 → sigit_code-1.5.8}/CHANGELOG.md +134 -0
  18. {sigit_code-1.5.6 → sigit_code-1.5.8}/CLAUDE.md +42 -8
  19. {sigit_code-1.5.6 → sigit_code-1.5.8}/Cargo.lock +1 -1
  20. {sigit_code-1.5.6 → sigit_code-1.5.8}/Cargo.toml +1 -1
  21. {sigit_code-1.5.6 → sigit_code-1.5.8}/PKG-INFO +1 -1
  22. {sigit_code-1.5.6 → sigit_code-1.5.8}/npm/README.md.tmpl +1 -1
  23. sigit_code-1.5.8/npm/package-compat.json.tmpl +41 -0
  24. {sigit_code-1.5.6 → sigit_code-1.5.8}/npm/package.json.tmpl +1 -1
  25. {sigit_code-1.5.6 → sigit_code-1.5.8}/npm/scripts/render-main-package.cjs +8 -7
  26. {sigit_code-1.5.6 → sigit_code-1.5.8}/npm/scripts/render-platform-package.cjs +11 -3
  27. {sigit_code-1.5.6 → sigit_code-1.5.8}/npm/sigit/README.md +8 -0
  28. sigit_code-1.5.8/npm/sigit/src/index.ts +74 -0
  29. {sigit_code-1.5.6 → sigit_code-1.5.8}/src/backend.rs +354 -24
  30. {sigit_code-1.5.6 → sigit_code-1.5.8}/src/chat.rs +127 -0
  31. {sigit_code-1.5.6 → sigit_code-1.5.8}/src/commands.rs +5 -4
  32. {sigit_code-1.5.6 → sigit_code-1.5.8}/src/headless.rs +42 -5
  33. {sigit_code-1.5.6 → sigit_code-1.5.8}/src/hooks.rs +3 -0
  34. sigit_code-1.5.8/src/inline_tool_calls.rs +420 -0
  35. {sigit_code-1.5.6 → sigit_code-1.5.8}/src/instructions.rs +51 -5
  36. {sigit_code-1.5.6 → sigit_code-1.5.8}/src/main.rs +911 -132
  37. {sigit_code-1.5.6 → sigit_code-1.5.8}/src/models.rs +50 -0
  38. {sigit_code-1.5.6 → sigit_code-1.5.8}/src/permissions.rs +132 -7
  39. {sigit_code-1.5.6 → sigit_code-1.5.8}/src/settings.rs +12 -25
  40. {sigit_code-1.5.6 → sigit_code-1.5.8}/src/skills.rs +5 -4
  41. {sigit_code-1.5.6 → sigit_code-1.5.8}/src/subagents.rs +5 -4
  42. {sigit_code-1.5.6 → sigit_code-1.5.8}/src/tools.rs +142 -15
  43. sigit_code-1.5.8/src/workspace.rs +160 -0
  44. sigit_code-1.5.8/tests/acp_endpoint_errors.rs +356 -0
  45. sigit_code-1.5.8/tests/acp_multi_root.rs +254 -0
  46. {sigit_code-1.5.6 → sigit_code-1.5.8}/tests/acp_permissions.rs +546 -0
  47. sigit_code-1.5.8/tests/acp_session_load.rs +320 -0
  48. sigit_code-1.5.8/tests/acp_tool_stdin.rs +235 -0
  49. sigit_code-1.5.6/npm/sigit/src/index.ts +0 -43
  50. {sigit_code-1.5.6 → sigit_code-1.5.8}/.agents/skills/ai-assisted-coding/SKILL.md +0 -0
  51. {sigit_code-1.5.6 → sigit_code-1.5.8}/.agents/skills/branding/SKILL.md +0 -0
  52. {sigit_code-1.5.6 → sigit_code-1.5.8}/.agents/skills/run-sigit/SKILL.md +0 -0
  53. {sigit_code-1.5.6 → sigit_code-1.5.8}/.agents/skills/run-sigit/driver.mjs +0 -0
  54. {sigit_code-1.5.6 → sigit_code-1.5.8}/.agents/skills/run-sigit/tui-smoke.sh +0 -0
  55. {sigit_code-1.5.6 → sigit_code-1.5.8}/.agents/skills/tool-calling/SKILL.md +0 -0
  56. {sigit_code-1.5.6 → sigit_code-1.5.8}/.claude/skills/ai-assisted-coding/SKILL.md +0 -0
  57. {sigit_code-1.5.6 → sigit_code-1.5.8}/.claude/skills/branding/SKILL.md +0 -0
  58. {sigit_code-1.5.6 → sigit_code-1.5.8}/.claude/skills/run-sigit/SKILL.md +0 -0
  59. {sigit_code-1.5.6 → sigit_code-1.5.8}/.claude/skills/run-sigit/driver.mjs +0 -0
  60. {sigit_code-1.5.6 → sigit_code-1.5.8}/.claude/skills/run-sigit/tui-smoke.sh +0 -0
  61. {sigit_code-1.5.6 → sigit_code-1.5.8}/.claude/skills/tool-calling/SKILL.md +0 -0
  62. {sigit_code-1.5.6 → sigit_code-1.5.8}/.github/workflows/ci.yml +0 -0
  63. {sigit_code-1.5.6 → sigit_code-1.5.8}/.nvmrc +0 -0
  64. {sigit_code-1.5.6 → sigit_code-1.5.8}/LICENSE +0 -0
  65. {sigit_code-1.5.6 → sigit_code-1.5.8}/README.md +0 -0
  66. {sigit_code-1.5.6 → sigit_code-1.5.8}/docs/hooks.md +0 -0
  67. {sigit_code-1.5.6 → sigit_code-1.5.8}/docs/mcp.md +0 -0
  68. {sigit_code-1.5.6 → sigit_code-1.5.8}/examples/settings-with-hooks.toml +0 -0
  69. {sigit_code-1.5.6 → sigit_code-1.5.8}/examples/skills/README.md +0 -0
  70. {sigit_code-1.5.6 → sigit_code-1.5.8}/examples/skills/commit-message/SKILL.md +0 -0
  71. {sigit_code-1.5.6 → sigit_code-1.5.8}/npm/package-main.json.tmpl +0 -0
  72. {sigit_code-1.5.6 → sigit_code-1.5.8}/npm/sigit/.gitignore +0 -0
  73. {sigit_code-1.5.6 → sigit_code-1.5.8}/npm/sigit/package.json +0 -0
  74. {sigit_code-1.5.6 → sigit_code-1.5.8}/npm/sigit/tsconfig.json +0 -0
  75. {sigit_code-1.5.6 → sigit_code-1.5.8}/nuget/.gitignore +0 -0
  76. {sigit_code-1.5.6 → sigit_code-1.5.8}/nuget/sigit/Program.cs +0 -0
  77. {sigit_code-1.5.6 → sigit_code-1.5.8}/nuget/sigit/README.md +0 -0
  78. {sigit_code-1.5.6 → sigit_code-1.5.8}/nuget/sigit/SiGit.Code.csproj +0 -0
  79. {sigit_code-1.5.6 → sigit_code-1.5.8}/packaging/aur/PKGBUILD.in +0 -0
  80. {sigit_code-1.5.6 → sigit_code-1.5.8}/packaging/nfpm.yaml +0 -0
  81. {sigit_code-1.5.6 → sigit_code-1.5.8}/packaging/winget/getSigit.siGitCode.installer.yaml.in +0 -0
  82. {sigit_code-1.5.6 → sigit_code-1.5.8}/packaging/winget/getSigit.siGitCode.locale.en-US.yaml.in +0 -0
  83. {sigit_code-1.5.6 → sigit_code-1.5.8}/packaging/winget/getSigit.siGitCode.yaml.in +0 -0
  84. {sigit_code-1.5.6 → sigit_code-1.5.8}/pypi/README.md +0 -0
  85. {sigit_code-1.5.6 → sigit_code-1.5.8}/pypi/pyproject.toml +0 -0
  86. {sigit_code-1.5.6 → sigit_code-1.5.8}/pyproject.toml +0 -0
  87. {sigit_code-1.5.6 → sigit_code-1.5.8}/rust-toolchain.toml +0 -0
  88. {sigit_code-1.5.6 → sigit_code-1.5.8}/src/account.rs +0 -0
  89. {sigit_code-1.5.6 → sigit_code-1.5.8}/src/browser_auth.rs +0 -0
  90. {sigit_code-1.5.6 → sigit_code-1.5.8}/src/credentials.rs +0 -0
  91. {sigit_code-1.5.6 → sigit_code-1.5.8}/src/frontmatter.rs +0 -0
  92. {sigit_code-1.5.6 → sigit_code-1.5.8}/src/mcp.rs +0 -0
  93. {sigit_code-1.5.6 → sigit_code-1.5.8}/src/provider.rs +0 -0
  94. {sigit_code-1.5.6 → sigit_code-1.5.8}/src/session_store.rs +0 -0
  95. {sigit_code-1.5.6 → sigit_code-1.5.8}/src/setup.rs +0 -0
  96. {sigit_code-1.5.6 → sigit_code-1.5.8}/tests/headless_mode.rs +0 -0
@@ -116,7 +116,13 @@ feeds results back. Neither the loop nor ACP/TUI surfaces depend on a concrete b
116
116
  smbCloud, and stay general otherwise).
117
117
  - **`src/backend.rs`** — the `InferenceBackend` trait and neutral types (`ToolSpec`, `ToolCall`,
118
118
  `ToolResult`, `TurnResult`). Two impls: `LocalBackend` (on-device via `onde::ChatEngine`) and
119
- `OpenAiBackend` (any OpenAI-compatible HTTP endpoint).
119
+ `OpenAiBackend` (any OpenAI-compatible HTTP endpoint). A `BackendError` is user-facing: the
120
+ ACP prompt handler passes it to the client verbatim and the editor puts it in an error banner,
121
+ so `describe_api_error` unwraps the OpenAI `{"error":{"message":…}}` envelope and shows that
122
+ message on its own, falling back to the status only when there is nothing to unwrap. An
123
+ endpoint can also fail *after* the response is open, reporting it as a `data:` frame holding
124
+ the same envelope; that frame has no `choices`, so `consume_stream` has to check for it
125
+ explicitly or it parses as an empty chunk and the turn ends looking like an empty answer.
120
126
  - **`src/provider.rs`** — decides *which* backend serves inference. Resolution order, first match
121
127
  wins: (1) override via `OPENAI_BASE_URL`+`OPENAI_API_KEY` or active profile in
122
128
  `~/.config/sigit/providers.toml`; (2) siGit Code Cloud when logged in; (3) on-device.
@@ -126,7 +132,13 @@ feeds results back. Neither the loop nor ACP/TUI surfaces depend on a concrete b
126
132
  spec list (`all_tools`) and the execute `match` (`execute_tool`). `run_command` also enforces
127
133
  commit attribution: when a command creates a new commit that lacks the
128
134
  `Co-Authored-By: siGit Code` trailer (`COMMIT_CO_AUTHOR_TRAILER`), it amends the trailer in —
129
- unless the commit already exists on a remote, which is never rewritten. Also owns the `task`
135
+ unless the commit already exists on a remote, which is never rewritten. Every child process it
136
+ spawns (`spawn_shell`, the `git` helpers, and `hooks.rs`) sets `stdin` to null and never
137
+ inherits it: in ACP mode sigit's stdin is the JSON-RPC pipe from the editor, so a command that
138
+ reads stdin both blocks forever and eats the client's next request, wedging the session past
139
+ any timeout. On Unix the shell also leads its own process group so the timeout kills the whole
140
+ tree, and stdout/stderr are drained on threads while the command runs — waiting first and
141
+ reading after deadlocks as soon as the output outgrows the pipe buffer. Also owns the `task`
130
142
  tool: a nested agent loop in a fresh conversation, offered only when `subagent_available()`
131
143
  (a subagent factory is registered — see `register_subagent_factory_for` in `main.rs`; on-device
132
144
  registers a `None`-returning factory since onde has a single shared history). A subagent's
@@ -145,7 +157,8 @@ feeds results back. Neither the loop nor ACP/TUI surfaces depend on a concrete b
145
157
  HTTP/JSON-RPC implementation.
146
158
  - **`src/skills.rs`** — [Agent Skills](https://agentskills.io) support. Discovers skill
147
159
  folders (each with a `SKILL.md`: YAML frontmatter `name` + `description`, then Markdown
148
- instructions) from `.sigit/skills/` and `.claude/skills/` in the cwd, `$SIGIT_CONFIG_DIR/skills/`,
160
+ instructions) from `.sigit/skills/` and `.claude/skills/` in every project root (see `src/workspace.rs`),
161
+ `$SIGIT_CONFIG_DIR/skills/`,
149
162
  and `~/.claude/skills/`. Progressive disclosure: the discovery list (name + description) is
150
163
  baked into the dynamically-built `skill` tool's description, and activating a skill (the model
151
164
  calls `skill` with a name) loads the full `SKILL.md` body. The `skill` tool is appended in the
@@ -153,7 +166,7 @@ feeds results back. Neither the loop nor ACP/TUI surfaces depend on a concrete b
153
166
  and only when at least one skill exists.
154
167
  - **`src/commands.rs`** — user-defined slash commands. Discovers Markdown files (each an
155
168
  optional YAML frontmatter block — `description`, `argument-hint` — followed by a prompt-template
156
- body) from `.sigit/commands/` and `.claude/commands/` in the cwd, `$SIGIT_CONFIG_DIR/commands/`,
169
+ body) from `.sigit/commands/` and `.claude/commands/` in every project root, `$SIGIT_CONFIG_DIR/commands/`,
157
170
  and `~/.claude/commands/`. A subdirectory namespaces the command with `:`
158
171
  (`.sigit/commands/git/commit.md` → `/git:commit`). Unlike skills there's no tool-call
159
172
  indirection: invoking one works exactly like the built-in `/init` — `commands::render`
@@ -167,7 +180,7 @@ feeds results back. Neither the loop nor ACP/TUI surfaces depend on a concrete b
167
180
  - **`src/subagents.rs`** — configurable subagent types for the `task` tool. Discovers Markdown
168
181
  files (YAML frontmatter `name` + `description`, optional comma-separated `tools:` allow-list,
169
182
  then a Markdown body that becomes the subagent's system prompt) from `.sigit/agents/` and
170
- `.claude/agents/` in the cwd, `$SIGIT_CONFIG_DIR/agents/`, and `~/.claude/agents/`. Passing a
183
+ `.claude/agents/` in every project root, `$SIGIT_CONFIG_DIR/agents/`, and `~/.claude/agents/`. Passing a
171
184
  type's `name` as `task`'s `subagent_type` argument swaps in that system prompt and, if `tools:`
172
185
  is set, narrows the offered toolset to its *intersection* with `SUBAGENT_TOOL_NAMES` — the
173
186
  security-relevant narrowing logic lives in `tools.rs` next to that constant, not here; this
@@ -218,9 +231,22 @@ feeds results back. Neither the loop nor ACP/TUI surfaces depend on a concrete b
218
231
  Reads `AGENTS.md` (the cross-tool [agents.md](https://agents.md) standard) and `CLAUDE.md`,
219
232
  walking from the session cwd up to the repo root (nearest ancestor with `.git`, never above it),
220
233
  plus a global file under `$SIGIT_CONFIG_DIR`. Files are ordered outermost-first so the deepest
221
- (most specific) wins. The combined block is injected via `session_context_message` in `main.rs`
222
- — pushed as a system message at every ACP session entry point (new/load/fork + model switch)
223
- and appended to the system prompt on the cloud and TUI-startup paths.
234
+ (most specific) wins. In a multi-root project that walk is repeated for every root
235
+ (`load_workspace_instructions`). The combined block is injected via `session_context_message`
236
+ in `main.rs` — pushed as a system message at every ACP session entry point (new/load/fork +
237
+ model switch) and appended to the system prompt on the cloud and TUI-startup paths.
238
+ - **`src/workspace.rs`** — the directories the session treats as project roots. An editor can
239
+ open several at once (Zed calls it a multi-root project) and ACP carries the extras as
240
+ `additional_directories` on every session request; the headless CLI takes them as repeatable
241
+ `--add-dir` flags. A client only sends those extras to an agent that advertises
242
+ `sessionCapabilities.additionalDirectories` in its `initialize` reply, so that capability in
243
+ `handle_initialize` is what makes the rest of this reachable — without it Zed keeps the first
244
+ root, drops the others, and shows "This agent doesn't currently support multi-root workspaces".
245
+ The process still has one working directory, so the extra roots live in a
246
+ process-global here and `project_dirs()` returns cwd-first, extras after. Project-local
247
+ discovery reads it: skills, slash commands, subagent types, and instruction files all scan
248
+ every root. MCP is deliberately not on that list — `mcp::init` runs once at startup, before
249
+ any session exists, so a second root's `.sigit/mcp.toml` has nobody to tell.
224
250
  - **`src/chat.rs`** — the Unix-only ratatui TUI. Loading-spinner phase then chat; uses
225
251
  `tokio::select!` to multiplex terminal events with streaming tokens.
226
252
  - **`src/setup.rs`** — model cache location, local model discovery, selected-model persistence.
@@ -301,6 +327,14 @@ winget, and the AUR PKGBUILD each need one, and they consume the raw binaries ra
301
327
  tarball. `release-github` also builds a `.deb` and `.rpm` per Linux target with nfpm
302
328
  (`packaging/nfpm.yaml`), packaging the already-built binary rather than re-invoking cargo.
303
329
 
330
+ Re-pushing a tag fires every release workflow a second time, so each one is grouped by tag in a
331
+ `concurrency` block with `cancel-in-progress: false` — the duplicate queues behind the original
332
+ rather than interrupting a publish that is halfway through uploading. What keeps the queued run
333
+ from going red is the "is this version already published?" check each publishing workflow does
334
+ before it uploads. Those checks are load-bearing, not belt-and-braces: v1.5.6 published twice
335
+ because the crates.io one asked the API with curl's default User-Agent, which crates.io answers
336
+ with a 403, which read as "not published yet".
337
+
304
338
  Three of these need credentials or a one-time manual step before they work:
305
339
 
306
340
  - Scoop needs a `getsigit/scoop-bucket` repo and a `SCOOP_BUCKET_TOKEN` secret, mirroring the
@@ -271,8 +271,14 @@ Ok(ForkSessionResponse::new(new_id).config_options(config_options))
271
271
  per-session picker (see Config options below).
272
272
 
273
273
  The session requests carry `cwd: PathBuf` and (with the feature)
274
- `additional_directories: Vec<PathBuf>`. siGit stashes `cwd`, `set_current_dir`s
275
- to it, and pushes a system message telling the model to use absolute paths under it.
274
+ `additional_directories: Vec<PathBuf>` — the other directories of a multi-root
275
+ project, sent only to an agent whose `initialize` reply carries
276
+ `SessionCapabilities::new().additional_directories(SessionAdditionalDirectoriesCapabilities::new())`. `SiGitAgent::enter_session_roots` stashes `cwd`, `set_current_dir`s to
277
+ it, hands the extras to `workspace::set_additional_roots`, and returns the ones
278
+ that survived (a root that isn't a directory, repeats, or just names `cwd` again
279
+ is dropped). `session_context_message` then names every root in the system
280
+ message, and project-local discovery — skills, slash commands, subagent types,
281
+ instruction files — scans all of them, primary root first.
276
282
 
277
283
  ### `PromptRequest` / blocks
278
284
 
@@ -555,8 +561,17 @@ Editor Agent
555
561
  10. **Zed re-fires the last config selection on connect** — make
556
562
  `setConfigOption` a no-op when the requested model is already active, and
557
563
  never start a model switch while a startup load is still in flight (GPU OOM).
558
- 11. **Store `SessionId` as `SessionId`**, not `String`, so `==` is clean.
559
- 12. **`SetSessionConfigOptionResponse::new(config_options)`** — the response
564
+ 11. **`additional_directories` is not decoration** — dropping it means a
565
+ multi-root project silently behaves as if only the first root existed, with
566
+ the other repositories' `AGENTS.md` and skills missing. Route it through
567
+ `workspace.rs`, not just the log line. Handling it is also only half the
568
+ job: Zed gates the field on
569
+ `agentCapabilities.sessionCapabilities.additionalDirectories` being present
570
+ in the `initialize` reply, so an agent that reads the field but never
571
+ advertises the capability is never sent one, and the user gets a banner
572
+ saying the agent has no multi-root support.
573
+ 12. **Store `SessionId` as `SessionId`**, not `String`, so `==` is clean.
574
+ 13. **`SetSessionConfigOptionResponse::new(config_options)`** — the response
560
575
  carries the *rebuilt* options so the picker reflects the new current value.
561
576
 
562
577
  ---
@@ -28,6 +28,8 @@ Use this skill when preparing a release for this repository.
28
28
  - Update the root `sigit` package entry in `Cargo.lock` when the crate version changes.
29
29
  - Add or update the top changelog entry in `CHANGELOG.md` for the release being cut.
30
30
  - Do not treat `npm/sigit/package.json` `0.0.0-dev` as a bug by default. The npm release workflow rewrites it at publish time using `npm/scripts/render-main-package.cjs` and the release tag.
31
+ - Every npm package publishes twice, under `@getsigit` and under `@smbcloud`. `@smbcloud` is the pre-1.5.5 name, mirrored so an install predating the rename keeps updating; `@getsigit` is canonical and is what the README, the Homebrew tap, and the ACP registry entry point at. All fourteen names (two base packages, twelve platform packages) publish over OIDC and each needs its own Trusted Publisher on npm pointing at `release-npm.yml`.
32
+ - The scope is a render-time argument, not a second copy of the packaging: `render-platform-package.cjs` takes it as a fifth argument, and the base package picks it up from `package-main.json.tmpl` versus `package-compat.json.tmpl`. `npm/sigit/src/index.ts` is built once and shipped under both names — it reads its own `package.json` at runtime to decide which scope to resolve the platform binary from, and prints the migration notice only when it finds itself running as `@smbcloud/sigit`.
31
33
  - Do not add a hardcoded version to `pypi/pyproject.toml` for normal releases. PyPI uses `maturin` with `dynamic = ["version"]` and derives the published package version from `Cargo.toml`.
32
34
  - Release workflows are tag-driven. `release-github.yml`, `release-npm.yml`, `release-pypi.yml`, `release-crates.yml`, `release-homebrew.yml`, `release-nuget.yml`, and `release-mcp-registry.yml` all derive `RELEASE_VERSION` from a `v*.*.*` tag or a manually supplied tag input.
33
35
  - The crate is published to crates.io (`release-crates.yml`) and the Homebrew tap is updated (`release-homebrew.yml`) as part of the tag-driven flow. Per the siGit release flow, Homebrew is auto-triggered — do not dispatch it manually.
@@ -58,6 +60,7 @@ Pushing the `v*.*.*` tag is what fires every release workflow, so create and pus
58
60
  - `CHANGELOG.md`
59
61
  - `README.md`
60
62
  - `npm/sigit/package.json`
63
+ - `npm/package-main.json.tmpl`, `npm/package-compat.json.tmpl`, `npm/package.json.tmpl` (publish-time package metadata for both scopes)
61
64
  - `npm/scripts/render-main-package.cjs`
62
65
  - `npm/`
63
66
  - `pypi/`
@@ -271,8 +271,14 @@ Ok(ForkSessionResponse::new(new_id).config_options(config_options))
271
271
  per-session picker (see Config options below).
272
272
 
273
273
  The session requests carry `cwd: PathBuf` and (with the feature)
274
- `additional_directories: Vec<PathBuf>`. siGit stashes `cwd`, `set_current_dir`s
275
- to it, and pushes a system message telling the model to use absolute paths under it.
274
+ `additional_directories: Vec<PathBuf>` — the other directories of a multi-root
275
+ project, sent only to an agent whose `initialize` reply carries
276
+ `SessionCapabilities::new().additional_directories(SessionAdditionalDirectoriesCapabilities::new())`. `SiGitAgent::enter_session_roots` stashes `cwd`, `set_current_dir`s to
277
+ it, hands the extras to `workspace::set_additional_roots`, and returns the ones
278
+ that survived (a root that isn't a directory, repeats, or just names `cwd` again
279
+ is dropped). `session_context_message` then names every root in the system
280
+ message, and project-local discovery — skills, slash commands, subagent types,
281
+ instruction files — scans all of them, primary root first.
276
282
 
277
283
  ### `PromptRequest` / blocks
278
284
 
@@ -555,8 +561,17 @@ Editor Agent
555
561
  10. **Zed re-fires the last config selection on connect** — make
556
562
  `setConfigOption` a no-op when the requested model is already active, and
557
563
  never start a model switch while a startup load is still in flight (GPU OOM).
558
- 11. **Store `SessionId` as `SessionId`**, not `String`, so `==` is clean.
559
- 12. **`SetSessionConfigOptionResponse::new(config_options)`** — the response
564
+ 11. **`additional_directories` is not decoration** — dropping it means a
565
+ multi-root project silently behaves as if only the first root existed, with
566
+ the other repositories' `AGENTS.md` and skills missing. Route it through
567
+ `workspace.rs`, not just the log line. Handling it is also only half the
568
+ job: Zed gates the field on
569
+ `agentCapabilities.sessionCapabilities.additionalDirectories` being present
570
+ in the `initialize` reply, so an agent that reads the field but never
571
+ advertises the capability is never sent one, and the user gets a banner
572
+ saying the agent has no multi-root support.
573
+ 12. **Store `SessionId` as `SessionId`**, not `String`, so `==` is clean.
574
+ 13. **`SetSessionConfigOptionResponse::new(config_options)`** — the response
560
575
  carries the *rebuilt* options so the picker reflects the new current value.
561
576
 
562
577
  ---
@@ -28,6 +28,8 @@ Use this skill when preparing a release for this repository.
28
28
  - Update the root `sigit` package entry in `Cargo.lock` when the crate version changes.
29
29
  - Add or update the top changelog entry in `CHANGELOG.md` for the release being cut.
30
30
  - Do not treat `npm/sigit/package.json` `0.0.0-dev` as a bug by default. The npm release workflow rewrites it at publish time using `npm/scripts/render-main-package.cjs` and the release tag.
31
+ - Every npm package publishes twice, under `@getsigit` and under `@smbcloud`. `@smbcloud` is the pre-1.5.5 name, mirrored so an install predating the rename keeps updating; `@getsigit` is canonical and is what the README, the Homebrew tap, and the ACP registry entry point at. All fourteen names (two base packages, twelve platform packages) publish over OIDC and each needs its own Trusted Publisher on npm pointing at `release-npm.yml`.
32
+ - The scope is a render-time argument, not a second copy of the packaging: `render-platform-package.cjs` takes it as a fifth argument, and the base package picks it up from `package-main.json.tmpl` versus `package-compat.json.tmpl`. `npm/sigit/src/index.ts` is built once and shipped under both names — it reads its own `package.json` at runtime to decide which scope to resolve the platform binary from, and prints the migration notice only when it finds itself running as `@smbcloud/sigit`.
31
33
  - Do not add a hardcoded version to `pypi/pyproject.toml` for normal releases. PyPI uses `maturin` with `dynamic = ["version"]` and derives the published package version from `Cargo.toml`.
32
34
  - Release workflows are tag-driven. `release-github.yml`, `release-npm.yml`, `release-pypi.yml`, `release-crates.yml`, `release-homebrew.yml`, `release-nuget.yml`, and `release-mcp-registry.yml` all derive `RELEASE_VERSION` from a `v*.*.*` tag or a manually supplied tag input.
33
35
  - The crate is published to crates.io (`release-crates.yml`) and the Homebrew tap is updated (`release-homebrew.yml`) as part of the tag-driven flow. Per the siGit release flow, Homebrew is auto-triggered — do not dispatch it manually.
@@ -58,6 +60,7 @@ Pushing the `v*.*.*` tag is what fires every release workflow, so create and pus
58
60
  - `CHANGELOG.md`
59
61
  - `README.md`
60
62
  - `npm/sigit/package.json`
63
+ - `npm/package-main.json.tmpl`, `npm/package-compat.json.tmpl`, `npm/package.json.tmpl` (publish-time package metadata for both scopes)
61
64
  - `npm/scripts/render-main-package.cjs`
62
65
  - `npm/`
63
66
  - `pypi/`
@@ -12,6 +12,14 @@ on:
12
12
  description: "Release tag (e.g. v1.5.2)"
13
13
  required: true
14
14
 
15
+ concurrency:
16
+ # A re-pushed or re-dispatched tag fires this a second time. Queue the
17
+ # duplicate behind the original instead of cancelling it: cancelling a run
18
+ # mid-publish can leave a channel half-uploaded, whereas a queued duplicate
19
+ # just hits the "already published" check and exits clean.
20
+ group: ${{ github.workflow }}-${{ github.event.inputs.tag || github.ref_name }}
21
+ cancel-in-progress: false
22
+
15
23
  permissions:
16
24
  contents: read
17
25
 
@@ -10,6 +10,14 @@ on:
10
10
  description: "Release tag (e.g. v1.0.2)"
11
11
  required: true
12
12
 
13
+ concurrency:
14
+ # A re-pushed or re-dispatched tag fires this a second time. Queue the
15
+ # duplicate behind the original instead of cancelling it: cancelling a run
16
+ # mid-publish can leave a channel half-uploaded, whereas a queued duplicate
17
+ # just hits the "already published" check and exits clean.
18
+ group: ${{ github.workflow }}-${{ github.event.inputs.tag || github.ref_name }}
19
+ cancel-in-progress: false
20
+
13
21
  permissions:
14
22
  contents: read
15
23
 
@@ -56,12 +64,32 @@ jobs:
56
64
  print(next(p['version'] for p in pkgs if p['name']=='sigit'))")
57
65
  echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
58
66
 
59
- if curl -fsS "https://crates.io/api/v1/crates/sigit/${VERSION}" >/dev/null 2>&1; then
60
- echo "sigit ${VERSION} already exists on crates.io, skipping publish"
61
- echo "exists=true" >> "$GITHUB_OUTPUT"
62
- else
63
- echo "exists=false" >> "$GITHUB_OUTPUT"
64
- fi
67
+ # crates.io answers 403 to requests without a descriptive
68
+ # User-Agent, so curl's default one makes this look like a 404 and
69
+ # the publish below runs into "crate already exists on the index".
70
+ # Branch on the status code rather than on curl's exit status, so an
71
+ # answer we do not understand stops the job instead of quietly
72
+ # degrading into that same failure.
73
+ # Retry first, so a timeout or a 5xx costs a few seconds instead of
74
+ # the whole release.
75
+ STATUS=$(curl -sS -o /dev/null -w '%{http_code}' \
76
+ --retry 3 --retry-delay 2 --retry-all-errors \
77
+ -H "User-Agent: ${GITHUB_REPOSITORY} release workflow (https://github.com/${GITHUB_REPOSITORY})" \
78
+ "https://crates.io/api/v1/crates/sigit/${VERSION}")
79
+
80
+ case "$STATUS" in
81
+ 200)
82
+ echo "sigit ${VERSION} already exists on crates.io, skipping publish"
83
+ echo "exists=true" >> "$GITHUB_OUTPUT"
84
+ ;;
85
+ 404)
86
+ echo "exists=false" >> "$GITHUB_OUTPUT"
87
+ ;;
88
+ *)
89
+ echo "Unexpected ${STATUS} from crates.io while checking whether sigit ${VERSION} is published" >&2
90
+ exit 1
91
+ ;;
92
+ esac
65
93
 
66
94
  - name: Publish to crates.io
67
95
  if: steps.crates-check.outputs.exists != 'true'
@@ -10,6 +10,14 @@ on:
10
10
  description: "Release tag (e.g. v0.1.1)"
11
11
  required: true
12
12
 
13
+ concurrency:
14
+ # A re-pushed or re-dispatched tag fires this a second time. Queue the
15
+ # duplicate behind the original instead of cancelling it: cancelling a run
16
+ # mid-publish can leave a channel half-uploaded, whereas a queued duplicate
17
+ # just hits the "already published" check and exits clean.
18
+ group: ${{ github.workflow }}-${{ github.event.inputs.tag || github.ref_name }}
19
+ cancel-in-progress: false
20
+
13
21
  permissions:
14
22
  contents: write
15
23
  actions: write
@@ -7,6 +7,14 @@ on:
7
7
  description: "Release tag (e.g. v0.1.1)"
8
8
  required: true
9
9
 
10
+ concurrency:
11
+ # A re-pushed or re-dispatched tag fires this a second time. Queue the
12
+ # duplicate behind the original instead of cancelling it: cancelling a run
13
+ # mid-publish can leave a channel half-uploaded, whereas a queued duplicate
14
+ # just hits the "already published" check and exits clean.
15
+ group: ${{ github.workflow }}-${{ github.event.inputs.tag || github.ref_name }}
16
+ cancel-in-progress: false
17
+
10
18
  permissions:
11
19
  contents: read
12
20
 
@@ -10,6 +10,14 @@ on:
10
10
  description: "Release tag (e.g. v0.1.1)"
11
11
  required: true
12
12
 
13
+ concurrency:
14
+ # A re-pushed or re-dispatched tag fires this a second time. Queue the
15
+ # duplicate behind the original instead of cancelling it: cancelling a run
16
+ # mid-publish can leave a channel half-uploaded, whereas a queued duplicate
17
+ # just hits the "already published" check and exits clean.
18
+ group: ${{ github.workflow }}-${{ github.event.inputs.tag || github.ref_name }}
19
+ cancel-in-progress: false
20
+
13
21
  permissions:
14
22
  id-token: write
15
23
  contents: read
@@ -108,8 +116,10 @@ jobs:
108
116
  shell: bash
109
117
  run: npm install -g npm@latest
110
118
 
111
- # Authentication is OIDC: each @getsigit/* package has a Trusted
112
- # Publisher pointing at this workflow, so there is no token to leak.
119
+ # Authentication is OIDC: each @getsigit/* and @smbcloud/* package has a
120
+ # Trusted Publisher pointing at this workflow, so there is no token to
121
+ # leak. @smbcloud is the pre-1.5.5 scope, published alongside @getsigit
122
+ # so that an install made before the rename keeps updating.
113
123
  - name: Publish platform package to NPM
114
124
  shell: bash
115
125
  run: |
@@ -141,13 +151,6 @@ jobs:
141
151
  # Create the package directory
142
152
  mkdir -p "${node_pkg}/bin"
143
153
 
144
- # Generate package.json + README for this platform slice
145
- node ./scripts/render-platform-package.cjs \
146
- "${node_pkg}" \
147
- "${node_version}" \
148
- "${node_os}" \
149
- "${node_arch}"
150
-
151
154
  # Windows binaries carry a .exe extension
152
155
  if [ "${{ matrix.build.OS }}" = "windows-2022" ]; then
153
156
  cargo_bin="${cargo_bin}.exe"
@@ -155,18 +158,36 @@ jobs:
155
158
 
156
159
  cp "../target/${{ matrix.build.TARGET }}/release/${cargo_bin}" "${node_pkg}/bin"
157
160
 
158
- cd "${node_pkg}"
159
-
160
- npm_package_name="@getsigit/${node_pkg}"
161
- if npm view "${npm_package_name}@${node_version}" version >/dev/null 2>&1; then
162
- echo "${npm_package_name}@${node_version} already exists on npm, skipping publish"
163
- exit 0
164
- fi
165
-
166
- npm publish --access public
161
+ # One binary, published under both scopes: re-render package.json and
162
+ # README for each, from the same directory.
163
+ for scope in "@getsigit" "@smbcloud"; do
164
+ node ./scripts/render-platform-package.cjs \
165
+ "${node_pkg}" \
166
+ "${node_version}" \
167
+ "${node_os}" \
168
+ "${node_arch}" \
169
+ "${scope}"
170
+
171
+ npm_package_name="${scope}/${node_pkg}"
172
+
173
+ # `npm view` exits non-zero both when the version is genuinely
174
+ # unpublished and when the registry could not be reached, so treat
175
+ # only an E404 as "not published yet". Anything else stops the job
176
+ # instead of publishing over a version that is already there.
177
+ if view_output=$(npm view "${npm_package_name}@${node_version}" version 2>&1); then
178
+ echo "${npm_package_name}@${node_version} already exists on npm, skipping publish"
179
+ continue
180
+ elif ! grep -q "E404" <<<"${view_output}"; then
181
+ echo "npm view failed while checking ${npm_package_name}@${node_version}:" >&2
182
+ echo "${view_output}" >&2
183
+ exit 1
184
+ fi
185
+
186
+ (cd "${node_pkg}" && npm publish --access public)
187
+ done
167
188
 
168
189
  publish-npm-base:
169
- name: Publish base NPM package (@getsigit/sigit)
190
+ name: Publish base NPM packages (@getsigit/sigit, @smbcloud/sigit)
170
191
  needs: publish-npm-binaries
171
192
  runs-on: ubuntu-latest
172
193
 
@@ -200,25 +221,47 @@ jobs:
200
221
  shell: bash
201
222
  run: npm install -g npm@latest
202
223
 
203
- - name: Publish base package to NPM
224
+ - name: Publish base packages to NPM
204
225
  shell: bash
205
226
  run: |
206
227
  cd npm/sigit
207
228
 
208
- # Rewrite package.json with the correct release version and
209
- # matching optionalDependency versions
210
- node ../scripts/render-main-package.cjs \
211
- "./package.json" \
212
- "${{ env.RELEASE_VERSION }}"
213
-
214
- npm_package_name="@getsigit/sigit"
215
229
  npm_package_version="${{ env.RELEASE_VERSION }}"
216
230
 
217
- if npm view "${npm_package_name}@${npm_package_version}" version >/dev/null 2>&1; then
218
- echo "${npm_package_name}@${npm_package_version} already exists on npm, skipping publish"
219
- exit 0
220
- fi
231
+ # Rewrite package.json with the correct release version and matching
232
+ # optionalDependency versions, then build. The compiled launcher is
233
+ # scope-agnostic — it reads its own package.json at runtime to decide
234
+ # which scope to resolve the platform binary from — so both packages
235
+ # ship the same lib/ and it only needs building once.
236
+ node ../scripts/render-main-package.cjs \
237
+ "./package.json" \
238
+ "${npm_package_version}"
221
239
 
222
240
  npm install
223
241
  npm run build
224
- npm publish --access public
242
+
243
+ # @getsigit/sigit is the canonical package; @smbcloud/sigit is the
244
+ # pre-1.5.5 name, kept publishing so installs predating the rename
245
+ # keep updating. Its template points optionalDependencies at the
246
+ # @smbcloud platform packages.
247
+ for template in "package-main.json.tmpl" "package-compat.json.tmpl"; do
248
+ node ../scripts/render-main-package.cjs \
249
+ "./package.json" \
250
+ "${npm_package_version}" \
251
+ "${template}"
252
+
253
+ npm_package_name=$(node -p "require('./package.json').name")
254
+
255
+ # Same E404-only reading as the platform packages above: a registry
256
+ # failure must not look like an unpublished version.
257
+ if view_output=$(npm view "${npm_package_name}@${npm_package_version}" version 2>&1); then
258
+ echo "${npm_package_name}@${npm_package_version} already exists on npm, skipping publish"
259
+ continue
260
+ elif ! grep -q "E404" <<<"${view_output}"; then
261
+ echo "npm view failed while checking ${npm_package_name}@${npm_package_version}:" >&2
262
+ echo "${view_output}" >&2
263
+ exit 1
264
+ fi
265
+
266
+ npm publish --access public
267
+ done
@@ -10,6 +10,14 @@ on:
10
10
  description: "Release tag (e.g. v1.5.2)"
11
11
  required: true
12
12
 
13
+ concurrency:
14
+ # A re-pushed or re-dispatched tag fires this a second time. Queue the
15
+ # duplicate behind the original instead of cancelling it: cancelling a run
16
+ # mid-publish can leave a channel half-uploaded, whereas a queued duplicate
17
+ # just hits the "already published" check and exits clean.
18
+ group: ${{ github.workflow }}-${{ github.event.inputs.tag || github.ref_name }}
19
+ cancel-in-progress: false
20
+
13
21
  permissions:
14
22
  contents: read
15
23
 
@@ -288,12 +296,35 @@ jobs:
288
296
  shell: bash
289
297
  run: |
290
298
  package_index_url="https://api.nuget.org/v3-flatcontainer/sigit.code/index.json"
291
- if curl -fsS "${package_index_url}" | grep -F "\"${RELEASE_VERSION}\"" >/dev/null; then
292
- echo "${NUGET_PACKAGE_ID} ${RELEASE_VERSION} already exists on NuGet, skipping publish"
293
- echo "exists=true" >> "$GITHUB_OUTPUT"
294
- else
295
- echo "exists=false" >> "$GITHUB_OUTPUT"
296
- fi
299
+
300
+ # Keep the fetch and the version match separate. Piping curl into
301
+ # grep hides a failed fetch behind an empty match, so an outage or a
302
+ # blocked request would read as "not published yet" and send the
303
+ # publish below into a version that is already there. A 404 here
304
+ # means the package itself is not on NuGet, which is the first
305
+ # release and a real "not published".
306
+ index=$(mktemp)
307
+ STATUS=$(curl -sS -o "${index}" -w '%{http_code}' \
308
+ --retry 3 --retry-delay 2 --retry-all-errors "${package_index_url}")
309
+
310
+ case "$STATUS" in
311
+ 200)
312
+ if grep -F "\"${RELEASE_VERSION}\"" "${index}" >/dev/null; then
313
+ echo "${NUGET_PACKAGE_ID} ${RELEASE_VERSION} already exists on NuGet, skipping publish"
314
+ echo "exists=true" >> "$GITHUB_OUTPUT"
315
+ else
316
+ echo "exists=false" >> "$GITHUB_OUTPUT"
317
+ fi
318
+ ;;
319
+ 404)
320
+ echo "${NUGET_PACKAGE_ID} is not on NuGet yet, publishing its first version"
321
+ echo "exists=false" >> "$GITHUB_OUTPUT"
322
+ ;;
323
+ *)
324
+ echo "Unexpected ${STATUS} from NuGet while checking whether ${NUGET_PACKAGE_ID} ${RELEASE_VERSION} is published" >&2
325
+ exit 1
326
+ ;;
327
+ esac
297
328
 
298
329
  # Exchange the workflow's OIDC token for a short-lived NuGet API key. The
299
330
  # key is written to the environment for the subsequent `dotnet nuget push`
@@ -10,6 +10,14 @@ on:
10
10
  description: "Release tag (e.g. v0.1.1)"
11
11
  required: true
12
12
 
13
+ concurrency:
14
+ # A re-pushed or re-dispatched tag fires this a second time. Queue the
15
+ # duplicate behind the original instead of cancelling it: cancelling a run
16
+ # mid-publish can leave a channel half-uploaded, whereas a queued duplicate
17
+ # just hits the "already published" check and exits clean.
18
+ group: ${{ github.workflow }}-${{ github.event.inputs.tag || github.ref_name }}
19
+ cancel-in-progress: false
20
+
13
21
  permissions:
14
22
  id-token: write
15
23
  contents: read
@@ -173,12 +181,27 @@ jobs:
173
181
  id: pypi-check
174
182
  shell: bash
175
183
  run: |
176
- if curl -fsS "https://pypi.org/pypi/sigit-code/${RELEASE_VERSION}/json" >/dev/null 2>&1; then
177
- echo "sigit-code ${RELEASE_VERSION} already exists on PyPI, skipping publish"
178
- echo "exists=true" >> "$GITHUB_OUTPUT"
179
- else
180
- echo "exists=false" >> "$GITHUB_OUTPUT"
181
- fi
184
+ # Branch on the status code rather than on curl's exit status: with
185
+ # -f every non-2xx answer looks alike, so a registry outage or a
186
+ # blocked request would read as "not published yet" and send the
187
+ # publish below into a version that is already there.
188
+ STATUS=$(curl -sS -o /dev/null -w '%{http_code}' \
189
+ --retry 3 --retry-delay 2 --retry-all-errors \
190
+ "https://pypi.org/pypi/sigit-code/${RELEASE_VERSION}/json")
191
+
192
+ case "$STATUS" in
193
+ 200)
194
+ echo "sigit-code ${RELEASE_VERSION} already exists on PyPI, skipping publish"
195
+ echo "exists=true" >> "$GITHUB_OUTPUT"
196
+ ;;
197
+ 404)
198
+ echo "exists=false" >> "$GITHUB_OUTPUT"
199
+ ;;
200
+ *)
201
+ echo "Unexpected ${STATUS} from PyPI while checking whether sigit-code ${RELEASE_VERSION} is published" >&2
202
+ exit 1
203
+ ;;
204
+ esac
182
205
 
183
206
  - name: Publish distribution
184
207
  if: steps.pypi-check.outputs.exists != 'true'
@@ -8,6 +8,14 @@ on:
8
8
  description: "Release tag (e.g. v1.5.2)"
9
9
  required: true
10
10
 
11
+ concurrency:
12
+ # A re-pushed or re-dispatched tag fires this a second time. Queue the
13
+ # duplicate behind the original instead of cancelling it: cancelling a run
14
+ # mid-publish can leave a channel half-uploaded, whereas a queued duplicate
15
+ # just hits the "already published" check and exits clean.
16
+ group: ${{ github.workflow }}-${{ github.event.inputs.tag || github.ref_name }}
17
+ cancel-in-progress: false
18
+
11
19
  permissions:
12
20
  contents: read
13
21
 
@@ -20,6 +20,14 @@ on:
20
20
  description: "Release tag (e.g. v1.5.2)"
21
21
  required: true
22
22
 
23
+ concurrency:
24
+ # A re-pushed or re-dispatched tag fires this a second time. Queue the
25
+ # duplicate behind the original instead of cancelling it: cancelling a run
26
+ # mid-publish can leave a channel half-uploaded, whereas a queued duplicate
27
+ # just hits the "already published" check and exits clean.
28
+ group: ${{ github.workflow }}-${{ github.event.inputs.tag || github.ref_name }}
29
+ cancel-in-progress: false
30
+
23
31
  permissions:
24
32
  contents: read
25
33
 
@@ -1,2 +1,3 @@
1
1
  /target
2
2
  /.zed
3
+ /.idea