Commit Graph
24 Commits
Author SHA1 Message Date
esengine 55c307834e fix(encoding): a file is GB18030 only if a write can restore its bytes
edit_file on a UTF-8 file carrying CJK text rewrote bytes it never touched:
continuation bytes of characters far from the edit came back as 84 31 A4 37.

Detect took a nil error from the GB18030 decoder as proof the file was
GB18030. The decoder never fails: it maps every invalid sequence to U+FFFD.
So any file that was not valid UTF-8 -- one stray byte is enough -- was read
as GB18030, and the read-modify-write tools (edit_file, multi_edit,
delete_range, write_file over an existing file), checkpoint restore and the
config migrations then re-encoded every unreadable sequence as the GB18030
form of U+FFFD.

Detect now names a charset only when decoding and re-encoding restores the
input byte for byte. GB18030 is tried first, then GBK, which restores what
GB18030 cannot such as CP936's single-byte euro 0x80, so such a file stays
editable in its own encoding; anything else is LossyUTF8, whose bytes pass
through untouched. The new Kind is appended because checkpoints persist it
by value.

GBK cannot hold every character, so Encode now returns an error instead of
falling back to UTF-8, which rewrote a whole CP936 file as UTF-8 when an edit
added a character such as an emoji. A legacy charset that cannot represent a
character answers an *UnencodableError naming it, its byte offset and the
charset, with ErrUnencodable as its identity; the file tools, checkpoint
restore and the config migrations refuse the write and leave the file as it
was. Fixtures encode through MustEncode.

Readers that hold only part of a stream say so rather than inherit the
whole-file answer:
- DetectFragment runs the round trip on the prefix the decoder consumes when
  not at EOF, so a character the cut split is dropped structurally. grep's
  8 KiB peek, read_file's 256 KiB sample and the desktop's 2 MiB file preview
  use it; the preview's next page starts at the split character, and lossy
  text is shown as text rather than as binary.
- DecodeOutput is the one entry point for bytes that are only displayed.
  The producer says which ends its bound cut (Cut{Head, Tail}); UTF-8 is read
  without a character split at a cut end, and an uncut end keeps its bytes,
  since a GBK pair there is output rather than half a rune. What no charset
  restores is still read as GB18030. Hook output passes its tail cut when
  truncated.
- DetectAndDecode decodes a legacy charset once; the edit tools and
  DecodeToUTF8 use it instead of detecting and decoding separately.

Tests that fail before this change (on the base or an earlier revision of it)
and pass here:
- TestE2EEditKeepsBytesOfUTF8FileWithStrayByte: e4 b8 ad became
  e4 b8 84 31 a4 37 outside the edit (fails on origin/main-v2).
- TestDetectUTF8WithStrayByteIsNotGB18030 (fails on origin/main-v2).
- TestWritesRefuseCharacterCP936CannotHold (edit_file, multi_edit,
  write_file): nil error and the whole CP936 file rewritten as UTF-8.
- TestDecodeOutputTrimsCutUTF8Edges: UTF-8 cut at the front read as mojibake.
- TestDecodeOutputKeepsUncutCodePageEdges, TestDecodeHookOutputKeepsUncutCodePageEdges:
  GBK pairs at an uncut edge (bc db, b0 a1 0d 0a, 6f 6b 20 e4 a1,
  b0 a1 20 6f 6b) were dropped as partial UTF-8 runes.
- TestEditKeepsCP936FileWithEuroByte: an edit adding Chinese wrote UTF-8
  into a CP936 file.
- TestGrepGB18030PeekWithoutNewline, TestGrepGB18030PeekCutMidCharacter:
  no matches past a peek cut mid-character.
- TestReadFileGB18030SampleWithoutNewline: garbled text past the sample.
- TestReadFileGB18030TruncatedWithoutNewline,
  TestReadFileGB18030TruncatedMidCharacter (desktop): the 2 MiB preview of a
  GB18030 file was garbled or ended in U+FFFD.
- TestDecodeHookOutputReadsTruncatedCodePageText,
  TestDecodeHookOutputReadsCP936Euro: mojibake for cut or euro-carrying cmd.exe output.
- TestEncodeRefusesCharacterTheCharsetCannotHold, TestDetectGBKWithEuroByte,
  TestDetectFragmentDropsTheSplitCharacter, TestDecodeOutputReadsCutCodePageText:
  the API's contract.
2026-09-26 11:52:22 -07:00
SivanCola 1c6776d9d9 fix(plugin): align Claude root instructions with upstream
Problem: Reasonix synthesized a SessionStart context hook from a Claude plugin root CLAUDE.md, although Claude Code does not load that file as plugin project context.

Root cause: Claude manifests shared the Codex compatibility helper that always appended root instructions.

Fix: keep supported Claude convention imports without the synthetic root hook, document the distinction, and update install-source fixtures to use official skills.

Verification: go test ./...; ./scripts/cache-guard.sh; focused plugin and install-source tests; desktop plugin/theme tests.
2026-08-10 01:59:34 +08:00
SivanCola 3552822776 feat: add generation-safe runtime composition 2026-08-08 01:03:45 +08:00
SivanCola 72a1e32b49 Fix cross-platform plugin Hook execution contracts / 修复跨平台插件 Hook 执行契约 (#6911)
* Fix plugin hook execution contracts

Problem: Windows plugin hooks with compound commands or nested quoting could still fail after the narrow quoted-batch compatibility fix, while explicit empty args could lose exec-form semantics across JSON round trips.

Root cause: Hook launch behavior was inferred from argument length and platform shell heuristics instead of preserving the manifest's exec-form or shell-form contract. CMD and PowerShell command strings also passed through incompatible quoting layers.

Fix: Preserve args field presence and shell selection through plugin parsing and loading, add explicit legacy/exec/shell execution modes, transport raw CMD and encoded PowerShell scripts without token re-rendering, retain the existing simple batch compatibility path, and document the cross-platform contract.

Verification: Focused Hook and plugin package tests, race and shuffled tests, fuzzing, root and Desktop full suites, go vet, and Windows/Linux cross-compilation.

* Fix Windows hook output regressions

Problem: The new compound CMD regression expected cmd.exe to strip quotes from %1, leaving the Windows job red. PowerShell hook scripts also preserved UTF-16LE input transport without forcing captured output to UTF-8.

Root cause: The test confused %1 with the dequoting %~1 form, and the Hook-specific EncodedCommand path did not reuse the shell runtime's existing PowerShell output-encoding prologue.

Fix: Align the CMD assertion with native batch semantics, share the PowerShell UTF-8 script wrapper across shell and Hook launchers, and exercise Chinese plus emoji output in the Windows runtime test.

Verification: Focused Hook/plugin/sandbox tests and vet; race/shuffle count 3; root and Desktop full suites; full vet; Windows amd64 test cross-compilation; PowerShell encoding fuzzing.
2026-07-24 23:07:09 +08:00
SivanCola 7fe7d2b3f3 Simplify MCP authorization for installed servers / 简化已安装 MCP 授权 (#6767)
* fix(mcp): trust installed readers in subagents

Problem:
User-installed MCP tools that declared readOnlyHint could be exposed as readers but then fail before dispatch in planners and read-only subagents. Users were told to configure a second per-tool reader policy after already installing or authorizing the server.

Root cause:
Strict reader trust did not treat explicit install or an exact project launch grant as authority for non-destructive server metadata. Direct, lazy, and on-demand paths also duplicated cached-to-live safety reconciliation, while trusted_read_only_tools remained wired through configuration and Desktop contracts.

Fix:
Authorize non-destructive readOnlyHint tools for installed or launch-authorized servers, carry reader intent only for trusted readers, and share one cached-to-live safety reconciliation boundary across lazy and on-demand adapters. Remove trusted_read_only_tools from runtime, Desktop, examples, and docs while accepting and dropping legacy input on the next save.

Verification:
- env -u DEEPSEEK_API_KEY go test -count=1 ./...
- cd desktop && go test -count=1 ./...
- cd desktop/frontend && pnpm test:typecheck
- cd desktop/frontend && pnpm exec tsx src/__tests__/capabilities-panel-actions.test.ts
- ./scripts/cache-guard.sh
- ./scripts/check-cache-impact.sh
- git diff --check

* refactor(mcp): simplify installed server authorization

Problem:
User-installed MCP servers still depended on official-catalog and per-tool reader trust layers, so direct, lazy, and on-demand paths could disagree and strict child sessions could reject an already authorized reader.

Root cause:
Server authorization, signed package status, cached reader trust, and legacy plan-mode allowlists were modeled as separate overlapping decisions.

Fix:
Remove the official MCP catalog, signature/revocation plumbing, package verification, per-tool cached trust, and obsolete reader settings. Use one server-level authorization source for user installs, host-session installs, and exact project grants while retaining live read-only, destructive, and capability-fingerprint checks.

Verification:
- go test ./internal/plugin ./internal/agent ./internal/boot ./internal/config ./internal/installsource ./internal/pluginpkg ./internal/mcplaunch ./internal/planmode
- go test ./internal/acp ./internal/agent ./internal/plugin ./internal/boot
- cd desktop && go test ./...
- ./scripts/cache-guard.sh
- git diff --check

* refactor(mcp): make install authorization final

Problem:
Installed MCP servers could still encounter server, per-tool, writer, or destructive approval branches, and users had to understand settings that duplicated the install decision.

Root cause:
MCP authorization, cached reader trust, approval policy, and process sandboxing evolved as separate paths across direct, lazy, and on-demand execution.

Fix:
Treat install or exact project identity confirmation as the server authorization boundary, keep only explicit global deny plus Plan/read-only and sandbox safety checks, remove catalog and approval configuration, and unify the persistent MCP process sandbox.

Verification:
env -u DEEPSEEK_API_KEY go test ./...
go build ./...
cd desktop && go test ./...
cd desktop/frontend && pnpm typecheck
cd desktop/frontend && pnpm exec tsx src/__tests__/capabilities-panel-actions.test.ts
cd desktop/frontend && pnpm exec tsx src/__tests__/decision-surface.test.tsx

* chore(mcp): remove retired cache helper

Problem:
CI lint found writeBoolKV left unused after per-tool reader trust was removed.

Root cause:
The cached reader-map fingerprint was deleted, but its serialization helper remained.

Fix:
Remove the dead helper.

Verification:
go test ./internal/plugin
go vet ./internal/plugin
go build ./...

* refactor(mcp): stop blocking schema-only drift

Problem:
Authorized MCP calls could be rejected when a cached tool schema differed from the live server even though authorization and safety classification were unchanged.

Root cause:
A capability fingerprint mixed schema compatibility with read-only and destructive execution safety across direct, lazy, and on-demand paths.

Fix:
Remove the tool-level schema fingerprint chain, keep live server authorization and read-only/destructive reclassification checks, and refresh changed schemas for the next session without interrupting the current authorized call.

Verification:
- go test ./internal/plugin ./internal/agent ./internal/tool
- env -u DEEPSEEK_API_KEY go test ./...
- go vet ./...
- go build ./...
- ./scripts/cache-guard.sh
- pnpm --dir site test
- cd desktop && go test ./...

* fix(mcp): preserve short Windows temp paths

Problem:
Windows stdio MCP servers inherited a deeply nested Reasonix private TEMP path. Servers such as MATLAB create watchdog Unix sockets below TEMP and exit before initialization when the path exceeds the platform limit.

Root cause:
MCP private-state preparation redirected TMP, TEMP, and TMPDIR below the workspace-scoped mcp-state directory on every platform even though Reasonix does not currently provide an OS sandbox on Windows.

Fix:
Keep the host temporary-directory environment for Windows stdio MCP processes while retaining private cache and state directories. Preserve the existing private temporary directory behavior on other platforms and add focused regression coverage for both branches.

Verification:
- go test ./internal/plugin -count=1
- GOOS=windows GOARCH=amd64 go test -c ./internal/plugin
- go test ./...
- go vet ./internal/plugin
- git diff --check

* Simplify MCP runtime authorization state

Use Spec.Authorized as the sole resolved server authorization result so installed and host-session MCP servers no longer depend on launch-manager presence.

Keep exact identity grants for project sources, align planner and strict read-only registries with non-destructive readOnlyHint metadata, and document the cooperative trust boundary.

Add source and agent-surface matrix coverage while preserving the main provider-visible MCP tool surface.

* Limit MCP identity digests to project servers

Problem: User-installed and host-session MCP servers still resolved and hashed their executable identity during every startup even though installation was already the authorization decision.

Root cause: Project launch authorization and schema-cache ownership shared generic fingerprint terminology, and start() computed the project identity before checking whether launch approval was required.

Fix: Skip project identity resolution for installed servers, remove dead and non-participating identity state, and rename the remaining concepts to ProjectLaunchIdentityDigest and SchemaCacheKey while preserving existing JSON keys and digest bytes.

Verification: go test ./internal/mcplaunch ./internal/plugin ./internal/capability ./internal/boot ./internal/control ./internal/agent; go test ./...; cd desktop && go test .; go vet ./...; ./scripts/cache-guard.sh; git diff --check.

* Restore MCP 0.53 compatibility surfaces

Problem: MCP authorization was simplified, but the current client still lacked four compatibility surfaces present in the 0.53 line: legacy SSE, workspace roots, progress notifications, and registry browsing/install.

Root cause: the Go transport abstraction dropped inbound notifications and rejected legacy SSE; runtime specs did not carry a separate workspace root; the registry UI and CLI had no official MCP Registry client.

Fix: add inbound request/progress routing across transports, implement legacy SSE with same-origin endpoint protection, advertise roots/list, and add an explicit cached official Registry marketplace to CLI and desktop. Remove stale approval documentation and the dead helper that broke lint.

Verification: go test ./...; focused go test -race for plugin/registry/CLI/control; go vet in root and desktop; desktop go test ./...; frontend production build and marketplace UI test; Windows amd64 cross-build; official Registry browse smoke test.

* Avoid MCP progress capacity overflow

* Align MCP authorization documentation

Problem: CLI help and Chinese documentation still contained ambiguous or obsolete wording that implied installed MCP writer or destructive calls could require per-call approval.

Root cause: The server-level authorization simplification removed MCP approval modes, but not every user-facing explanation was updated consistently.

Fix: State explicitly that install or exact project identity confirmation authorizes the server, that writer and destructive hints do not prompt per call, and that deny, Plan, and strict read-only filtering remain separate boundaries.

Verification: go test ./internal/cli; go vet ./internal/cli; reasonix mcp help smoke test; stale-wording scan; git diff --check.

* Harden MCP registry installs and SSE replies

Problem:
Registry cache freshness was shared across queries, cached entries could flow into install paths, and legacy SSE server requests spawned an unbounded reply goroutine per request.

Root cause:
The cache stored one global timestamp, Resolve reused Search's offline fallback, the desktop installed browse payloads directly, and the SSE reader delegated every reply to a new goroutine.

Fix:
Track freshness per Registry query with legacy-cache compatibility, require live resolution before CLI and desktop installs, and serialize SSE replies through a bounded single-worker queue with finite deadlines.

Verification:
- env -u DEEPSEEK_API_KEY go test -count=1 ./...
- env -u DEEPSEEK_API_KEY go test -race -count=1 ./internal/mcpregistry ./internal/plugin
- env -u DEEPSEEK_API_KEY go test -count=1 ./... (desktop)
- env -u DEEPSEEK_API_KEY go vet ./... (root and desktop)
- pnpm run test:all
- pnpm run build
- env -u DEEPSEEK_API_KEY ./scripts/cache-guard.sh
- git diff --check

* Reject unavailable live registry installs

Problem:
A Registry entry could become non-installable between browsing and the desktop's live re-resolution, leaving the generic add flow with an invalid empty connection. The SSE concurrency regression also used a fixed delay after routing the client response.

Root cause:
The desktop resolver verified freshness but not the resolved entry's installability, and the test inferred worker startup from elapsed time.

Fix:
Validate the live entry through PluginEntry before returning it to the installer, and synchronize the SSE test on the first reply POST instead of sleeping.

Verification:
- env -u DEEPSEEK_API_KEY go test -count=5 ./internal/plugin
- env -u DEEPSEEK_API_KEY go test -race -count=3 ./internal/plugin
- env -u DEEPSEEK_API_KEY go test -count=1 ./...
- cd desktop && env -u DEEPSEEK_API_KEY go test -count=1 ./...
- git diff --check
2026-07-22 01:58:43 +08:00
SivanCola c636f017a8 feat(mcp): enforce trusted execution boundaries 2026-07-15 13:55:34 +08:00
SivanCola 0d5f70401b fix(plugins): polish hook adapter semantics and deflake spawn tests
- An unbounded Reasonix wait now omits TaskOutput's optional timeout
  instead of claiming 0 — in Claude's schema timeout is the maximum wait
  in ms, so 0 would read as "don't wait", the opposite of the call.
- Structural input gaps (WebFetch prompt, NotebookEdit cell_number,
  TaskOutput multi-job) are reported once per hooks file instead of once
  per hook item, so a wildcard-matcher plugin gets one warning per gap
  rather than one per hook; file-level wording also keeps the emitted
  set deterministic across the random event-map iteration order.
- Real-spawn hook tests share one generous realSpawnTimeout: node cold
  starts and first-run scans of a freshly built test binary can stall
  for seconds on a loaded machine, and these tests assert behavior, not
  latency. The timeout-path case keeps its tight budget on purpose.
2026-07-15 02:24:50 +08:00
SivanCola c0c93610e6 fix(plugins): complete Claude hook contract adapters 2026-07-15 01:33:36 +08:00
SivanCola 347fd46f85 fix(plugins): flag Claude hook matchers that can never fire
Fourth review round on #6434 found two P2 accuracy gaps once the two P1
security bypasses were confirmed fixed:

- claude_compat.go accepted a hook's matcher without checking whether it
  names a real Claude tool Reasonix has no equivalent for. "WebSearch"
  (no Reasonix web-search tool) and "ExitPlanMode"/"EnterPlanMode" (plan
  approval is a controller-level decision, never dispatched through the
  tool-call hook path; confirmed by tracing permissionRequestHookPayload's
  explicit exclusion of planApprovalTool) are two concrete, independently
  verified examples: a hook scoped to only these matchers would never fire
  while the package still reported "full" compatibility. A plain
  tool-name matcher, or a "|"-alternation of them, naming only tools from a
  conservative, individually-verified unsupported set now downgrades the
  package to "partial" with a structured warning; matchers using other
  regex syntax, or naming even one tool Reasonix does support, are left
  unevaluated to avoid false positives. Also fixed a real gap found while
  auditing this: "run_skill" (Reasonix's skill-invocation tool) was never
  mapped to Claude's "Skill" tool name, so a "Skill"-scoped hook silently
  never fired either — same bug shape as the task/ask fixes from the
  previous round.
- Added a regression test exercising the opts.fresh-only branch of the
  fresh-human approval fix (PlanModeReadOnlyCommandApprovalTool, which is
  fresh via requestFreshApprovalDecision without being one of
  RequiresFreshHumanApprovalTool's fixed cases) — the existing tests only
  covered tools protected through requiresFreshApprovalTool(tool).

Co-authored-by: SivanCola <[email protected]>
2026-07-14 09:30:28 +08:00
SivanCola 741fb83664 fix(plugins): map Claude tool names in hooks and close remaining deny gaps
Second review round on #6434 found two more hook security gaps and an
over-claimed compatibility status:

- imported Claude hook matchers (e.g. "Bash", "Write|Edit") and the
  tool_name field a hook script inspects were left in Reasonix's own
  vocabulary (bash, write_file, ...), so a matcher-scoped or tool_name-gated
  Claude hook silently never fired. Added a name-translation layer
  (claudeFacingToolName) applied only to PayloadFormat "claude" hooks, both
  at match time and in the emitted payload.
- UserPromptSubmit's standard exit-0 JSON block (top-level
  {"decision":"block","reason":...}, distinct from PreToolUse/
  PermissionRequest's hookSpecificOutput shape) was not parsed, so a hook
  denying a sensitive prompt via JSON instead of exit 2 was ignored.
- PermissionRequest's "allow" decision (auto-approve without prompting) was
  never implemented — only the deny half of Claude's auto-answer contract
  from the prior fix. Runner.PermissionRequest now returns a tri-state
  decision (nil/deny/allow) and the approval flow honors all three.
- claude_compat.go now parses the "if" and "asyncRewake" hook fields and a
  Stop/SubagentStop hook import, and downgrades compatibility to "partial"
  with a structured warning instead of claiming "full" for semantics
  Reasonix doesn't evaluate (conditional execution, wake-on-exit-2 async,
  and block-the-turn respectively). The hook is still imported best-effort.

An existing test (TestPlanClaudeCompatibilityReportsAgentsHooksAndMCP,
modeled on the PR's own agent-critter example) had asserted "full"
compatibility for exactly this Stop-hook case; updated to the corrected
"partial" expectation.

Co-authored-by: SivanCola <[email protected]>
2026-07-14 03:38:21 +08:00
SivanCola 6506ba46b7 fix(plugins): honor Claude hook deny decisions and fix hook dedup
Review on #6434 found that an imported Claude PreToolUse/PermissionRequest
hook's deny decision was silently dropped: ParseOutput only ever extracted
additionalContext from hook stdout, and PermissionRequest's exit code 2 was
explicitly downgraded to a notification instead of a block. A Claude plugin
that relies on hooks to refuse dangerous commands or permissions would show
as fully compatible while the tool call still ran.

- honor Claude's JSON deny decision (hookSpecificOutput.permissionDecision
  for PreToolUse, hookSpecificOutput.decision.behavior for PermissionRequest)
  even on exit 0, and have exit code 2 block an imported PermissionRequest
  hook the same way Claude's own contract does. Scoped to PayloadFormat
  "claude" so native Reasonix hook semantics (PermissionRequest stays
  advisory-only) are unchanged.
- wire the PermissionRequest hook synchronously into the approval flow so a
  deny preempts the prompt instead of only notifying after the fact.
- fix hook dedup (appendUniqueHook) to also compare Env, Timeout, Async, and
  Cwd, so two hooks that share a command/matcher but differ in one of those
  no longer collapse into one.
- fix Windows path-separator mismatch: ${CLAUDE_PLUGIN_ROOT}/${CLAUDE_PROJECT_DIR}
  expansion left a mixed "C:\...\pkg/bin/server" string since the manifest's
  "/"-authored suffix was never converted to the host separator.
- lint: tagged switch (QF1003) and remove two functions orphaned by this PR's
  own refactor (appendClaudeSettingsHooks, pluginNameExists).

Co-authored-by: SivanCola <[email protected]>
2026-07-14 02:58:26 +08:00
SivanCola cfe2b80c5c feat(plugins): add Claude plugin compatibility 2026-07-14 01:25:22 +08:00
SivanCola ac2aa357c8 feat(plugin): qualify plugin skill slash names 2026-07-11 18:39:50 +08:00
SivanCola 4e61d66c6f feat(plugin): qualify plugin slash commands 2026-07-11 17:37:53 +08:00
SivanCola feff9f4da5 fix(plugin): pin GitHub plugin installs to the approved commit
Round-3 review follow-ups on the commands mapping:

- P1: plan and apply each cloned the source separately, so a repo that
  moved between the approved plan and the apply resolution could install
  content the approval never covered (and the optional planId echo could
  not catch the apply call's own second clone). pluginSource now reports
  the resolved commit for git sources; the plan records it on the action
  — entering the plan ID, so approvals fingerprint the exact snapshot —
  and apply pins a diverged clone back to the approved commit via a
  full-SHA fetch + detached checkout, refusing with a re-plan error when
  the snapshot is no longer reachable. Regression covers the unpinnable
  drift refusal end to end.
- P2: the command-dir adoption gate capped recursion at 5 levels while
  the runtime loader walks unbounded, so a plugin whose only command sat
  deeper was never adopted. The gate now delegates to internal/command
  itself — adoption and runtime discovery share one implementation and
  cannot diverge; a six-level nesting regression pins it.
2026-07-11 04:58:20 +00:00
SivanCola 2ef71c8165 fix(plugin): plan GitHub plugin installs from the real source tree
Review follow-ups on the commands mapping:

- P1: planGitHubPluginPackage assembled a sparse temp dir holding only
  the fetched manifest, so convention-discovered capabilities (skills/,
  commands/) and their warnings were invisible at plan time while apply
  cloned the full repo — the approved plan under-described what actually
  got installed. Plan and apply now resolve the source through one
  shared pluginSource resolver (shallow clone for GitHub), making the
  approval contract hold by construction; a plan/apply consistency
  regression covers it.
- P2: dirContainsCommandMd only looked at the top level, so a plugin
  whose commands all live in namespace subdirs (commands/git/commit.md)
  was never adopted even though the runtime loader walks them. The
  adoption gate now recurses (bounded depth, hidden dirs skipped).
- P3: appendInventoryText's empty-inventory fallback ignored commands, so
  a commands-only plugin printed its command list followed by
  "capabilities: no detailed inventory available".

Verified: reviewer's repro now plans skills=6 commands=11 for
github.com/OthmanAdi/planning-with-files with zero warnings.
2026-07-11 04:19:24 +00:00
SivanCola d38945513c feat(plugin): map Claude plugin commands to custom slash commands
Claude plugins like planning-with-files ship their primary interface as
commands/ (flat <name>.md prompt templates invoked as /<name>), which
Reasonix previously dropped with a "does not map yet" warning — the
plugin installed but /plan, /pwf etc. never appeared, reading to users
as a failed install (discussion #5762).

A Claude command is exactly Reasonix's custom slash command shape
(internal/command: frontmatter description/argument-hint, $ARGUMENTS /
$1..$N substitution, subdir namespacing), so map the capability instead
of warning about it:

- pluginpkg: Manifest gains Commands (explicit "commands" list in any
  manifest kind; claude plugins adopt conventional commands/ and
  .claude/commands/ automatically, even alongside explicit skills).
  Inventory/CapabilityCounts/describe now report commands, loaded via
  internal/command so names can never drift from runtime behavior.
- config: CommandDirsForRoot prepends enabled plugin packages' command
  roots at the lowest priority — boot and hot ReloadCommands both pick
  them up, and a user- or project-authored command wins name clashes.
- cli/installsource: plugin verify checks command roots; install plans
  carry commands/commandCount; plugin show lists commands.
- desktop: plugin view exposes commands count + details; capabilities
  panel renders a Commands section (en/zh/zh-TW strings).
- docs: EN/zh-CN plugin docs describe the mapping; commands removed
  from the unmapped-capabilities warning list (agents, hooks/hooks.json,
  .mcp.json still warn).

Verified end-to-end against planning-with-files v3.4.0: installs with
zero warnings, 6 skills + 11 commands, /plan invocable.
2026-07-10 17:58:13 +00:00
Sivan 5078a1987f Fix Windows encoded config reads 2026-07-10 02:40:43 +08:00
SivanCola 6a68799e18 fix(pluginpkg): discover conventional Claude skill dirs and warn on unmapped capabilities
Review findings on the Claude manifest support:

- A standard Claude plugin ships a metadata-only plugin.json and relies
  on Claude's auto-discovery of the conventional skills/ directory (packs
  in the wild also use .claude/skills/). Reading only the explicit
  `skills` field installed such packages as zero capabilities with no
  warning — "install succeeded" while nothing usable landed. Claude-kind
  parsing now falls back to the conventional directories when the
  manifest declares none, adopting a directory only if it actually holds
  a <name>/SKILL.md. Explicit declarations disable the fallback; codex
  parsing is untouched.
- Conventional Claude capabilities Reasonix does not map yet (commands/,
  agents/, hooks/hooks.json, .mcp.json) now surface as install warnings
  instead of being silently dropped.
- Negative test pins the includeCodexSessionStartHook boundary: a
  claude-kind package shipping hooks/session-start-codex must not get it
  registered as an executable SessionStart hook.
- Docs: retitle the compatibility section to "Codex & Claude", document
  the fallback, the warnings, and that marketplace.json indexes are out
  of scope.

Fallback reverse-verified: removing the convention-dir call fails
TestParseClaudePluginConventionSkillDirs.
2026-07-07 15:58:05 +08:00
SivanCola c070dc7432 Support Claude plugin manifests 2026-07-07 15:25:59 +08:00
SivanCola 9bc6344fa1 feat: add session plugin usage command 2026-07-03 11:58:21 +08:00
SivanCola 253faa132f feat: surface plugin package usage details 2026-07-03 11:39:17 +08:00
SivanCola ad3d06343d feat: expand Claude-style plugin compatibility 2026-07-03 11:01:47 +08:00
SivanCola 42ee48f214 feat(plugin): add unified plugin packages 2026-07-02 17:23:56 +08:00