kimi-plugin-cc 2.4.5 → 2.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +22 -0
- package/README.md +103 -29
- package/dist/commands/setup.js +78 -51
- package/dist/dsh/companion.js +177 -0
- package/dist/dsh/index.js +169 -0
- package/dist/hooks/install-paths.js +2 -2
- package/dist/hooks/managed-block.js +86 -20
- package/dist/version.js +1 -1
- package/docs/migration.md +4 -2
- package/docs/models.md +5 -1
- package/docs/registry-releases.md +84 -20
- package/docs/safety.md +25 -10
- package/docs/upstream-2.4.1-certification.md +6 -0
- package/package.json +75 -8
- package/plugins/kimi-codex/.codex-plugin/plugin.json +1 -1
- package/plugins/kimi-codex/dist/commands/setup.js +78 -51
- package/plugins/kimi-codex/dist/dsh/companion.js +177 -0
- package/plugins/kimi-codex/dist/dsh/index.js +169 -0
- package/plugins/kimi-codex/dist/hooks/install-paths.js +2 -2
- package/plugins/kimi-codex/dist/hooks/managed-block.js +86 -20
- package/plugins/kimi-codex/dist/version.js +1 -1
- package/plugins/kimi-dsh/LICENSE +201 -0
- package/plugins/kimi-dsh/README.md +83 -0
- package/plugins/kimi-dsh/cordis.patch.yml +45 -0
- package/plugins/kimi-dsh/dist/background-spawn.js +216 -0
- package/plugins/kimi-dsh/dist/cli-cancellation.js +43 -0
- package/plugins/kimi-dsh/dist/cli-client.js +876 -0
- package/plugins/kimi-dsh/dist/commands/ask.js +336 -0
- package/plugins/kimi-dsh/dist/commands/cancel.js +216 -0
- package/plugins/kimi-dsh/dist/commands/cli-helpers.js +201 -0
- package/plugins/kimi-dsh/dist/commands/models.js +140 -0
- package/plugins/kimi-dsh/dist/commands/not-implemented.js +4 -0
- package/plugins/kimi-dsh/dist/commands/pursue.js +307 -0
- package/plugins/kimi-dsh/dist/commands/recover-swarm.js +135 -0
- package/plugins/kimi-dsh/dist/commands/registry.js +74 -0
- package/plugins/kimi-dsh/dist/commands/repair-sessions.js +160 -0
- package/plugins/kimi-dsh/dist/commands/replay.js +124 -0
- package/plugins/kimi-dsh/dist/commands/rescue.js +347 -0
- package/plugins/kimi-dsh/dist/commands/result.js +66 -0
- package/plugins/kimi-dsh/dist/commands/review-gate.js +390 -0
- package/plugins/kimi-dsh/dist/commands/review.js +220 -0
- package/plugins/kimi-dsh/dist/commands/setup.js +1074 -0
- package/plugins/kimi-dsh/dist/commands/status.js +27 -0
- package/plugins/kimi-dsh/dist/commands/swarm.js +754 -0
- package/plugins/kimi-dsh/dist/commands/worker.js +25 -0
- package/plugins/kimi-dsh/dist/companion.js +97 -0
- package/plugins/kimi-dsh/dist/config.js +22 -0
- package/plugins/kimi-dsh/dist/dsh/companion.js +177 -0
- package/plugins/kimi-dsh/dist/dsh/index.js +169 -0
- package/plugins/kimi-dsh/dist/errors.js +40 -0
- package/plugins/kimi-dsh/dist/git.js +224 -0
- package/plugins/kimi-dsh/dist/help.js +43 -0
- package/plugins/kimi-dsh/dist/hook-escape.js +71 -0
- package/plugins/kimi-dsh/dist/hooks/approval-hook.js +101 -0
- package/plugins/kimi-dsh/dist/hooks/approval-policy.js +211 -0
- package/plugins/kimi-dsh/dist/hooks/config-safety.js +653 -0
- package/plugins/kimi-dsh/dist/hooks/install-paths.js +428 -0
- package/plugins/kimi-dsh/dist/hooks/install.js +210 -0
- package/plugins/kimi-dsh/dist/hooks/managed-block.js +685 -0
- package/plugins/kimi-dsh/dist/hooks/review-gate-stop.js +19 -0
- package/plugins/kimi-dsh/dist/job-id.js +12 -0
- package/plugins/kimi-dsh/dist/job-store.js +659 -0
- package/plugins/kimi-dsh/dist/jobs.js +217 -0
- package/plugins/kimi-dsh/dist/kimi-command.js +70 -0
- package/plugins/kimi-dsh/dist/kimi-engine.js +632 -0
- package/plugins/kimi-dsh/dist/kimi-errors.js +260 -0
- package/plugins/kimi-dsh/dist/kimi-home.js +9 -0
- package/plugins/kimi-dsh/dist/kimi-timeouts.js +52 -0
- package/plugins/kimi-dsh/dist/kimi-version-probe.js +216 -0
- package/plugins/kimi-dsh/dist/logging.js +22 -0
- package/plugins/kimi-dsh/dist/mcp/binding.js +58 -0
- package/plugins/kimi-dsh/dist/mcp/job-control.js +345 -0
- package/plugins/kimi-dsh/dist/mcp/launcher.js +257 -0
- package/plugins/kimi-dsh/dist/mcp/operations.js +301 -0
- package/plugins/kimi-dsh/dist/mcp/protocol.js +275 -0
- package/plugins/kimi-dsh/dist/mcp/server.js +8 -0
- package/plugins/kimi-dsh/dist/mcp/stdio.js +78 -0
- package/plugins/kimi-dsh/dist/mcp/supervised-child.js +15 -0
- package/plugins/kimi-dsh/dist/mcp/tool-result.js +7 -0
- package/plugins/kimi-dsh/dist/mcp/workspace.js +34 -0
- package/plugins/kimi-dsh/dist/model-command.js +32 -0
- package/plugins/kimi-dsh/dist/native-v2-preflight.js +417 -0
- package/plugins/kimi-dsh/dist/package.json +1 -0
- package/plugins/kimi-dsh/dist/parsing.js +620 -0
- package/plugins/kimi-dsh/dist/paths.js +27 -0
- package/plugins/kimi-dsh/dist/plugin-data.js +98 -0
- package/plugins/kimi-dsh/dist/process-tree.js +479 -0
- package/plugins/kimi-dsh/dist/prompt-size.js +18 -0
- package/plugins/kimi-dsh/dist/render.js +177 -0
- package/plugins/kimi-dsh/dist/rescue-approval.js +865 -0
- package/plugins/kimi-dsh/dist/schemas/review-gate-output.js +43 -0
- package/plugins/kimi-dsh/dist/session-recovery.js +200 -0
- package/plugins/kimi-dsh/dist/session-title.js +293 -0
- package/plugins/kimi-dsh/dist/stream-json.js +346 -0
- package/plugins/kimi-dsh/dist/swarm-activation.js +834 -0
- package/plugins/kimi-dsh/dist/swarm-recovery.js +134 -0
- package/plugins/kimi-dsh/dist/types.js +1 -0
- package/plugins/kimi-dsh/dist/vendor/shell-quote/LICENSE +24 -0
- package/plugins/kimi-dsh/dist/vendor/shell-quote/index.js +1 -0
- package/plugins/kimi-dsh/dist/vendor/shell-quote/parse.js +229 -0
- package/plugins/kimi-dsh/dist/vendor/smol-toml/LICENSE +24 -0
- package/plugins/kimi-dsh/dist/vendor/smol-toml/date.js +156 -0
- package/plugins/kimi-dsh/dist/vendor/smol-toml/error.js +67 -0
- package/plugins/kimi-dsh/dist/vendor/smol-toml/extract.js +293 -0
- package/plugins/kimi-dsh/dist/vendor/smol-toml/parse.js +164 -0
- package/plugins/kimi-dsh/dist/vendor/smol-toml/primitive.js +151 -0
- package/plugins/kimi-dsh/dist/vendor/smol-toml/struct.js +156 -0
- package/plugins/kimi-dsh/dist/vendor/smol-toml/util.js +57 -0
- package/plugins/kimi-dsh/dist/version.js +7 -0
- package/plugins/kimi-dsh/package.json +78 -0
- package/plugins/kimi-dsh/scripts/companion.sh +37 -0
- package/plugins/kimi-dsh/scripts/review-gate-hook.sh +32 -0
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
# Registry releases
|
|
2
2
|
|
|
3
3
|
The npm package `kimi-plugin-cc` ships the Claude plugin at its root and the
|
|
4
|
-
self-contained Codex
|
|
5
|
-
|
|
4
|
+
self-contained Codex and DSH plugins under `plugins/`. DSH users install that
|
|
5
|
+
same root package with `dsh plugin --profile <name> add kimi-plugin-cc`.
|
|
6
|
+
GitHub marketplaces remain available. The package has no install scripts and
|
|
7
|
+
no separate CLI.
|
|
6
8
|
|
|
7
9
|
## Publishing status
|
|
8
10
|
|
|
9
|
-
Plugin **2.
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
Plugin **2.5.2** hardens setup recovery and DSH dependency ownership. Its real Kimi Code **2.4.1** smoke passed **24 tests and 167 assertions with no skips**; the exact-source audit passed **16 tests and 97 assertions**. Normal npm installation keeps DSH host services optional. DSH use requires explicitly patched host/profile dependencies and both installation audits described below. Exact CLI certification remains unchanged.
|
|
12
|
+
|
|
13
|
+
Plugin **2.5.1** adds DeepSeek Harness through the existing root npm package and its established OIDC workflow. [Release v2.5.1](https://github.com/linxule/kimi-plugin-cc/releases/tag/v2.5.1) is anchored at `afe68ee2f0e7744e1fc97eb0f1e4007585d969d0`; its [CI check](https://github.com/linxule/kimi-plugin-cc/actions/runs/41335810767) and [release-event publishing run](https://github.com/linxule/kimi-plugin-cc/actions/runs/41336273042) passed. The registry tarball has SHA-1 `f0e42febcb14082be6a654b9b0830885242fd92a`, contains 328 files, matches the CI artifact, and carries signed provenance (sigstore log index `3635166158`). A fresh registry installation into an isolated DSH profile composed all seven providers and booted the Web host. DSH users install with `dsh plugin --profile <name> add kimi-plugin-cc`; no second npm package or Trusted Publishing binding is required.
|
|
14
|
+
|
|
15
|
+
Plugin **2.4.5** certifies exact CLI **2.4.1** for all eight operations with host-preactivated swarm sessions. [Release v2.4.5](https://github.com/linxule/kimi-plugin-cc/releases/tag/v2.4.5) is anchored at `de8c195c2986443a088b674717133b164d2e4ef2`; its [CI check](https://github.com/linxule/kimi-plugin-cc/actions/runs/41226096281) and [release-event publishing run](https://github.com/linxule/kimi-plugin-cc/actions/runs/41226438326) passed. The registry tarball has SHA-1 `39f97a6de4b26ad51ac77305054682fcd5cd0164`, contains 233 files, and matches the CI artifact byte-for-byte. Fresh registry installs passed setup/check and unsupported-version refusal for both Claude Code and Codex, and both compiled mirrors are byte-identical to the release checkout. See the [certification record](./upstream-2.4.1-certification.md) and structured verification at `.claude/kimi-code-research/release-2.4.5/registry-verification.json`.
|
|
14
16
|
|
|
15
17
|
Plugin **2.4.2** adds durable interrupted-swarm recovery and Windows spawn diagnostics.
|
|
16
18
|
Exact CLI certification remains unchanged through **2.3.2**; this is not a new
|
|
@@ -80,9 +82,11 @@ file at the tag, so fixes to `publish.yml` reach an existing tag only through
|
|
|
80
82
|
## Validation
|
|
81
83
|
|
|
82
84
|
Use the repository's normal version bump and generated-surface procedure in
|
|
83
|
-
AGENTS.md. Keep plugin and npm versions identical
|
|
84
|
-
|
|
85
|
-
|
|
85
|
+
AGENTS.md. Keep plugin and npm versions identical, and update
|
|
86
|
+
`DSH_PUBLISH_TIMESTAMP` with the actual publication time so the generated DSH
|
|
87
|
+
Discover index stays current. Certification for new Kimi Code versions still
|
|
88
|
+
requires the source audit and real-binary controls; adding a registry channel
|
|
89
|
+
does not certify another CLI version.
|
|
86
90
|
|
|
87
91
|
```sh
|
|
88
92
|
bun install --frozen-lockfile
|
|
@@ -90,15 +94,73 @@ bun run build
|
|
|
90
94
|
bun run generate:surfaces
|
|
91
95
|
# Review and stage generated distributions before the drift gate.
|
|
92
96
|
bun run check
|
|
93
|
-
bun run check:
|
|
97
|
+
bun run check:consumer-security
|
|
98
|
+
node scripts/check-package.mjs .tmp/npm
|
|
99
|
+
node scripts/check-dsh-package.mjs .tmp/dsh-npm
|
|
94
100
|
```
|
|
95
101
|
|
|
96
|
-
The package check inspects the actual tarball, rejects sync-conflict copies in
|
|
102
|
+
The root package check inspects the actual tarball, rejects sync-conflict copies in
|
|
97
103
|
any path component, verifies runtime copies and vendored licenses, and
|
|
98
|
-
installs/checks the safety hook in isolated homes for
|
|
104
|
+
installs/checks the safety hook in isolated homes for all three hosts. It also
|
|
99
105
|
exercises unsupported-version refusal using a fake CLI and modern MCP discovery
|
|
100
|
-
plus legacy initialization on
|
|
101
|
-
|
|
106
|
+
plus legacy initialization on the extracted compiled distributions. The DSH
|
|
107
|
+
check installs that same root tarball into an isolated DSH home, verifies all
|
|
108
|
+
seven tools, and boots the Web host. Neither check invokes a model or reads the
|
|
109
|
+
maintainer's Kimi configuration.
|
|
110
|
+
|
|
111
|
+
The consumer-security gate installs the packed package into a fresh npm project
|
|
112
|
+
and audits the resolved production dependency graph. DSH services and its tool
|
|
113
|
+
adapter are optional peers supplied by the DSH host; ordinary npm installation
|
|
114
|
+
for Claude Code or Codex does not install the harness. Optional peers still
|
|
115
|
+
carry compatibility ranges when present. The gate runs in CI and release
|
|
116
|
+
validation and refuses high/critical advisories, failed audits, and incomplete
|
|
117
|
+
reports. The checkout's overrides do not constrain downstream npm resolution.
|
|
118
|
+
|
|
119
|
+
### Patched DSH host and profiles
|
|
120
|
+
|
|
121
|
+
DSH 0.2.1-rc.5 and 0.2.2-alpha.2 still pin vulnerable `zod` and `undici`
|
|
122
|
+
versions. See the [upstream dependency report](https://github.com/deepseek-ai/deepseek-harness/discussions/10847).
|
|
123
|
+
Use a patched DSH host installation with `zod >=4.6.6` and `undici >=8.12.2`.
|
|
124
|
+
This is an explicit installation requirement; the plugin does not rewrite the
|
|
125
|
+
host or profiles automatically. A profile override alone does not patch copies
|
|
126
|
+
inside a separately installed DSH CLI.
|
|
127
|
+
|
|
128
|
+
For example, create a dedicated npm host project (Node >=24.9), then use its
|
|
129
|
+
DSH binary for plugin installation and launch:
|
|
130
|
+
|
|
131
|
+
```sh
|
|
132
|
+
mkdir -p ~/.local/share/kimi-dsh-host
|
|
133
|
+
cd ~/.local/share/kimi-dsh-host
|
|
134
|
+
npm init -y
|
|
135
|
+
npm pkg set 'overrides.zod=^4.6.6' 'overrides.undici=^8.12.2'
|
|
136
|
+
npm install @deepseek-ai/dsh@0.2.1-rc.5
|
|
137
|
+
npm audit --omit=dev --audit-level=high
|
|
138
|
+
./node_modules/.bin/dsh plugin --profile web add kimi-plugin-cc
|
|
139
|
+
./node_modules/.bin/dsh plugin --profile web audit --prod --audit-level high
|
|
140
|
+
./node_modules/.bin/dsh web --no-open
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
The host project is separate from `$DSH_HOME/profiles/web`. Keep using that
|
|
144
|
+
project's DSH binary so the audited host is the one that runs. The commands
|
|
145
|
+
above install the published plugin; use a validated local tarball when testing
|
|
146
|
+
an unpublished change.
|
|
147
|
+
|
|
148
|
+
If an existing profile separately installs DSH packages, its owner must also
|
|
149
|
+
patch and audit that profile:
|
|
150
|
+
|
|
151
|
+
```sh
|
|
152
|
+
dsh plugin --profile web pkg set 'pnpm.overrides.zod=^4.6.6' 'pnpm.overrides.undici=^8.12.2'
|
|
153
|
+
dsh plugin --profile web install
|
|
154
|
+
dsh plugin --profile web audit --prod --audit-level high
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Replace `web` with the intended profile, and run these commands with the patched
|
|
158
|
+
host binary. Overrides affect dependency resolution and require application
|
|
159
|
+
checks. The DSH package gate creates a fresh host with the declared overrides,
|
|
160
|
+
audits both that host and the installed profile for high/critical advisories,
|
|
161
|
+
then verifies all seven tool compositions and Web boot using that same host.
|
|
162
|
+
These checks do not claim that every moderate/low advisory is fixed or that
|
|
163
|
+
upstream has published a patched release.
|
|
102
164
|
|
|
103
165
|
## First publication (completed bootstrap reference)
|
|
104
166
|
|
|
@@ -135,8 +197,10 @@ operations; Bun remains the development toolchain.
|
|
|
135
197
|
|
|
136
198
|
Push the reviewed release commit, wait for CI, tag it, and publish its GitHub
|
|
137
199
|
release. `.github/workflows/publish.yml` runs on `release.published`; it checks
|
|
138
|
-
the tag against `package.json`, runs the full gate and package
|
|
139
|
-
publishes the
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
200
|
+
the tag against `package.json`, runs the full gate and both package smokes, then
|
|
201
|
+
publishes the root tarball through OIDC with provenance. The DSH smoke installs
|
|
202
|
+
that same tarball directly into an isolated DSH profile, so no second npm
|
|
203
|
+
package or Trusted Publishing binding is needed. The publish job has no source
|
|
204
|
+
checkout or build scripts. A manual dispatch with an existing release tag is
|
|
205
|
+
available for recovery after a verified publication failure. Check npm before
|
|
206
|
+
retrying; released versions are immutable.
|
package/docs/safety.md
CHANGED
|
@@ -72,26 +72,29 @@ Key constraints (verified by [`runtime/hooks/managed-block.ts`](../runtime/hooks
|
|
|
72
72
|
1. **No `matcher` line.** kimi-code compiles the matcher field via `new RegExp(matcher)`. An empty matcher means "fire for every tool." `new RegExp("*")` throws, which kimi-code catches and treats as "no matcher" → silently disables the hook. The installer omits the field intentionally; the verifier rejects any block that contains a `matcher = ...` line.
|
|
73
73
|
2. **Absolute Node path.** kimi-code spawns hook commands via `/bin/sh -c "<command>"`. If we wrote bare `node`, the shell would resolve it against kimi-code's PATH at execution time. On a system where kimi-code launches from a GUI/LaunchAgent with a sanitized PATH (nvm/asdf/mise users), bare `node` would exit 127 → hook protocol treats non-{0,2} as **allow** → safety contract collapses with no user-visible signal.
|
|
74
74
|
3. **TOML basic-string escaping.** The command field is a TOML basic string. Quotes, backslashes, newlines, and control characters in the resolved hook path are escaped during install; paths containing characters that cannot be safely escaped (literal `"`, raw control chars) are rejected up front with `SETUP_HOOK_PATH_UNSAFE`.
|
|
75
|
-
4. **Exact marker grammar.** The verifier rejects
|
|
75
|
+
4. **Exact marker grammar.** The verifier rejects duplicate blocks and orphan or mismatched host markers. Install removes lexically proven unpaired marker comments across hosts, preserving surrounding content and all foreign hook tables, and warns. Text inside TOML strings is not a marker. The verifier remains fail-closed until repair and full validation succeed.
|
|
76
76
|
5. **Exact command equality, per host.** The verifier reconstructs the canonical command byte string from the current env and equality-checks the installed `command` (never a substring — a crafted `true # /path/to/approval-hook.js` runs only `true` and would auto-allow). This holds *per host id* (below): each host verifies its own block.
|
|
77
77
|
6. **Full-config parse and whole-hook-array validation.** The verifier parses the complete file with vendored `smol-toml@1.9.1` with upstream fixes included and no local JavaScript modifications (see `runtime/vendor/smol-toml/README.md`), before inspecting the managed block. It then applies upstream's strict config schema to every configured hook (`event`, optional string `matcher`, non-empty `command`, optional integer `timeout` 1–600, no extra fields). A malformed unrelated TOML value or one invalid foreign hook can make kimi-code discard enforcement; both therefore fail verification.
|
|
78
78
|
7. **Serialized, identity-bound setup mutations.** Install and uninstall hold a private `0o600` sibling lock across the complete read-modify-write transaction. The candidate lock is published atomically; lock reads are no-follow, nonblocking, regular-file-only, and capped at 4 KiB. Acquisition is bounded, live owners are never stolen, stale recovery is guarded by a nonrecursive recovery lease, and inode/token ownership is rechecked before config mutation or lock removal. Simultaneous Claude Code/Codex setup runs therefore cannot lose either host's block, and a replaced lock is never treated as ours. A newly created Kimi home is `0o700`; atomic config writes remain `0o600`.
|
|
79
79
|
8. **Inline hook arrays are normalized before insertion.** kimi-code accepts both `hooks = [...]` and `[[hooks]]`, but appending a managed array-of-tables block after an inline assignment can produce a conflicting document. Setup converts a valid inline hook array to canonical `[[hooks]]` tables before inserting the host block and emits an explicit warning that formatting and comments inside that assignment are normalized. Surrounding config bytes are preserved.
|
|
80
80
|
|
|
81
|
-
### Host scoping (
|
|
81
|
+
### Host scoping (all hosts share one config)
|
|
82
82
|
|
|
83
83
|
Claude Code and Codex install this plugin to **different, version-stamped
|
|
84
|
-
paths** (`~/.claude/plugins/cache/…` vs `~/.codex/plugins/cache/…`)
|
|
85
|
-
|
|
84
|
+
paths** (`~/.claude/plugins/cache/…` vs `~/.codex/plugins/cache/…`), while DSH
|
|
85
|
+
uses its own profile package path. All read the **same**
|
|
86
|
+
`~/.kimi-code/config.toml`. So the managed block is **host-scoped**:
|
|
86
87
|
its marker carries a `:<host-id>` suffix, and each host owns, verifies, and
|
|
87
88
|
uninstalls only its **own** block.
|
|
88
89
|
|
|
89
90
|
- **Host id** (`runtime/hooks/install-paths.ts::resolveHostId`) is derived
|
|
90
91
|
version-independently from the hook path: `~/.claude/…` → `claude-code`,
|
|
91
|
-
`~/.codex/…` → `codex`, else a stable `host-<sha1>` for dev checkouts.
|
|
92
|
-
plugin upgrade *refreshes* the same host's block
|
|
93
|
-
|
|
94
|
-
|
|
92
|
+
`~/.codex/…` → `codex`, else a stable `host-<sha1>` for dev checkouts. DSH
|
|
93
|
+
explicitly pins `dsh`. A plugin upgrade *refreshes* the same host's block
|
|
94
|
+
rather than adding a new one.
|
|
95
|
+
- **No clobbering.** `/kimi:setup` (Claude), `$kimi-setup` (Codex), and
|
|
96
|
+
`/kimi-setup` (DSH) each write their own block and leave the others
|
|
97
|
+
byte-identical. Before v1.7.0 there was
|
|
95
98
|
a single block exact-matched to the running host, so the two hosts overwrote
|
|
96
99
|
each other and the loser's write-commands refused with "hook path drift."
|
|
97
100
|
- **Enforcement is redundant-safe.** kimi-code fires *every* `[[hooks]]` entry
|
|
@@ -103,8 +106,8 @@ uninstalls only its **own** block.
|
|
|
103
106
|
- **Migration + cleanup.** A pre-v1.7.0 un-suffixed block is adopted and
|
|
104
107
|
converted in place by the current host on its next `/kimi:setup`. Orphaned,
|
|
105
108
|
marker-less `[[hooks]]` entries that are unambiguously our approval hook
|
|
106
|
-
(canonical command, `approval-hook.js` under a kimi-marketplace/kimi-plugin-cc
|
|
107
|
-
tree) are pruned **host-scoped** (v1.8.2): install and scoped uninstall touch
|
|
109
|
+
(canonical command, `approval-hook.js` under a kimi-marketplace/kimi-plugin-cc/
|
|
110
|
+
dsh-kimi-plugin-cc tree) are pruned **host-scoped** (v1.8.2): install and scoped uninstall touch
|
|
108
111
|
only tables whose command path derives to the *current* host. `/kimi:setup
|
|
109
112
|
--uninstall` removes only the current host's block (plus legacy + this host's
|
|
110
113
|
orphans); `/kimi:setup --uninstall --all` removes every host's block and every
|
|
@@ -123,6 +126,18 @@ uninstalls only its **own** block.
|
|
|
123
126
|
hook and is left byte-untouched (pre-v1.8.2, the host-blind prune here caused
|
|
124
127
|
each host's setup to disarm the other after any kimi login — the "seesaw").
|
|
125
128
|
|
|
129
|
+
## DeepSeek Harness providers
|
|
130
|
+
|
|
131
|
+
DSH's host and any profile-local harness packages must use patched `zod >=4.6.6` and `undici >=8.12.2`. The plugin treats DSH services as optional host-provided peers; it does not install the harness for other hosts or rewrite an existing DSH installation. Audit the actual host installation as well as the profile. See [patched DSH setup](./registry-releases.md#patched-dsh-host-and-profiles).
|
|
132
|
+
|
|
133
|
+
The DSH bundle exposes the same seven operations as the other hosts: `kimi_ask`, `kimi_review`, `kimi_challenge`, `kimi_rescue`, `kimi_pursue`, `kimi_swarm`, and `kimi_swarm_write`. Each activation starts the existing companion through DSH's managed subprocess service and uses the same `kimi -p` transport, hook verification, exact-version certification, job store, and response or safety budgets. ACP is not used.
|
|
134
|
+
|
|
135
|
+
Run `/kimi-setup` explicitly before delegating. Installing or enabling the bundle does not modify Kimi configuration. DSH uses host id `dsh` and `$DSH_HOME/plugin-data/kimi-plugin-cc` unless an absolute data-root override selects another store. Its provider-owned environment clears other-host roots/data, hook/script overrides, trusted roots, and verification bypasses instead of inheriting them.
|
|
136
|
+
|
|
137
|
+
The parent receives only complete successful stdout. stderr remains on the host, and nonzero, empty, or truncated output becomes a bounded diagnostic rather than partial prose. Cancellation terminates the managed process range and waits for quiescence. DSH activation ids are distinct from companion job ids; use `/kimi-status`, `/kimi-result`, or `/kimi-cancel` for the persisted job.
|
|
138
|
+
|
|
139
|
+
Write boundaries are identical to the companion commands: rescue and pursue edit the real selected workspace, ask/review/challenge and read-only swarm deny writes and shell commands, and swarm-write works from born HEAD in a temporary worktree and returns a patch without applying or committing it. `/kimi-setup --uninstall` removes only the DSH managed block; the all-host uninstall flag is not exposed.
|
|
140
|
+
|
|
126
141
|
## The hook's per-command policy
|
|
127
142
|
|
|
128
143
|
[`runtime/hooks/approval-policy.ts`](../runtime/hooks/approval-policy.ts) is the single source of truth:
|
|
@@ -43,6 +43,12 @@ Controls used a task-authorized dedicated Agent Key in process memory/environmen
|
|
|
43
43
|
- Codex's targeted re-review requested genuine error results, legacy-compatible turn starts, and a whole-workspace settlement assertion. A subsequent review added marker-prefix, call-derived turn-ID, and directory-entry requirements. The final production matrix (`production-prefix-smoke.log`) passed **24 tests and 167 assertions with no skips in 440.66 seconds**. All seven call-derived turn IDs had same-call Write denials with `isError: true` and the managed-message prefix, and the entire workspace was empty at settlement.
|
|
44
44
|
- Literal standard `bun run check` (`check-standard-final.log`): **1,318 pass, 30 expected opt-in skips, 0 fail, 17,319 assertions across 79 files**, 358.83 seconds. Earlier parallel attempts hit timing-sensitive lifecycle failures; the focused suites and a complete serial run passed before the literal standard gate also passed. Codex's final targeted re-review returned **APPROVE**, closing all original and subsequent findings. Final edited-byte package validation (`package-standard-final.log`) passed **233 files** and includes this record.
|
|
45
45
|
|
|
46
|
+
## Release and publication
|
|
47
|
+
|
|
48
|
+
Plugin 2.4.5 was committed as `de8c195c2986443a088b674717133b164d2e4ef2` and pushed to `main`. [Release-commit CI 41226096281](https://github.com/linxule/kimi-plugin-cc/actions/runs/41226096281) passed the full check/package and Windows spawn jobs. Annotated tag `v2.4.5` and [GitHub release](https://github.com/linxule/kimi-plugin-cc/releases/tag/v2.4.5) were created. [Publish workflow 41226438326](https://github.com/linxule/kimi-plugin-cc/actions/runs/41226438326) passed its build and npm publish jobs.
|
|
49
|
+
|
|
50
|
+
The registry tarball has SHA-1 `39f97a6de4b26ad51ac77305054682fcd5cd0164`, contains 233 files, and matches the CI artifact byte-for-byte. Fresh npm registry installations passed isolated setup/check and unsupported-version refusal for both hosts, and the compiled mirrors are byte-identical to the release checkout. Structured proof is retained at `.claude/kimi-code-research/release-2.4.5/registry-verification.json`. Installed hosts and operator CLI pins remain unchanged.
|
|
51
|
+
|
|
46
52
|
## Preserved boundaries
|
|
47
53
|
|
|
48
54
|
Certification remains exact and per-operation. Legacy-v1 ends at 0.41.x. Plan mode and unsafe experimental selectors remain refused. Hook verification, trusted-root confinement, concurrency/defaults, one print runner, memory-only web tokens, finite budgets, process/session identity checks, deactivation, and recovery ownership are unchanged. Existing Git trust and hook-process failure residuals remain documented; this release does not make the hook an OS sandbox. Publication does not install or re-pin either host.
|
package/package.json
CHANGED
|
@@ -1,30 +1,94 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kimi-plugin-cc",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "Kimi for Claude Code and
|
|
3
|
+
"version": "2.5.2",
|
|
4
|
+
"description": "Kimi for Claude Code, Codex, and DeepSeek Harness: code questions, independent reviews, bounded fixes, autonomous goals, parallel tasks, and MCP tools through the local Kimi Code CLI.",
|
|
5
5
|
"type": "module",
|
|
6
|
+
"main": "dist/dsh/index.js",
|
|
6
7
|
"engines": {
|
|
7
|
-
"node": ">=22.5.0"
|
|
8
|
+
"node": ">=22.5.0",
|
|
9
|
+
"dsh": ">=0.2.1-rc.5 <0.3.0"
|
|
10
|
+
},
|
|
11
|
+
"dsh": {
|
|
12
|
+
"bundle": {
|
|
13
|
+
"patch": "./plugins/kimi-dsh/cordis.patch.yml"
|
|
14
|
+
}
|
|
8
15
|
},
|
|
9
16
|
"scripts": {
|
|
10
|
-
"check": "bun run build && bun run check:contracts && bun run check:surfaces && bunx tsc --noEmit && bun test && git diff --exit-code -- dist plugins/kimi-codex && UNTRACKED=\"$(git ls-files --others --exclude-standard -- dist plugins/kimi-codex)\" && { [ -z \"$UNTRACKED\" ] || { echo \"drift gate: untracked dist/codex files (stage them and re-run check):\"; echo \"$UNTRACKED\"; exit 1; }; }",
|
|
17
|
+
"check": "bun run build && bun run check:contracts && bun run check:surfaces && bunx tsc --noEmit && bun test && git diff --exit-code -- dist plugins/kimi-codex plugins/kimi-dsh && UNTRACKED=\"$(git ls-files --others --exclude-standard -- dist plugins/kimi-codex plugins/kimi-dsh)\" && { [ -z \"$UNTRACKED\" ] || { echo \"drift gate: untracked dist/codex/dsh files (stage them and re-run check):\"; echo \"$UNTRACKED\"; exit 1; }; }",
|
|
11
18
|
"check:surfaces": "bun run scripts/generate-surfaces.ts --check",
|
|
12
19
|
"generate:surfaces": "bun run scripts/generate-surfaces.ts",
|
|
13
20
|
"build": "rm -rf dist && bunx tsc -p tsconfig.build.json && mkdir -p dist/vendor/shell-quote dist/vendor/smol-toml && cp runtime/vendor/shell-quote/LICENSE dist/vendor/shell-quote/LICENSE && cp runtime/vendor/smol-toml/*.js runtime/vendor/smol-toml/LICENSE dist/vendor/smol-toml/ && echo '{\"type\":\"module\"}' > dist/package.json",
|
|
14
21
|
"companion": "node --import tsx runtime/companion.ts",
|
|
15
22
|
"test": "bun test",
|
|
16
23
|
"smoke:real": "KIMI_PLUGIN_CC_SMOKE=1 bun test tests/runtime/real-binary-smoke.test.ts --path-ignore-patterns '**/worktrees/**'",
|
|
17
|
-
"check:package": "node scripts/check-package.mjs",
|
|
24
|
+
"check:package": "node scripts/check-package.mjs && node scripts/check-dsh-package.mjs",
|
|
25
|
+
"check:consumer-security": "node scripts/check-consumer-security.mjs",
|
|
18
26
|
"check:contracts": "node scripts/check-contract-size.mjs"
|
|
19
27
|
},
|
|
20
28
|
"devDependencies": {
|
|
21
|
-
"@
|
|
22
|
-
"@
|
|
29
|
+
"@deepseek-ai/cordis": "4.0.5",
|
|
30
|
+
"@deepseek-ai/dsh-commands": "0.2.1-rc.5",
|
|
31
|
+
"@deepseek-ai/dsh-llm": "0.2.1-rc.5",
|
|
32
|
+
"@deepseek-ai/dsh-session": "0.2.1-rc.5",
|
|
33
|
+
"@deepseek-ai/dsh-subagent": "0.2.1-rc.5",
|
|
34
|
+
"@deepseek-ai/dsh-subprocess": "0.2.1-rc.5",
|
|
35
|
+
"@deepseek-ai/dsh-system-prompt": "0.2.1-rc.5",
|
|
36
|
+
"@deepseek-ai/dsh-tool-subagent": "0.2.1-rc.5",
|
|
37
|
+
"@deepseek-ai/dsh-working-directory": "0.2.1-rc.5",
|
|
38
|
+
"@types/bun": "^1.4.4",
|
|
39
|
+
"@types/node": "^26.6.6",
|
|
23
40
|
"smol-toml": "1.9.1",
|
|
24
41
|
"tsx": "^4.23.13",
|
|
25
42
|
"typescript": "^7.0.2"
|
|
26
43
|
},
|
|
27
|
-
"dependencies": {
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"@deepseek-ai/dsh-brand": "0.2.1-rc.5",
|
|
46
|
+
"@deepseek-ai/schemastery": "3.18.5"
|
|
47
|
+
},
|
|
48
|
+
"peerDependencies": {
|
|
49
|
+
"@deepseek-ai/cordis": "~4.0.5",
|
|
50
|
+
"@deepseek-ai/dsh-commands": ">=0.2.1-rc.5 <0.3.0",
|
|
51
|
+
"@deepseek-ai/dsh-llm": ">=0.2.1-rc.5 <0.3.0",
|
|
52
|
+
"@deepseek-ai/dsh-session": ">=0.2.1-rc.5 <0.3.0",
|
|
53
|
+
"@deepseek-ai/dsh-subagent": ">=0.2.1-rc.5 <0.3.0",
|
|
54
|
+
"@deepseek-ai/dsh-subprocess": ">=0.2.1-rc.5 <0.3.0",
|
|
55
|
+
"@deepseek-ai/dsh-system-prompt": ">=0.2.1-rc.5 <0.3.0",
|
|
56
|
+
"@deepseek-ai/dsh-working-directory": ">=0.2.1-rc.5 <0.3.0",
|
|
57
|
+
"@deepseek-ai/dsh-tool-subagent": ">=0.2.1-rc.5 <0.3.0"
|
|
58
|
+
},
|
|
59
|
+
"peerDependenciesMeta": {
|
|
60
|
+
"@deepseek-ai/cordis": {
|
|
61
|
+
"optional": true
|
|
62
|
+
},
|
|
63
|
+
"@deepseek-ai/dsh-commands": {
|
|
64
|
+
"optional": true
|
|
65
|
+
},
|
|
66
|
+
"@deepseek-ai/dsh-llm": {
|
|
67
|
+
"optional": true
|
|
68
|
+
},
|
|
69
|
+
"@deepseek-ai/dsh-session": {
|
|
70
|
+
"optional": true
|
|
71
|
+
},
|
|
72
|
+
"@deepseek-ai/dsh-subagent": {
|
|
73
|
+
"optional": true
|
|
74
|
+
},
|
|
75
|
+
"@deepseek-ai/dsh-subprocess": {
|
|
76
|
+
"optional": true
|
|
77
|
+
},
|
|
78
|
+
"@deepseek-ai/dsh-system-prompt": {
|
|
79
|
+
"optional": true
|
|
80
|
+
},
|
|
81
|
+
"@deepseek-ai/dsh-working-directory": {
|
|
82
|
+
"optional": true
|
|
83
|
+
},
|
|
84
|
+
"@deepseek-ai/dsh-tool-subagent": {
|
|
85
|
+
"optional": true
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
"overrides": {
|
|
89
|
+
"zod": "^4.6.6",
|
|
90
|
+
"undici": "^8.12.2"
|
|
91
|
+
},
|
|
28
92
|
"license": "Apache-2.0",
|
|
29
93
|
"author": "Xule Lin",
|
|
30
94
|
"repository": {
|
|
@@ -38,6 +102,8 @@
|
|
|
38
102
|
"keywords": [
|
|
39
103
|
"claude-code",
|
|
40
104
|
"codex",
|
|
105
|
+
"deepseek-harness",
|
|
106
|
+
"dsh",
|
|
41
107
|
"kimi",
|
|
42
108
|
"kimi-code",
|
|
43
109
|
"plugin",
|
|
@@ -58,6 +124,7 @@
|
|
|
58
124
|
"hooks/",
|
|
59
125
|
"dist/",
|
|
60
126
|
"plugins/kimi-codex/",
|
|
127
|
+
"plugins/kimi-dsh/",
|
|
61
128
|
"scripts/companion.sh",
|
|
62
129
|
"scripts/review-gate-hook.sh",
|
|
63
130
|
"docs/safety.md",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kimi",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.5.2",
|
|
4
4
|
"description": "Kimi for Codex: code questions, independent reviews, bounded fixes, autonomous goals, parallel tasks, and MCP job controls through the local Kimi Code CLI.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "linxule"
|
|
@@ -9,7 +9,8 @@ import { SETUP_SUPPORTED_FLAGS } from "../parsing.js";
|
|
|
9
9
|
// absolute Node path avoids PATH dependence under sanitized launches. Setup
|
|
10
10
|
// probes the hook directly with resolveNodeBinary and through `/bin/sh -c`
|
|
11
11
|
// using the exact installed command bytes. It also warns about broad permission
|
|
12
|
-
// denies
|
|
12
|
+
// denies, repairs orphan marker comments, and refuses remaining
|
|
13
|
+
// duplicate, orphaned, or malformed managed blocks.
|
|
13
14
|
//
|
|
14
15
|
// Subcommands: install (default), --check, --uninstall [--all], and
|
|
15
16
|
// --enable-review-gate/--disable-review-gate.
|
|
@@ -25,12 +26,13 @@ import { readPluginConfig, writePluginConfig } from "../config.js";
|
|
|
25
26
|
import { RuntimeError } from "../errors.js";
|
|
26
27
|
import { normalizeTopLevelInlineHooks, validateKimiHookSetForEnvironment, withKimiConfigLock, } from "../hooks/config-safety.js";
|
|
27
28
|
import { buildHookShellCommand, hostIdFromHookCommand, resolveHookScriptPath, resolveHostId, resolveNodeBinary, } from "../hooks/install-paths.js";
|
|
28
|
-
import { decodeManagedCommandLine, effectiveHost, evaluateInstalled, findUnmanagedApprovalHookBlocks, MARKERS, parseManagedBlock, } from "../hooks/managed-block.js";
|
|
29
|
+
import { decodeManagedCommandLine, effectiveHost, evaluateInstalled, findUnmanagedApprovalHookBlocks, markerCommentLines, MARKERS, parseManagedBlock, } from "../hooks/managed-block.js";
|
|
29
30
|
import { formatVersionOutOfRangeWarning, probeKimiVersion, } from "../kimi-version-probe.js";
|
|
30
31
|
import { isNativeV2CertifiedVersion } from "../kimi-engine.js";
|
|
31
32
|
import { resolveKimiHome } from "../kimi-home.js";
|
|
32
33
|
import { ensurePluginPaths, resolvePluginPaths } from "../paths.js";
|
|
33
34
|
import { KIMI_PLUGIN_CC_VERSION } from "../version.js";
|
|
35
|
+
import { parse as parseToml } from "../vendor/smol-toml/parse.js";
|
|
34
36
|
const BEGIN_MARKER_PREFIX = "# === BEGIN kimi-plugin-cc-managed";
|
|
35
37
|
const DEFAULT_HOOK_TIMEOUT_S = 15;
|
|
36
38
|
// Probe timing. The direct script probe is local and fast; allow 5s for
|
|
@@ -119,7 +121,17 @@ async function runInstall(configPath, hookScriptPath, hostId, reviewGateEnabled,
|
|
|
119
121
|
const mutation = await withKimiConfigLock(configPath, async () => {
|
|
120
122
|
const original = await readConfigSafe(configPath);
|
|
121
123
|
const lineEnding = detectLineEnding(original);
|
|
122
|
-
|
|
124
|
+
try {
|
|
125
|
+
parseToml(original);
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
await assertKimiHookSetValid(original, configPath, "setup.install", context.env);
|
|
129
|
+
}
|
|
130
|
+
const repaired = removeOrphanMarkers(original);
|
|
131
|
+
if (repaired.count > 0) {
|
|
132
|
+
warnings.push(`Removed ${repaired.count} orphaned managed marker comment(s); surrounding config was preserved.`);
|
|
133
|
+
}
|
|
134
|
+
const inlineHooks = normalizeTopLevelInlineHooks(repaired.contents, lineEnding);
|
|
123
135
|
if (inlineHooks.found) {
|
|
124
136
|
// Validate before normalizing so an invalid inline entry is reported
|
|
125
137
|
// against the user's original bytes and the config remains untouched.
|
|
@@ -133,7 +145,7 @@ async function runInstall(configPath, hookScriptPath, hostId, reviewGateEnabled,
|
|
|
133
145
|
// splice line numbers. Another host's marker-less table may be its live hook
|
|
134
146
|
// after a config rewrite strips comments, so it must remain untouched.
|
|
135
147
|
const expectedCommand = buildHookShellCommand(hookScriptPath, context.env);
|
|
136
|
-
const { pruned, count: prunedCount, commands: prunedCommands } = pruneOrphanApprovalHooks(inlineHooks.contents,
|
|
148
|
+
const { pruned, count: prunedCount, commands: prunedCommands } = pruneOrphanApprovalHooks(inlineHooks.contents, hostId, expectedCommand);
|
|
137
149
|
const existing = pruned;
|
|
138
150
|
if (prunedCount > 0) {
|
|
139
151
|
const readorned = prunedCommands.includes(expectedCommand);
|
|
@@ -145,7 +157,7 @@ async function runInstall(configPath, hookScriptPath, hostId, reviewGateEnabled,
|
|
|
145
157
|
if (state.kind === "orphan") {
|
|
146
158
|
throw new RuntimeError("SETUP_ORPHAN_MARKERS", [
|
|
147
159
|
`kimi-code config at ${configPath} contains an orphaned ${state.detail} marker.`,
|
|
148
|
-
"
|
|
160
|
+
"Inspect the managed marker comments and re-run setup.",
|
|
149
161
|
].join(" "), "setup.install", { details: { configPath, orphan: state.detail } });
|
|
150
162
|
}
|
|
151
163
|
if (state.kind === "duplicate") {
|
|
@@ -393,12 +405,11 @@ async function runUninstallLocked(configPath, hookScriptPath, hostId, removeAllH
|
|
|
393
405
|
// other hosts' blocks and live marker-less tables remain intact. `--all` is
|
|
394
406
|
// the explicit every-host sweep for all of this plugin's hook tables.
|
|
395
407
|
const { stripped: strippedMarkers, removedBlocks, orphansLeft } = stripManagedBlocks(existing, hostId, removeAllHosts);
|
|
396
|
-
const lineEnding = detectLineEnding(existing);
|
|
397
408
|
// Exact current command bytes also identify this host's table when a host-id
|
|
398
409
|
// override disagrees with path derivation or a dev path is outside the strict
|
|
399
410
|
// ownership pattern.
|
|
400
411
|
const expectedCommand = buildHookShellCommand(hookScriptPath, context.env);
|
|
401
|
-
const { pruned: stripped, count: prunedCount } = pruneOrphanApprovalHooks(strippedMarkers,
|
|
412
|
+
const { pruned: stripped, count: prunedCount } = pruneOrphanApprovalHooks(strippedMarkers, removeAllHosts ? undefined : hostId, expectedCommand);
|
|
402
413
|
const changed = stripped !== existing;
|
|
403
414
|
if (changed) {
|
|
404
415
|
await writeConfigAtomic(configPath, stripped);
|
|
@@ -517,10 +528,10 @@ function detectLineEnding(contents) {
|
|
|
517
528
|
return contents.includes("\r\n") ? "\r\n" : "\n";
|
|
518
529
|
}
|
|
519
530
|
function spliceBlock(contents, beginLine, endLine, replacement, lineEnding) {
|
|
520
|
-
const lines =
|
|
521
|
-
const before = lines.slice(0, beginLine).join(
|
|
522
|
-
const after = lines.slice(endLine + 1).join(
|
|
523
|
-
const beforeWithSep = before.endsWith(
|
|
531
|
+
const lines = contents.split(/(?<=\n)/);
|
|
532
|
+
const before = lines.slice(0, beginLine).join("");
|
|
533
|
+
const after = lines.slice(endLine + 1).join("");
|
|
534
|
+
const beforeWithSep = before.endsWith("\n") || before.length === 0 ? before : `${before}${lineEnding}`;
|
|
524
535
|
const replacementWithEnding = replacement.endsWith(lineEnding) ? replacement : `${replacement}${lineEnding}`;
|
|
525
536
|
return `${beforeWithSep}${replacementWithEnding}${after}`;
|
|
526
537
|
}
|
|
@@ -536,14 +547,54 @@ function splitPreservingEnding(contents) {
|
|
|
536
547
|
// pass the desired line ending).
|
|
537
548
|
return contents.split("\n").map((line) => line.replace(/\r$/, ""));
|
|
538
549
|
}
|
|
550
|
+
// A partial upstream hooks-array rewrite can leave one marker comment behind.
|
|
551
|
+
// Remove only unpaired marker comments, never the intervening content. The
|
|
552
|
+
// normal full-file validation still gates writes.
|
|
553
|
+
function removeOrphanMarkers(contents) {
|
|
554
|
+
const lines = splitPreservingEnding(contents);
|
|
555
|
+
const orphans = [];
|
|
556
|
+
const comments = markerCommentLines(contents);
|
|
557
|
+
let open = null;
|
|
558
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
559
|
+
if (!comments.has(i))
|
|
560
|
+
continue;
|
|
561
|
+
const line = lines[i].trim();
|
|
562
|
+
const begin = MARKERS.BEGIN_LINE_RE.exec(line);
|
|
563
|
+
if (begin !== null) {
|
|
564
|
+
if (open !== null)
|
|
565
|
+
orphans.push(open);
|
|
566
|
+
open = { index: i, host: begin[1]?.toLowerCase() ?? null };
|
|
567
|
+
}
|
|
568
|
+
else {
|
|
569
|
+
const end = MARKERS.END_LINE_RE.exec(line);
|
|
570
|
+
if (end === null)
|
|
571
|
+
continue;
|
|
572
|
+
const endMarker = { index: i, host: end[1]?.toLowerCase() ?? null };
|
|
573
|
+
if (open === null)
|
|
574
|
+
orphans.push(endMarker);
|
|
575
|
+
else {
|
|
576
|
+
if (open.host !== endMarker.host)
|
|
577
|
+
orphans.push(open, endMarker);
|
|
578
|
+
open = null;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
if (open !== null)
|
|
583
|
+
orphans.push(open);
|
|
584
|
+
const remove = new Set(orphans.map((marker) => marker.index));
|
|
585
|
+
return remove.size === 0
|
|
586
|
+
? { contents, count: 0 }
|
|
587
|
+
: { contents: contents.split(/(?<=\n)/).filter((_, index) => !remove.has(index)).join(""), count: remove.size };
|
|
588
|
+
}
|
|
539
589
|
/**
|
|
540
590
|
* Remove managed blocks owned by `currentHost`, or every host with `removeAll`.
|
|
541
591
|
* Ownership comes from the marker suffix or a legacy command's derived host.
|
|
542
592
|
* Orphan markers are removed individually so surrounding user content survives.
|
|
543
593
|
*/
|
|
544
594
|
function stripManagedBlocks(contents, currentHost, removeAll) {
|
|
545
|
-
const lineEnding = detectLineEnding(contents);
|
|
546
595
|
const lines = splitPreservingEnding(contents);
|
|
596
|
+
const rawLines = contents.split(/(?<=\n)/);
|
|
597
|
+
const markerLines = markerCommentLines(contents);
|
|
547
598
|
const result = [];
|
|
548
599
|
const orphansLeft = [];
|
|
549
600
|
let removedBlocks = 0;
|
|
@@ -567,7 +618,7 @@ function stripManagedBlocks(contents, currentHost, removeAll) {
|
|
|
567
618
|
while (i < lines.length) {
|
|
568
619
|
const line = lines[i];
|
|
569
620
|
const trimmed = line.trim();
|
|
570
|
-
const begin = MARKERS.BEGIN_LINE_RE.exec(trimmed);
|
|
621
|
+
const begin = markerLines.has(i) ? MARKERS.BEGIN_LINE_RE.exec(trimmed) : null;
|
|
571
622
|
if (begin !== null) {
|
|
572
623
|
const beginHost = begin[1]?.toLowerCase() ?? null;
|
|
573
624
|
// Look ahead for the matching END, capturing the block's command en route.
|
|
@@ -575,12 +626,14 @@ function stripManagedBlocks(contents, currentHost, removeAll) {
|
|
|
575
626
|
let blockCommand = null;
|
|
576
627
|
for (let j = i + 1; j < lines.length; j += 1) {
|
|
577
628
|
const nextTrimmed = lines[j].trim();
|
|
578
|
-
if (MARKERS.BEGIN_LINE_RE.test(nextTrimmed)) {
|
|
629
|
+
if (markerLines.has(j) && MARKERS.BEGIN_LINE_RE.test(nextTrimmed)) {
|
|
579
630
|
// Two BEGINs in a row — the outer one is an orphan.
|
|
580
631
|
break;
|
|
581
632
|
}
|
|
582
|
-
|
|
583
|
-
|
|
633
|
+
const end = markerLines.has(j) ? MARKERS.END_LINE_RE.exec(nextTrimmed) : null;
|
|
634
|
+
if (end !== null) {
|
|
635
|
+
if ((end[1]?.toLowerCase() ?? null) === beginHost)
|
|
636
|
+
endIdx = j;
|
|
584
637
|
break;
|
|
585
638
|
}
|
|
586
639
|
const decoded = decodeManagedCommandLine(nextTrimmed);
|
|
@@ -594,7 +647,7 @@ function stripManagedBlocks(contents, currentHost, removeAll) {
|
|
|
594
647
|
i += 1;
|
|
595
648
|
continue;
|
|
596
649
|
}
|
|
597
|
-
result.push(
|
|
650
|
+
result.push(rawLines[i]);
|
|
598
651
|
i += 1;
|
|
599
652
|
continue;
|
|
600
653
|
}
|
|
@@ -606,11 +659,11 @@ function stripManagedBlocks(contents, currentHost, removeAll) {
|
|
|
606
659
|
}
|
|
607
660
|
// Another host's block — keep it verbatim.
|
|
608
661
|
for (let k = i; k <= endIdx; k += 1)
|
|
609
|
-
result.push(
|
|
662
|
+
result.push(rawLines[k]);
|
|
610
663
|
i = endIdx + 1;
|
|
611
664
|
continue;
|
|
612
665
|
}
|
|
613
|
-
const end = MARKERS.END_LINE_RE.exec(trimmed);
|
|
666
|
+
const end = markerLines.has(i) ? MARKERS.END_LINE_RE.exec(trimmed) : null;
|
|
614
667
|
if (end !== null) {
|
|
615
668
|
const endHost = end[1]?.toLowerCase() ?? null;
|
|
616
669
|
// Orphan END with no preceding BEGIN: drop only this line (if ours).
|
|
@@ -619,27 +672,14 @@ function stripManagedBlocks(contents, currentHost, removeAll) {
|
|
|
619
672
|
i += 1;
|
|
620
673
|
continue;
|
|
621
674
|
}
|
|
622
|
-
result.push(
|
|
675
|
+
result.push(rawLines[i]);
|
|
623
676
|
i += 1;
|
|
624
677
|
continue;
|
|
625
678
|
}
|
|
626
|
-
result.push(
|
|
679
|
+
result.push(rawLines[i]);
|
|
627
680
|
i += 1;
|
|
628
681
|
}
|
|
629
|
-
|
|
630
|
-
let blankRun = 0;
|
|
631
|
-
for (const line of result) {
|
|
632
|
-
if (line.length === 0) {
|
|
633
|
-
blankRun += 1;
|
|
634
|
-
if (blankRun <= 2)
|
|
635
|
-
collapsed.push(line);
|
|
636
|
-
}
|
|
637
|
-
else {
|
|
638
|
-
blankRun = 0;
|
|
639
|
-
collapsed.push(line);
|
|
640
|
-
}
|
|
641
|
-
}
|
|
642
|
-
return { stripped: collapsed.join(lineEnding), removedBlocks, orphansLeft };
|
|
682
|
+
return { stripped: result.join(""), removedBlocks, orphansLeft };
|
|
643
683
|
}
|
|
644
684
|
/**
|
|
645
685
|
* Remove orphaned, marker-less `[[hooks]]` tables that are unambiguously this
|
|
@@ -654,7 +694,7 @@ function stripManagedBlocks(contents, currentHost, removeAll) {
|
|
|
654
694
|
* host — REQUIRED for scoped install/uninstall so one host never disarms the
|
|
655
695
|
* other's live (marker-stripped) hook. Omit only for `--uninstall --all`.
|
|
656
696
|
*/
|
|
657
|
-
function pruneOrphanApprovalHooks(contents,
|
|
697
|
+
function pruneOrphanApprovalHooks(contents, ownedBy, alsoMatchCommand) {
|
|
658
698
|
const ranges = findUnmanagedApprovalHookBlocks(contents, ownedBy, alsoMatchCommand);
|
|
659
699
|
if (ranges.length === 0)
|
|
660
700
|
return { pruned: contents, count: 0, commands: [] };
|
|
@@ -663,23 +703,10 @@ function pruneOrphanApprovalHooks(contents, lineEnding, ownedBy, alsoMatchComman
|
|
|
663
703
|
for (let i = start; i < end; i += 1)
|
|
664
704
|
remove.add(i);
|
|
665
705
|
}
|
|
666
|
-
const lines =
|
|
706
|
+
const lines = contents.split(/(?<=\n)/);
|
|
667
707
|
const kept = lines.filter((_, idx) => !remove.has(idx));
|
|
668
|
-
const collapsed = [];
|
|
669
|
-
let blankRun = 0;
|
|
670
|
-
for (const line of kept) {
|
|
671
|
-
if (line.length === 0) {
|
|
672
|
-
blankRun += 1;
|
|
673
|
-
if (blankRun <= 2)
|
|
674
|
-
collapsed.push(line);
|
|
675
|
-
}
|
|
676
|
-
else {
|
|
677
|
-
blankRun = 0;
|
|
678
|
-
collapsed.push(line);
|
|
679
|
-
}
|
|
680
|
-
}
|
|
681
708
|
return {
|
|
682
|
-
pruned:
|
|
709
|
+
pruned: kept.join(""),
|
|
683
710
|
count: ranges.length,
|
|
684
711
|
commands: ranges.map((range) => range.command),
|
|
685
712
|
};
|