opensocrates 1.1.2 → 1.1.3

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/CHANGELOG.md CHANGED
@@ -3,6 +3,108 @@
3
3
  All notable changes to OpenSocrates are documented here. This project follows
4
4
  [Semantic Versioning](https://semver.org/).
5
5
 
6
+ ## [Unreleased]
7
+
8
+ ## [1.1.3] - 2026-08-11
9
+
10
+ ### Added
11
+
12
+ - Bounded semantic packaged-README checks now reject novel universal-support,
13
+ vendor-endorsement, and managed-environment safety overclaims, with mutation
14
+ coverage that also protects accurate limitation wording from false positives
15
+ ([#27](https://github.com/ParkerHwang/OpenSocrates/issues/27)).
16
+ - Native plugin packages now expose one honest `darwin-arm64` boundary: the
17
+ unvalidated PowerShell launcher is removed, the POSIX launcher rejects every
18
+ other target, and package manifests and docs distinguish unshipped build
19
+ candidates from the released platform
20
+ ([#12](https://github.com/ParkerHwang/OpenSocrates/issues/12)).
21
+ - A Claude Chat upload probe records the exact verified v1.1.2 skills ZIP
22
+ shape and hash, live uploader acceptance, single visible skill entry,
23
+ catalog routing, representative method loading, and grounding audit without
24
+ retaining prompt or conversation content
25
+ ([#5](https://github.com/ParkerHwang/OpenSocrates/issues/5)).
26
+ - A privacy-safe Cowork probe validates the separately uploaded
27
+ `/opensocrates` skill and corrects the installation boundary: Claude Code CLI
28
+ marketplace registration is not a Cowork-native plugin install. The exact
29
+ published v1.1.2 plugin archive is rejected by Cowork's upload-size limits,
30
+ and repository sync is unavailable without a marketplace manifest, so native
31
+ hooks remain blocked
32
+ ([#4](https://github.com/ParkerHwang/OpenSocrates/issues/4)).
33
+ - A privacy-safe Claude Code desktop live probe now validates a complete
34
+ authenticated UserPromptSubmit → PostToolUse(Read) → Stop lifecycle. It
35
+ observes the owner-only instruction artifact, authenticated grounding
36
+ receipt, and final cleanup without retaining prompts, transcripts, paths,
37
+ credentials, payloads, or selector reasoning
38
+ ([#3](https://github.com/ParkerHwang/OpenSocrates/issues/3)).
39
+ - A reproducible packaged `darwin-arm64` timing harness verifies PostToolUse
40
+ receipt creation and Stop cleanup across fresh-path and warm runs. All 80
41
+ measured operations completed, the worst p95 was 1,333.067 ms against the
42
+ 3-second budget, and no timeout change is recommended
43
+ ([#6](https://github.com/ParkerHwang/OpenSocrates/issues/6)).
44
+ - An opt-in Claude structured-output matrix runs 20–50 bounded attempts per
45
+ explicitly requested model, requires 95% valid envelopes, and stores only
46
+ aggregate fixed outcome counts. The authenticated Claude Code 2.1.226
47
+ `host-default` row passed 20/20 attempts with every failure outcome at zero;
48
+ no unobserved named model is claimed
49
+ ([#8](https://github.com/ParkerHwang/OpenSocrates/issues/8)).
50
+ - An opt-in, privacy-safe real Claude selector contract verifies the exact
51
+ isolation command, environment allowlist, temporary workspace, and real
52
+ subprocess failure categories. An authenticated, non-sensitive live call on
53
+ Claude Code 2.1.226 returned `selected`, removed its temporary workspace, and
54
+ retained no prompt, raw output, credential, or selector reasoning
55
+ ([#7](https://github.com/ParkerHwang/OpenSocrates/issues/7)).
56
+ - Sanitized Claude Code payload receipts for the complete v1.1.2 hook
57
+ lifecycle — `SessionStart`, `UserPromptSubmit`, `PostToolUse`, `Stop`, and
58
+ `SessionEnd` — captured from a supported runtime and compared field by field
59
+ with the shared adapter mapping. The checks assert that `prompt_id` is
60
+ projected into turn identity, that `last_assistant_message` reaches the Stop
61
+ decision, that two consecutive turns of one session stay artifact-isolated,
62
+ and that Stop and `SessionEnd` clean up
63
+ ([#9](https://github.com/ParkerHwang/OpenSocrates/issues/9)).
64
+
65
+ ### Fixed
66
+
67
+ - The Claude Chat skills ZIP now uses the uploader's required single top-level
68
+ `opensocrates/` folder with `SKILL.md` directly inside; the prior plugin-shaped
69
+ archive was rejected by the real Customize → Skills upload UI
70
+ ([#5](https://github.com/ParkerHwang/OpenSocrates/issues/5)).
71
+ - The installer now accepts only explicit Claude list JSON variants, rejects
72
+ malformed, duplicate, or conflicting managed entries, surfaces disabled
73
+ plugins as drift, re-enables them on install/update, preserves their state on
74
+ rollback, and emits executable recovery commands after a secondary root
75
+ removal failure ([#11](https://github.com/ParkerHwang/OpenSocrates/issues/11)).
76
+ - POSIX and PowerShell launchers now enforce the single generated `bin/`
77
+ package layout and never probe a competing `bin/runtime/` tree
78
+ ([#26](https://github.com/ParkerHwang/OpenSocrates/issues/26)).
79
+ - Packaged-launcher checks now derive each host's runtime root from canonical
80
+ `generator.json` metadata and include a mismatch probe that remains
81
+ discriminating when no native runtime targets were built
82
+ ([#25](https://github.com/ParkerHwang/OpenSocrates/issues/25)).
83
+ - Claude sessions now supersede private instruction artifacts by `prompt_id`.
84
+ When `Stop` is absent, the next `UserPromptSubmit` removes prior turn trees in
85
+ that session while preserving the new active turn; `SessionEnd` and the
86
+ 24-hour `SessionStart` sweep remain backstops
87
+ ([#13](https://github.com/ParkerHwang/OpenSocrates/issues/13)).
88
+ - The Claude selector now enforces its 512 KiB stdout ceiling while reading the
89
+ child process stream. Output that crosses the limit terminates and reaps the
90
+ process immediately, fails open with a content-free diagnostic, and is never
91
+ retained or logged ([#10](https://github.com/ParkerHwang/OpenSocrates/issues/10)).
92
+ - An ordinary Claude payload no longer reports an unknown field. `effort` was
93
+ present in the captured `PostToolUse` and `Stop` receipts, while
94
+ `background_tasks` and `session_crons` were present in the captured `Stop`
95
+ receipt. They were missing from the parser's known-field sets, so the
96
+ `native_unknown_field_ignored` diagnostic fired on healthy turns. The parser
97
+ still never reads those fields, and no parsing, projection, or gating
98
+ behavior changed.
99
+
100
+ ### Security and privacy
101
+
102
+ - The committed receipts contain no prompt, transcript, path, credential, or
103
+ user-identifying content. Field names, value types, and container shapes are
104
+ preserved as captured; every sensitive value is a fixed synthetic marker, and
105
+ a check refuses every native-payload string outside a closed vocabulary so a
106
+ future re-capture cannot commit real payload content by accident.
107
+
6
108
  ## [1.1.2] - 2026-08-11
7
109
 
8
110
  ### Added
@@ -168,3 +270,4 @@ All notable changes to OpenSocrates are documented here. This project follows
168
270
  [1.1.0]: https://github.com/ParkerHwang/OpenSocrates/compare/v1.0.0...v1.1.0
169
271
  [1.1.1]: https://github.com/ParkerHwang/OpenSocrates/compare/v1.1.0...v1.1.1
170
272
  [1.1.2]: https://github.com/ParkerHwang/OpenSocrates/compare/v1.1.1...v1.1.2
273
+ [1.1.3]: https://github.com/ParkerHwang/OpenSocrates/compare/v1.1.2...v1.1.3
package/README.ko.md CHANGED
@@ -16,15 +16,19 @@ OpenSocrates는 Claude와 Codex를 위한 로컬 연동 및 저작 추론 프레
16
16
  추론 시스템을 고른 뒤, 이론과 예제 전체를 활성 작업에 추가합니다. 단순한 사실
17
17
  확인과 기계적인 작업은 그대로 통과할 수 있습니다.
18
18
 
19
- 버전 `1.1.2`에는 48개의 추론 시스템, Claude의 단일 사용자 진입점, 선택된 절차를
19
+ 버전 `1.1.3`에는 48개의 추론 시스템, Claude의 단일 사용자 진입점, 선택된 절차를
20
20
  실제로 읽었는지 확인하는 Claude 접지 게이트, Claude/Codex 통합 라이프사이클,
21
21
  선택형 자동 업데이트가 들어 있습니다. 현재 릴리스 플랫폼은 Apple Silicon
22
22
  macOS(`darwin-arm64`)입니다. 기존 호스트 로그인을 사용하므로 API 키나
23
23
  OpenSocrates 백엔드가 필요하지 않습니다.
24
24
 
25
- > **최신 릴리스: [OpenSocrates 1.1.2](https://github.com/ParkerHwang/OpenSocrates/releases/tag/v1.1.2).**
26
- > 인증된 Claude 접지 영수증, 한 번으로 제한된 보정, 아티팩트 인스턴스에 결합된
27
- > 재사용 방지를 추가했습니다. `npx --yes opensocrates@1.1.2 update --host all`로
25
+ 네이티브 플러그인 아카이브에는 의도적으로 `bin/launch.sh`만 포함됩니다. 이
26
+ 런처는 `darwin-arm64`만 허용하며, Intel Mac·Linux·Windows용 런처와 런타임은
27
+ 이번 릴리스에 포함되거나 지원되지 않습니다.
28
+
29
+ > **최신 릴리스: [OpenSocrates 1.1.3](https://github.com/ParkerHwang/OpenSocrates/releases/tag/v1.1.3).**
30
+ > 인증된 실제 호스트 검증, 설치·정리 강화, 엄격한 패키지 지원 범위, 업로드 가능한
31
+ > Claude Chat 스킬 ZIP을 추가했습니다. `npx --yes opensocrates@1.1.3 update --host all`로
28
32
  > 관리 중인 두 호스트를 함께 업데이트할 수 있습니다.
29
33
 
30
34
  ## 호스트 지원 범위
@@ -33,9 +37,9 @@ OpenSocrates 백엔드가 필요하지 않습니다.
33
37
  | --- | --- | --- | --- |
34
38
  | Codex CLI 및 Desktop | 지원 | OpenSocrates 플러그인 | `darwin-arm64` 릴리스 검증 완료 |
35
39
  | Claude Code CLI | `UserPromptSubmit` 훅으로 지원 | `/opensocrates` | `darwin-arm64` 로컬 검증 완료 |
36
- | Claude Code 데스크톱 앱 | Claude Code 플러그인이 실행되는 곳에서 구현됨 | `/opensocrates` | 구현 완료, 실측 수신 기록 없음 |
37
- | Claude Cowork | 로컬 플러그인 런타임을 사용할 수 있을 때 구현됨 | `/opensocrates` | 실험적, 실측 수신 기록 없음 |
38
- | Claude 웹 및 Desktop Chat | 훅 미지원 | `/opensocrates` | 스킬 전용, 업로드 경로 미검증 |
40
+ | Claude Code 데스크톱 앱 | Claude Code 플러그인이 실행되는 곳에서 지원 | `/opensocrates` | [인증된 훅 생명주기 로컬 검증 완료](docs/claude-desktop-live-probe.md) |
41
+ | Claude Cowork | 실측 설치에서는 자동 훅 미지원 | `/opensocrates` 독립 스킬 | [스킬 로컬 검증 완료, 네이티브 플러그인은 아카이브 제한으로 차단](docs/claude-cowork-live-probe.md) |
42
+ | Claude 웹 및 Desktop Chat | 훅 미지원 | `/opensocrates` | [실제 업로드 및 호출 검증 완료](docs/claude-chat-upload-probe.md) |
39
43
 
40
44
  상태 열은 서로 다른 네 단계를 뜻하며 같은 의미로 읽으면 안 됩니다.
41
45
 
@@ -52,11 +56,12 @@ Claude Chat 화면은 플러그인 훅을 실행하지 않습니다. 생성된
52
56
  Code와 Cowork 화면에서만 작동합니다. 훅이나 셀렉터를 사용할 수 없으면
53
57
  OpenSocrates는 작업을 막지 않고 통과합니다.
54
58
 
55
- Cowork에는 아직 확인되지 않은 전제가 하나 더 있습니다. OpenSocrates는
56
- `claude plugin marketplace add --scope user`로 마켓플레이스를 등록하며 이는
57
- Claude Code 사용자 설정에 기록됩니다. Anthropic 문서는 Cowork에서 플러그인 훅이
58
- 실행된다고 밝히지만, Claude Code CLI로 등록한 마켓플레이스가 Cowork에서 보인다는
59
- 내용은 없습니다. 실측 기록이 생기기 전까지 Cowork는 실험적 상태로 두세요.
59
+ 현재 실측 UI에서 Cowork는 Claude Code CLI 사용자 마켓플레이스 등록을 네이티브
60
+ Cowork 플러그인으로 가져오지 않습니다. 별도로 업로드한 `/opensocrates` 스킬은
61
+ 로컬 검증을 통과했지만, 공개된 v1.1.2 Claude 플러그인 아카이브는 Cowork의 문서화된
62
+ 압축 크기 제한과 실측 압축 해제 크기 제한을 모두 초과합니다. 저장소에도 Cowork
63
+ 마켓플레이스 매니페스트가 없습니다. Cowork 네이티브 플러그인을 설치하고 훅을 다시
64
+ 검증하기 전까지는 독립 스킬 전용 화면으로 취급하세요.
60
65
 
61
66
  ## 설치
62
67
 
@@ -73,7 +78,7 @@ Node.js 20 이상이 필요합니다. npm에 게시된 `opensocrates` 패키지
73
78
  ### 준비된 모든 호스트에 설치
74
79
 
75
80
  ```bash
76
- npx --yes opensocrates@1.1.2 install --host all
81
+ npx --yes opensocrates@1.1.3 install --host all
77
82
  ```
78
83
 
79
84
  모든 호스트 경로는 지원되는 인증 완료 CLI를 찾고, 어느 호스트도 바꾸기 전에
@@ -84,9 +89,9 @@ npx --yes opensocrates@1.1.2 install --host all
84
89
  전체 라이프사이클에서 같은 호스트 값을 사용할 수 있습니다.
85
90
 
86
91
  ```bash
87
- npx --yes opensocrates@1.1.2 status --host all
88
- npx --yes opensocrates@1.1.2 update --host all
89
- npx --yes opensocrates@1.1.2 remove --host all
92
+ npx --yes opensocrates@1.1.3 status --host all
93
+ npx --yes opensocrates@1.1.3 update --host all
94
+ npx --yes opensocrates@1.1.3 remove --host all
90
95
  ```
91
96
 
92
97
  소유자 전용 `~/.opensocrates/desired-state.json`에는 선택 채널, 설치된 호스트,
@@ -98,21 +103,28 @@ npx --yes opensocrates@1.1.2 remove --host all
98
103
  기존 사용자와의 호환성을 위해 기본 호스트는 계속 Codex입니다.
99
104
 
100
105
  ```bash
101
- npx --yes opensocrates@1.1.2 install
102
- # 같은 명령: npx --yes opensocrates@1.1.2 install --host codex
103
- npx --yes opensocrates@1.1.2 install --host claude
106
+ npx --yes opensocrates@1.1.3 install
107
+ # 같은 명령: npx --yes opensocrates@1.1.3 install --host codex
108
+ npx --yes opensocrates@1.1.3 install --host claude
104
109
  ```
105
110
 
106
111
  기존 `--host codex` 및 `--host claude` 라이프사이클은 그대로 지원합니다. 호스트별
107
112
  업데이트는 의도적으로 버전 차이를 만들 수 있고, 이후 `update --host all` 또는
108
113
  성공한 자동 조정이 desired state에 기록된 모든 호스트를 다시 한 버전으로 맞춥니다.
109
114
 
115
+ Claude 상태는 활성 설치와 설치됐지만 비활성화된 플러그인을 구분합니다. 원하는
116
+ 설치가 비활성 상태라면 drift로 보고합니다. `install` 또는 `update`를 실행하면 검증된
117
+ 패키지를 의도적으로 다시 활성화하며, 활성화가 실패하면 롤백이 이전의 활성·비활성
118
+ 상태를 복원합니다. 설치 프로그램은 Claude 목록 명령에서 실측한 직접 JSON 배열과
119
+ 명시적인 `marketplaces`/`plugins` 래퍼만 허용합니다. 개인정보를 제거한 2.1.226
120
+ 픽스처는 `installer/fixtures/claude-cli/`에 있습니다.
121
+
110
122
  ### 선택형 자동 업데이트
111
123
 
112
124
  ```bash
113
- npx --yes opensocrates@1.1.2 auto-update enable --host all
114
- npx --yes opensocrates@1.1.2 auto-update status
115
- npx --yes opensocrates@1.1.2 auto-update disable
125
+ npx --yes opensocrates@1.1.3 auto-update enable --host all
126
+ npx --yes opensocrates@1.1.3 auto-update status
127
+ npx --yes opensocrates@1.1.3 auto-update disable
116
128
  ```
117
129
 
118
130
  자동 업데이트는 명시적으로 켜기 전까지 비활성화되어 있습니다. macOS LaunchAgent는
@@ -134,8 +146,10 @@ LaunchAgent를 언로드하고 삭제하며, `remove --host all`도 관리 호
134
146
  ### Claude 웹 및 Desktop Chat 스킬
135
147
 
136
148
  이 화면들은 플러그인 스킬은 지원하지만 훅은 실행하지 않습니다. 릴리스에서
137
- `opensocrates-1.1.2-claude-chat-skills.zip`을 내려받아 Claude의 플러그인 사용자
138
- 설정 화면에서 업로드하세요. 패키지는 `/opensocrates` 스킬 하나만 노출하고, 48개
149
+ `opensocrates-1.1.3-claude-chat-skills.zip`을 내려받아 Claude의 **사용자 지정 →
150
+ 스킬 → 스킬 업로드** 화면에서 업로드하세요. ZIP은 업로더 요구사항에 맞춰
151
+ 최상위 `opensocrates/` 폴더 바로 아래에 `SKILL.md`를 둡니다. 패키지는
152
+ `/opensocrates` 스킬 하나만 노출하고, 48개
139
153
  방법 절차와 엄격도·근거·추적 제어는 내부 참조로 둡니다. Chat은 플러그인 훅을
140
154
  실행하지 않으므로 자동 선택은 없습니다. Anthropic의
141
155
  [플러그인 화면 안내](https://support.claude.com/en/articles/13837440-use-plugins-in-claude)를
@@ -146,21 +160,21 @@ LaunchAgent를 언로드하고 삭제하며, `remove --host all`도 관리 호
146
160
  npm 레지스트리를 거치지 않을 때도 같은 호스트 옵션을 사용합니다.
147
161
 
148
162
  ```bash
149
- npx --yes github:ParkerHwang/OpenSocrates#v1.1.2 install --host all
150
- npx --yes github:ParkerHwang/OpenSocrates#v1.1.2 install --host claude
151
- npx --yes github:ParkerHwang/OpenSocrates#v1.1.2 install --host codex
163
+ npx --yes github:ParkerHwang/OpenSocrates#v1.1.3 install --host all
164
+ npx --yes github:ParkerHwang/OpenSocrates#v1.1.3 install --host claude
165
+ npx --yes github:ParkerHwang/OpenSocrates#v1.1.3 install --host codex
152
166
  ```
153
167
 
154
168
  ### 릴리스 파일 직접 검증
155
169
 
156
- [v1.1.2 릴리스](https://github.com/ParkerHwang/OpenSocrates/releases/tag/v1.1.2)에서
170
+ [v1.1.3 릴리스](https://github.com/ParkerHwang/OpenSocrates/releases/tag/v1.1.3)에서
157
171
  `opensocrates.mjs`, 호스트 패키지, `.sha256` 파일을 내려받습니다. Claude의 예:
158
172
 
159
173
  ```bash
160
- shasum -a 256 -c opensocrates-1.1.2-claude-plugin.zip.sha256
174
+ shasum -a 256 -c opensocrates-1.1.3-claude-plugin.zip.sha256
161
175
  node opensocrates.mjs install --host claude \
162
- --asset opensocrates-1.1.2-claude-plugin.zip \
163
- --checksum opensocrates-1.1.2-claude-plugin.zip.sha256
176
+ --asset opensocrates-1.1.3-claude-plugin.zip \
177
+ --checksum opensocrates-1.1.3-claude-plugin.zip.sha256
164
178
  ```
165
179
 
166
180
  Codex 패키지는 `claude`를 `codex`로 바꾸면 됩니다.
@@ -175,7 +189,7 @@ Codex 패키지는 `claude`를 `codex`로 바꾸면 됩니다.
175
189
  ```bash
176
190
  claude plugin uninstall opensocrates@OpenSocrates --scope user
177
191
  claude plugin marketplace remove OpenSocrates --scope user
178
- npx --yes opensocrates@1.1.2 install --host claude
192
+ npx --yes opensocrates@1.1.3 install --host claude
179
193
  ```
180
194
 
181
195
  관리형 v1.1.0 Claude 설치를 업데이트하면 패키지 트리 전체가 교체됩니다. 따라서
@@ -262,8 +276,11 @@ OpenSocrates 기록, 로그, 메트릭, 진단 또는 instruction 파일에 쓰
262
276
  전체 Read 응답은 메모리에서 종료 표식만 확인하고 보관하지 않습니다. 소유자 전용
263
277
  접지 영수증에는 artifact 해시, 콘텐츠 리비전, 선택한 방법 ID, 키 기반 인증 및
264
278
  도구 사용 태그만 저장하며 프롬프트, 도구 출력, 워크스페이스 경로와 artifact 경로는
265
- 저장하지 않습니다. `Stop`에서 현재 turn 파일과 영수증을, `SessionEnd`에서 남은
266
- 세션 파일을, `SessionStart`에서 24시간이 지난 비정상 종료 잔여 파일을 삭제합니다.
279
+ 저장하지 않습니다. `Stop`에서 현재 turn 파일과 영수증을 삭제합니다. `Stop`이 오지
280
+ 않으면 같은 세션의 다음 `UserPromptSubmit`이 새 turn을 선택하기 전에 이전
281
+ `prompt_id` 트리를 모두 삭제하되 새 활성 트리는 보존합니다. `SessionEnd`는 세션에
282
+ 남은 파일을 삭제하고, 다음 `SessionStart`는 24시간이 지난 비정상 종료 잔여 파일을
283
+ 삭제합니다.
267
284
 
268
285
  자동 업데이트를 켜면 앞서 설명한 desired state와 간결한 영수증만 소유자 전용
269
286
  권한으로 저장합니다. 실행 중인 Claude나 Codex 세션을 검사하거나 종료하지 않습니다.
@@ -283,6 +300,20 @@ OpenSocrates 기록, 로그, 메트릭, 진단 또는 instruction 파일에 쓰
283
300
  인증된 실제 Claude Code 및 Codex 홈에서 검증하고 개인정보를 뺀 근거 묶음을
284
301
  확보하세요.
285
302
 
303
+ 셀렉터의 인증된 Claude 하위 프로세스 계약은 별도의
304
+ [선택형 검사](docs/real-claude-selector.md)로 검증합니다. 인증된 v1.1.2 영수증은 실제
305
+ 하위 프로세스 격리 및 구조화 출력 계약을 통과했으며, 일반 오프라인 테스트는 이
306
+ 실제 호출을 다시 실행하지 않습니다.
307
+
308
+ `--max-turns 1` 구조화 출력의 반복 동작은 별도의
309
+ [집계 신뢰성 매트릭스](docs/claude-structured-output-reliability.md)로 검증합니다. 인증된
310
+ Claude Code 2.1.226의 `host-default` 행은 20/20회를 통과했으며, 관측하지 않은
311
+ 명시적 모델은 지원된다고 주장하지 않습니다.
312
+
313
+ 패키지 Claude PostToolUse 영수증과 Stop 정리 지연 시간에는 재현 가능한
314
+ [Apple Silicon 측정 영수증](docs/claude-hook-timing.md)이 있습니다. v1.1.2에서 관측한
315
+ p95는 3초 훅 예산의 절반 이상을 여유로 남겨 타임아웃을 변경하지 않았습니다.
316
+
286
317
  ```bash
287
318
  make bootstrap
288
319
  make format-check
package/README.md CHANGED
@@ -17,16 +17,21 @@ uses a fresh host-native selector to choose relevant systems, and adds their
17
17
  complete theory and examples to the active task. Straightforward factual and
18
18
  mechanical work can pass through unchanged.
19
19
 
20
- Version `1.1.2` includes 48 reasoning systems, one user-facing Claude entry, a
20
+ Version `1.1.3` includes 48 reasoning systems, one user-facing Claude entry, a
21
21
  Claude grounding gate that verifies selected procedures were read, coordinated
22
22
  Claude/Codex lifecycle management, and opt-in automatic updates on
23
23
  Apple-silicon macOS (`darwin-arm64`). It uses your existing host login and does
24
24
  not require an API key or an OpenSocrates backend.
25
25
 
26
- > **Latest release: [OpenSocrates 1.1.2](https://github.com/ParkerHwang/OpenSocrates/releases/tag/v1.1.2).**
27
- > It adds authenticated Claude grounding receipts, one bounded repair pass,
28
- > and artifact-instance-bound replay protection. Upgrade both managed hosts
29
- > together with `npx --yes opensocrates@1.1.2 update --host all`.
26
+ The native plugin archives intentionally ship only `bin/launch.sh`. That
27
+ launcher accepts `darwin-arm64` only; macOS Intel, Linux, and Windows launchers
28
+ and runtimes are not shipped or supported in this release.
29
+
30
+ > **Latest release: [OpenSocrates 1.1.3](https://github.com/ParkerHwang/OpenSocrates/releases/tag/v1.1.3).**
31
+ > It adds authenticated live-host validation, installer and cleanup hardening,
32
+ > strict package support boundaries, and an uploadable Claude Chat skills ZIP.
33
+ > Upgrade both managed hosts together with
34
+ > `npx --yes opensocrates@1.1.3 update --host all`.
30
35
 
31
36
  ## Host support
32
37
 
@@ -34,9 +39,9 @@ not require an API key or an OpenSocrates backend.
34
39
  | --- | --- | --- | --- |
35
40
  | Codex CLI and Desktop | Yes | OpenSocrates plugin | Release-validated on `darwin-arm64` |
36
41
  | Claude Code CLI | Yes, through `UserPromptSubmit` | `/opensocrates` | Locally validated on `darwin-arm64` |
37
- | Claude Code desktop app | Implemented, where Claude Code plugins run | `/opensocrates` | Implemented; no live probe receipt |
38
- | Claude Cowork | Implemented, when the local plugin runtime is available | `/opensocrates` | Experimental; no live probe receipt |
39
- | Claude web and Desktop Chat | No hooks | `/opensocrates` | Skills-only; upload path unvalidated |
42
+ | Claude Code desktop app | Yes, where Claude Code plugins run | `/opensocrates` | [Authenticated hook lifecycle locally validated](docs/claude-desktop-live-probe.md) |
43
+ | Claude Cowork | No automatic hooks in the tested install | `/opensocrates` standalone skill | [Skill locally validated; native plugin blocked by archive limits](docs/claude-cowork-live-probe.md) |
44
+ | Claude web and Desktop Chat | No hooks | `/opensocrates` | [Live upload and invocation validated](docs/claude-chat-upload-probe.md) |
40
45
 
41
46
  The status column uses four distinct levels. Do not read them as
42
47
  interchangeable:
@@ -54,11 +59,13 @@ skills, but automatic per-prompt selection is available only on Claude Code
54
59
  and Cowork surfaces that execute the plugin runtime. OpenSocrates fails open
55
60
  if a hook or selector is unavailable.
56
61
 
57
- Cowork carries one further unverified assumption. OpenSocrates registers its
58
- marketplace with `claude plugin marketplace add --scope user`, which writes
59
- Claude Code user settings. Anthropic documents that plugin hooks run in Cowork,
60
- but does not document that a marketplace registered through the Claude Code CLI
61
- is visible to Cowork. Until a receipt exists, treat Cowork as experimental.
62
+ Cowork does not import the Claude Code CLI user-marketplace registration as a
63
+ native Cowork plugin in the current live UI. A separately uploaded
64
+ `/opensocrates` skill is locally validated, but the published v1.1.2 Claude
65
+ plugin archive exceeds Cowork's documented compressed limit and observed
66
+ uncompressed limit. The repository also lacks a Cowork marketplace manifest.
67
+ Until a Cowork-native plugin can be installed and its hooks re-probed, treat
68
+ Cowork as a standalone-skill-only surface.
62
69
 
63
70
  ## Install
64
71
 
@@ -75,7 +82,7 @@ before registering an owner-marked managed marketplace.
75
82
  ### Install every ready host
76
83
 
77
84
  ```bash
78
- npx --yes opensocrates@1.1.2 install --host all
85
+ npx --yes opensocrates@1.1.3 install --host all
79
86
  ```
80
87
 
81
88
  The all-host path detects supported, authenticated host CLIs, completes every
@@ -87,9 +94,9 @@ registration.
87
94
  Use the same host value for the complete lifecycle:
88
95
 
89
96
  ```bash
90
- npx --yes opensocrates@1.1.2 status --host all
91
- npx --yes opensocrates@1.1.2 update --host all
92
- npx --yes opensocrates@1.1.2 remove --host all
97
+ npx --yes opensocrates@1.1.3 status --host all
98
+ npx --yes opensocrates@1.1.3 update --host all
99
+ npx --yes opensocrates@1.1.3 remove --host all
93
100
  ```
94
101
 
95
102
  A private `~/.opensocrates/desired-state.json` manifest records the selected
@@ -102,9 +109,9 @@ automatic update, and per-host drift.
102
109
  Codex remains the default host for backward compatibility:
103
110
 
104
111
  ```bash
105
- npx --yes opensocrates@1.1.2 install
106
- # Equivalent: npx --yes opensocrates@1.1.2 install --host codex
107
- npx --yes opensocrates@1.1.2 install --host claude
112
+ npx --yes opensocrates@1.1.3 install
113
+ # Equivalent: npx --yes opensocrates@1.1.3 install --host codex
114
+ npx --yes opensocrates@1.1.3 install --host claude
108
115
  ```
109
116
 
110
117
  Existing `--host codex` and `--host claude` lifecycle commands remain
@@ -112,12 +119,21 @@ supported. A host-specific update can intentionally create drift; a later
112
119
  `update --host all` or successful automatic reconciliation brings every host
113
120
  recorded in desired state back to one version.
114
121
 
122
+ For Claude, status distinguishes an enabled installation from an
123
+ installed-but-disabled plugin. A disabled desired installation is reported as
124
+ drift. Running `install` or `update` intentionally installs the verified
125
+ package enabled again; if activation fails, rollback restores the previous
126
+ enabled or disabled state. The installer accepts only the observed direct JSON
127
+ arrays and explicit `marketplaces`/`plugins` wrappers from Claude's list
128
+ commands. The privacy-safe 2.1.226 fixture is under
129
+ `installer/fixtures/claude-cli/`.
130
+
115
131
  ### Opt-in automatic updates
116
132
 
117
133
  ```bash
118
- npx --yes opensocrates@1.1.2 auto-update enable --host all
119
- npx --yes opensocrates@1.1.2 auto-update status
120
- npx --yes opensocrates@1.1.2 auto-update disable
134
+ npx --yes opensocrates@1.1.3 auto-update enable --host all
135
+ npx --yes opensocrates@1.1.3 auto-update status
136
+ npx --yes opensocrates@1.1.3 auto-update disable
121
137
  ```
122
138
 
123
139
  Automatic updates are disabled until explicitly enabled. The macOS LaunchAgent
@@ -140,8 +156,10 @@ workspace paths. `auto-update disable` unloads and removes the LaunchAgent;
140
156
  ### Claude web and Desktop Chat skills
141
157
 
142
158
  These surfaces support plugin skills but not hooks. Download
143
- `opensocrates-1.1.2-claude-chat-skills.zip` from the release and upload it from
144
- Claude's plugin customization UI. The package exposes exactly one
159
+ `opensocrates-1.1.3-claude-chat-skills.zip` from the release and upload it from
160
+ Claude's **Customize → Skills → Upload skill** UI. The ZIP contains one
161
+ top-level `opensocrates/` folder with `SKILL.md` directly inside, as required by
162
+ the skill uploader. The package exposes exactly one
145
163
  `/opensocrates` skill; its 48 method procedures, rigor, evidence, and trace
146
164
  controls are internal supporting references. Automatic selection is absent
147
165
  because Chat does not execute plugin hooks. See Anthropic's
@@ -152,22 +170,22 @@ because Chat does not execute plugin hooks. See Anthropic's
152
170
  The same host option works without the npm registry:
153
171
 
154
172
  ```bash
155
- npx --yes github:ParkerHwang/OpenSocrates#v1.1.2 install --host all
156
- npx --yes github:ParkerHwang/OpenSocrates#v1.1.2 install --host claude
157
- npx --yes github:ParkerHwang/OpenSocrates#v1.1.2 install --host codex
173
+ npx --yes github:ParkerHwang/OpenSocrates#v1.1.3 install --host all
174
+ npx --yes github:ParkerHwang/OpenSocrates#v1.1.3 install --host claude
175
+ npx --yes github:ParkerHwang/OpenSocrates#v1.1.3 install --host codex
158
176
  ```
159
177
 
160
178
  ### Manual release verification
161
179
 
162
180
  Download `opensocrates.mjs`, the host package, and its `.sha256` file from the
163
- [v1.1.2 release](https://github.com/ParkerHwang/OpenSocrates/releases/tag/v1.1.2).
181
+ [v1.1.3 release](https://github.com/ParkerHwang/OpenSocrates/releases/tag/v1.1.3).
164
182
  For Claude, for example:
165
183
 
166
184
  ```bash
167
- shasum -a 256 -c opensocrates-1.1.2-claude-plugin.zip.sha256
185
+ shasum -a 256 -c opensocrates-1.1.3-claude-plugin.zip.sha256
168
186
  node opensocrates.mjs install --host claude \
169
- --asset opensocrates-1.1.2-claude-plugin.zip \
170
- --checksum opensocrates-1.1.2-claude-plugin.zip.sha256
187
+ --asset opensocrates-1.1.3-claude-plugin.zip \
188
+ --checksum opensocrates-1.1.3-claude-plugin.zip.sha256
171
189
  ```
172
190
 
173
191
  Replace `claude` with `codex` for the Codex package.
@@ -182,7 +200,7 @@ what is installed, remove it explicitly:
182
200
  ```bash
183
201
  claude plugin uninstall opensocrates@OpenSocrates --scope user
184
202
  claude plugin marketplace remove OpenSocrates --scope user
185
- npx --yes opensocrates@1.1.2 install --host claude
203
+ npx --yes opensocrates@1.1.3 install --host claude
186
204
  ```
187
205
 
188
206
  Updating a managed v1.1.0 Claude installation replaces the complete package
@@ -276,9 +294,11 @@ or instruction files. The complete Read response is inspected only in memory
276
294
  for its terminal marker and is not retained. The owner-only grounding receipt
277
295
  stores only an artifact digest, content revision, selected method IDs, and
278
296
  keyed authentication/tool-use tags—never a prompt, tool output, workspace path,
279
- or artifact path. Turn files and receipts are removed on `Stop`, remaining
280
- session files on `SessionEnd`, and crash leftovers older than 24 hours on
281
- `SessionStart`.
297
+ or artifact path. Turn files and receipts are removed on `Stop`. If `Stop` never
298
+ arrives, the next `UserPromptSubmit` in the same session removes every prior
299
+ `prompt_id` tree before selecting for the new turn while preserving the new
300
+ active tree. `SessionEnd` removes anything still left in the session, and the
301
+ next `SessionStart` removes crash leftovers older than 24 hours.
282
302
 
283
303
  When enabled, the updater stores only desired lifecycle state and the concise
284
304
  receipt described above, under owner-only permissions. It does not inspect or
@@ -299,6 +319,21 @@ Before merging a release candidate, follow the
299
319
  to exercise the real authenticated Claude Code and Codex homes and return a
300
320
  privacy-safe evidence bundle.
301
321
 
322
+ The selector's authenticated Claude subprocess contract has a separate
323
+ [opt-in check](docs/real-claude-selector.md). The authenticated v1.1.2 receipt
324
+ passes the real subprocess isolation and structured-output contract; the live
325
+ call is never rerun by the ordinary offline suite.
326
+
327
+ Repeated `--max-turns 1` structured-output behavior has a separate
328
+ [aggregate reliability matrix](docs/claude-structured-output-reliability.md).
329
+ The authenticated Claude Code 2.1.226 `host-default` row passed 20/20 attempts;
330
+ no unobserved named model is claimed supported.
331
+
332
+ Packaged Claude PostToolUse receipt and Stop cleanup latency has a reproducible
333
+ [Apple-silicon timing receipt](docs/claude-hook-timing.md). The observed v1.1.2
334
+ p95 results retain more than half of the 3-second hook budget, so the timeout is
335
+ unchanged.
336
+
302
337
  ```bash
303
338
  make bootstrap
304
339
  make format-check
package/SECURITY.md CHANGED
@@ -86,7 +86,10 @@ does not retain the response. The owner-only authenticated receipt stores only
86
86
  the artifact digest, content revision, selected method IDs, and keyed
87
87
  authentication/tool-use tags; prompts, transcripts, raw tool output,
88
88
  credentials, workspace paths, and artifact paths are excluded. `Stop` removes
89
- the receipt with the turn artifact, and the 24-hour sweep covers crash leftovers.
89
+ the receipt with the turn artifact. If `Stop` is absent, the next
90
+ `UserPromptSubmit` in that session removes prior prompt trees without touching
91
+ the new active tree. `SessionEnd` remains the session backstop, and the
92
+ 24-hour `SessionStart` sweep covers crash leftovers when neither event arrives.
90
93
 
91
94
  The gate's scope is deliberate and worth stating plainly. It establishes that a
92
95
  successful `Read` callback naming the exact current-turn artifact returned
@@ -107,7 +110,14 @@ transcripts, workspace paths, credentials, and raw error output are excluded.
107
110
  Automatic major-version upgrades are disabled unless the user explicitly
108
111
  changes that policy.
109
112
 
113
+ Installer rollback mutates only its owner-marked managed marketplace root. If
114
+ the failed root cannot be removed and the previous backup therefore cannot be
115
+ renamed into place, the installer preserves that backup and prints quoted,
116
+ executable recovery commands naming only those two managed paths.
117
+
110
118
  Signing, notarization, clean-machine installation, platforms other than
111
119
  `darwin-arm64`, Claude Chat automatic hooks, and live delivery on every host
112
- surface are not claimed as validated. See the release limitations file for the
113
- complete measured boundary.
120
+ surface are not claimed as validated. Native plugin archives ship only
121
+ `bin/launch.sh`; it rejects macOS Intel, Linux, Windows, and every other target,
122
+ and no PowerShell launcher is included. See the release limitations file for
123
+ the complete measured boundary.
package/VERSION CHANGED
@@ -1 +1 @@
1
- 1.1.2
1
+ 1.1.3
@@ -24,7 +24,7 @@ import { Readable } from "node:stream";
24
24
  import { pipeline } from "node:stream/promises";
25
25
  import { fileURLToPath } from "node:url";
26
26
 
27
- export const PRODUCT_VERSION = "1.1.2";
27
+ export const PRODUCT_VERSION = "1.1.3";
28
28
  export const REPOSITORY = "ParkerHwang/OpenSocrates";
29
29
  export const MARKETPLACE_NAME = "opensocrates";
30
30
  export const PLUGIN_NAME = "opensocrates";
@@ -83,6 +83,10 @@ function fail(message) {
83
83
  throw new InstallerError(message);
84
84
  }
85
85
 
86
+ function shellQuote(value) {
87
+ return `'${String(value).replaceAll("'", `'"'"'`)}'`;
88
+ }
89
+
86
90
  async function exists(target) {
87
91
  try {
88
92
  await access(target);
@@ -703,6 +707,58 @@ function runClaudeJson(args) {
703
707
  return runJson(claudeBinary(), args, "Claude Code");
704
708
  }
705
709
 
710
+ function claudeListEntries(payload, wrapper, label) {
711
+ if (Array.isArray(payload)) {
712
+ return payload;
713
+ }
714
+ if (
715
+ payload !== null &&
716
+ typeof payload === "object" &&
717
+ !Array.isArray(payload) &&
718
+ Object.keys(payload).length === 1 &&
719
+ Array.isArray(payload[wrapper])
720
+ ) {
721
+ return payload[wrapper];
722
+ }
723
+ fail(`Claude Code ${label} returned an unexpected schema`);
724
+ }
725
+
726
+ function requireUniqueClaudeEntries(entries, identity, label) {
727
+ const seen = new Set();
728
+ for (const entry of entries) {
729
+ if (
730
+ entry === null ||
731
+ typeof entry !== "object" ||
732
+ Array.isArray(entry) ||
733
+ typeof entry[identity] !== "string" ||
734
+ entry[identity].trim().length === 0
735
+ ) {
736
+ fail(`Claude Code ${label} returned a malformed entry`);
737
+ }
738
+ if (seen.has(entry[identity])) {
739
+ fail(`Claude Code ${label} returned duplicate entries for ${entry[identity]}`);
740
+ }
741
+ seen.add(entry[identity]);
742
+ }
743
+ return entries;
744
+ }
745
+
746
+ function claudeMarketplaceEntries(payload) {
747
+ return requireUniqueClaudeEntries(
748
+ claudeListEntries(payload, "marketplaces", "marketplace list"),
749
+ "name",
750
+ "marketplace list",
751
+ );
752
+ }
753
+
754
+ function claudePluginEntries(payload) {
755
+ return requireUniqueClaudeEntries(
756
+ claudeListEntries(payload, "plugins", "plugin list"),
757
+ "id",
758
+ "plugin list",
759
+ );
760
+ }
761
+
706
762
  function versionAtLeast(value, minimum) {
707
763
  const match = String(value).match(/(\d+)\.(\d+)\.(\d+)/u);
708
764
  if (!match) {
@@ -740,7 +796,7 @@ function marketplaceEntries(host) {
740
796
  host === "claude"
741
797
  ? runClaudeJson(["plugin", "marketplace", "list", "--json"])
742
798
  : runCodexJson(["plugin", "marketplace", "list", "--json"]).marketplaces;
743
- const entries = payload;
799
+ const entries = host === "claude" ? claudeMarketplaceEntries(payload) : payload;
744
800
  if (!Array.isArray(entries)) {
745
801
  fail(`${host} marketplace list returned an unexpected schema`);
746
802
  }
@@ -758,17 +814,22 @@ function marketplaceEntry(host) {
758
814
 
759
815
  function pluginState(host) {
760
816
  if (host === "claude") {
761
- const installed = runClaudeJson(["plugin", "list", "--json"]);
762
- if (!Array.isArray(installed)) {
763
- fail("Claude Code plugin list returned an unexpected schema");
764
- }
817
+ const installed = claudePluginEntries(runClaudeJson(["plugin", "list", "--json"]));
765
818
  const matches = installed.filter((entry) => entry?.id === PLUGIN_ID);
766
- if (matches.length > 1) {
767
- fail(`Claude Code reported duplicate entries for ${PLUGIN_ID}`);
819
+ if (matches.length === 0) return { kind: "missing", version: null };
820
+ const entry = matches[0];
821
+ if (
822
+ (entry.version !== undefined &&
823
+ entry.version !== null &&
824
+ (typeof entry.version !== "string" || entry.version.trim().length === 0)) ||
825
+ typeof entry.enabled !== "boolean"
826
+ ) {
827
+ fail(`Claude Code reported an invalid state for ${PLUGIN_ID}`);
768
828
  }
769
- return matches.length === 1
770
- ? { kind: "installed", version: matches[0].version ?? null }
771
- : { kind: "missing", version: null };
829
+ return {
830
+ kind: entry.enabled ? "installed" : "disabled",
831
+ version: entry.version ?? null,
832
+ };
772
833
  }
773
834
  const payload = runCodexJson([
774
835
  "plugin", "list", "--marketplace", MARKETPLACE_NAME, "--available", "--json",
@@ -799,15 +860,12 @@ function detectLegacyClaudeInstallation() {
799
860
  entry.name !== MARKETPLACE_NAME &&
800
861
  entry.name.toLowerCase() === MARKETPLACE_NAME,
801
862
  );
802
- const plugins = runClaudeJson(["plugin", "list", "--json"]);
803
- const legacyPlugins = Array.isArray(plugins)
804
- ? plugins.filter(
805
- (entry) =>
806
- typeof entry?.id === "string" &&
807
- entry.id !== PLUGIN_ID &&
808
- entry.id.toLowerCase() === PLUGIN_ID,
809
- )
810
- : [];
863
+ const plugins = claudePluginEntries(runClaudeJson(["plugin", "list", "--json"]));
864
+ const legacyPlugins = plugins.filter(
865
+ (entry) =>
866
+ entry.id !== PLUGIN_ID &&
867
+ entry.id.toLowerCase() === PLUGIN_ID,
868
+ );
811
869
  const names = marketplaces.map((entry) => entry.name).join(", ") || "OpenSocrates";
812
870
  return { found: marketplaces.length > 0 || legacyPlugins.length > 0, names };
813
871
  }
@@ -1148,7 +1206,34 @@ function entryRoot(entry, host) {
1148
1206
  if (entry === null) {
1149
1207
  return null;
1150
1208
  }
1151
- const value = host === "claude" ? entry.path ?? entry.installLocation : entry.root;
1209
+ let value;
1210
+ if (host === "claude") {
1211
+ const fields = ["path", "installLocation"].filter(
1212
+ (field) => entry[field] !== undefined && entry[field] !== null,
1213
+ );
1214
+ if (
1215
+ fields.length === 0 ||
1216
+ fields.some(
1217
+ (field) => typeof entry[field] !== "string" || entry[field].trim().length === 0,
1218
+ )
1219
+ ) {
1220
+ fail(`${host} marketplace ${MARKETPLACE_NAME} has no usable root`);
1221
+ }
1222
+ const roots = fields.map((field) => {
1223
+ const candidate = resolve(entry[field]);
1224
+ try {
1225
+ return realpathSync(candidate);
1226
+ } catch {
1227
+ return candidate;
1228
+ }
1229
+ });
1230
+ if (new Set(roots).size !== 1) {
1231
+ fail(`${host} marketplace ${MARKETPLACE_NAME} reported conflicting roots`);
1232
+ }
1233
+ [value] = roots;
1234
+ } else {
1235
+ value = entry.root;
1236
+ }
1152
1237
  if (typeof value !== "string" || value.trim().length === 0) {
1153
1238
  fail(`${host} marketplace ${MARKETPLACE_NAME} has no usable root`);
1154
1239
  }
@@ -1166,7 +1251,7 @@ function entryRoot(entry, host) {
1166
1251
 
1167
1252
  function removeRegistration(host, entry, state) {
1168
1253
  if (host === "claude") {
1169
- if (state.kind === "installed") {
1254
+ if (["installed", "disabled"].includes(state.kind)) {
1170
1255
  run(claudeBinary(), ["plugin", "uninstall", PLUGIN_ID, "--scope", "user"]);
1171
1256
  }
1172
1257
  if (entry !== null) {
@@ -1184,11 +1269,14 @@ function removeRegistration(host, entry, state) {
1184
1269
  }
1185
1270
  }
1186
1271
 
1187
- function addRegistration(host, root, installPlugin) {
1272
+ function addRegistration(host, root, installPlugin, { enabled = true } = {}) {
1188
1273
  if (host === "claude") {
1189
1274
  run(claudeBinary(), ["plugin", "marketplace", "add", root, "--scope", "user"]);
1190
1275
  if (installPlugin) {
1191
1276
  run(claudeBinary(), ["plugin", "install", PLUGIN_ID, "--scope", "user"]);
1277
+ if (!enabled) {
1278
+ run(claudeBinary(), ["plugin", "disable", PLUGIN_ID, "--scope", "user"]);
1279
+ }
1192
1280
  }
1193
1281
  return null;
1194
1282
  }
@@ -1212,7 +1300,7 @@ function removeRegistrationBestEffort(host) {
1212
1300
  }
1213
1301
  try {
1214
1302
  const state = pluginState(host);
1215
- if (state.kind === "installed") {
1303
+ if (["installed", "disabled"].includes(state.kind)) {
1216
1304
  const binary = host === "claude" ? claudeBinary() : codexBinary();
1217
1305
  const args =
1218
1306
  host === "claude"
@@ -1403,7 +1491,12 @@ async function rollbackInstallation(transaction) {
1403
1491
  (restored || !transaction.backupCreated)
1404
1492
  ) {
1405
1493
  await recoveryStep(`re-register the previous ${host} installation`, async () => {
1406
- addRegistration(host, paths.root, previousState.kind === "installed");
1494
+ addRegistration(
1495
+ host,
1496
+ paths.root,
1497
+ ["installed", "disabled"].includes(previousState.kind),
1498
+ { enabled: previousState.kind !== "disabled" },
1499
+ );
1407
1500
  });
1408
1501
  }
1409
1502
  if (transaction.backupCreated && !restored) {
@@ -1411,10 +1504,11 @@ async function rollbackInstallation(transaction) {
1411
1504
  `error: the previous ${host} OpenSocrates installation could not be restored automatically.`,
1412
1505
  );
1413
1506
  console.error(`error: your previous files are preserved at: ${transaction.backup}`);
1507
+ console.error(`error: recovery command: /bin/rm -rf -- ${shellQuote(paths.root)}`);
1414
1508
  console.error(
1415
- `error: remove ${paths.root} if it still exists, move the preserved directory ` +
1416
- `to ${paths.root}, then rerun \`opensocrates install --host ${host}\`.`,
1509
+ `error: recovery command: /bin/mv -- ${shellQuote(transaction.backup)} ${shellQuote(paths.root)}`,
1417
1510
  );
1511
+ console.error(`error: recovery command: opensocrates install --host ${host}`);
1418
1512
  }
1419
1513
  }
1420
1514
 
@@ -1537,6 +1631,11 @@ async function showStatus(host) {
1537
1631
  : " (in sync)"
1538
1632
  : " (not in desired state)"),
1539
1633
  );
1634
+ } else if (status.kind === "disabled") {
1635
+ console.log(
1636
+ `${candidate}: installed but disabled (${status.version ?? "unknown"})` +
1637
+ (expected ? " (drift: desired host is not active)" : " (not in desired state)"),
1638
+ );
1540
1639
  } else if (status.kind === "available") {
1541
1640
  console.log(
1542
1641
  `${candidate}: available but not installed (${status.version ?? "unknown"})` +
@@ -1563,6 +1662,11 @@ async function showStatus(host) {
1563
1662
  const status = await inspectHostStatus(host);
1564
1663
  if (status.kind === "installed") {
1565
1664
  console.log(`OpenSocrates ${status.version ?? "unknown"} is installed.`);
1665
+ } else if (status.kind === "disabled") {
1666
+ console.log(
1667
+ `OpenSocrates ${status.version ?? "unknown"} is installed but disabled. ` +
1668
+ "Run install or update to re-enable it.",
1669
+ );
1566
1670
  } else if (status.kind === "available") {
1567
1671
  console.log(`OpenSocrates ${status.version ?? "unknown"} is available but not installed.`);
1568
1672
  } else if (status.kind === "files-only") {
@@ -1606,7 +1710,8 @@ async function rollbackRemoval(transaction) {
1606
1710
  addRegistration(
1607
1711
  transaction.host,
1608
1712
  transaction.paths.root,
1609
- transaction.previousState.kind === "installed",
1713
+ ["installed", "disabled"].includes(transaction.previousState.kind),
1714
+ { enabled: transaction.previousState.kind !== "disabled" },
1610
1715
  );
1611
1716
  });
1612
1717
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opensocrates",
3
- "version": "1.1.2",
3
+ "version": "1.1.3",
4
4
  "description": "Install and manage OpenSocrates for Claude Code, Cowork, and Codex.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -13,7 +13,7 @@
13
13
  "VERSION"
14
14
  ],
15
15
  "scripts": {
16
- "test": "node --test installer/opensocrates.test.mjs installer/lifecycle.test.mjs tools/clean_machine_acceptance.test.mjs && npm run test:npx",
16
+ "test": "node --test installer/opensocrates.test.mjs installer/lifecycle.test.mjs tools/clean_machine_acceptance.test.mjs tools/release_gate.test.mjs && npm run test:npx",
17
17
  "test:npx": "node installer/package-smoke.mjs",
18
18
  "pack:check": "npm pack --dry-run",
19
19
  "prepublishOnly": "npm test"