aptly 0.1.0 → 0.1.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.
@@ -0,0 +1,98 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ import { windowsPowerShellPath } from './windows-powershell.js';
3
+ // Static code only: paths are JSON on stdin, never PowerShell source/arguments.
4
+ // Windows PowerShell's .NET Framework ACL APIs keep inherited ACEs visible.
5
+ const script = String.raw `
6
+ $ErrorActionPreference = 'Stop'
7
+ [Console]::InputEncoding = [Text.UTF8Encoding]::new($false)
8
+ try {
9
+ $request = [Console]::In.ReadToEnd() | ConvertFrom-Json
10
+ $path = [string]$request.path
11
+ if ([string]::IsNullOrWhiteSpace($path) -or ![IO.Path]::IsPathRooted($path)) { throw 'invalid' }
12
+ $attributes = [IO.File]::GetAttributes($path)
13
+ if (($attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0) { throw 'invalid' }
14
+ $isDirectory = ($attributes -band [IO.FileAttributes]::Directory) -ne 0
15
+ $sidType = [Security.Principal.SecurityIdentifier]
16
+ $current = [Security.Principal.WindowsIdentity]::GetCurrent().User
17
+ $allowed = @($current.Value, 'S-1-5-18', 'S-1-5-32-544')
18
+ if ($request.action -eq 'protect-new-directory') {
19
+ if (!$isDirectory) { throw 'invalid' }
20
+ $acl = [Security.AccessControl.DirectorySecurity]::new()
21
+ $acl.SetOwner($current)
22
+ $acl.SetAccessRuleProtection($true, $false)
23
+ foreach ($sid in $allowed) {
24
+ $identity = [Security.Principal.SecurityIdentifier]::new($sid)
25
+ $rule = [Security.AccessControl.FileSystemAccessRule]::new($identity, [Security.AccessControl.FileSystemRights]::FullControl, ([Security.AccessControl.InheritanceFlags]::ContainerInherit -bor [Security.AccessControl.InheritanceFlags]::ObjectInherit), [Security.AccessControl.PropagationFlags]::None, [Security.AccessControl.AccessControlType]::Allow)
26
+ $acl.AddAccessRule($rule)
27
+ }
28
+ [IO.Directory]::SetAccessControl($path, $acl)
29
+ } elseif ($request.action -ne 'assert-private') { throw 'invalid' }
30
+ $acl = if ($isDirectory) { [IO.Directory]::GetAccessControl($path) } else { [IO.File]::GetAccessControl($path) }
31
+ if ($allowed -notcontains $acl.GetOwner($sidType).Value) { throw 'invalid' }
32
+ $descriptor = [Security.AccessControl.RawSecurityDescriptor]::new($acl.GetSecurityDescriptorBinaryForm(), 0)
33
+ if ($null -eq $descriptor.DiscretionaryAcl) { throw 'invalid' }
34
+ foreach ($rule in $acl.GetAccessRules($true, $true, $sidType)) {
35
+ if ($rule.AccessControlType -eq [Security.AccessControl.AccessControlType]::Allow -and $allowed -notcontains $rule.IdentityReference.Value) { throw 'invalid' }
36
+ }
37
+ [Console]::Out.Write('private')
38
+ } catch { [Console]::Out.Write('rejected'); exit 1 }
39
+ `;
40
+ const run = (executable, args, input) => execFileSync(executable, [...args], {
41
+ input, encoding: 'utf8', windowsHide: true, timeout: 10000, maxBuffer: 4096, stdio: ['pipe', 'pipe', 'pipe']
42
+ });
43
+ const copyScript = String.raw `
44
+ $ErrorActionPreference = 'Stop'
45
+ [Console]::InputEncoding = [Text.UTF8Encoding]::new($false)
46
+ try {
47
+ $request = [Console]::In.ReadToEnd() | ConvertFrom-Json
48
+ $source = [string]$request.source
49
+ $target = [string]$request.target
50
+ if (![IO.Path]::IsPathRooted($source) -or ![IO.Path]::IsPathRooted($target)) { throw 'invalid' }
51
+ $source = [IO.Path]::GetFullPath($source)
52
+ $target = [IO.Path]::GetFullPath($target)
53
+ if ([string]::Equals($source, $target, [StringComparison]::OrdinalIgnoreCase) -or ![string]::Equals([IO.Path]::GetDirectoryName($source), [IO.Path]::GetDirectoryName($target), [StringComparison]::OrdinalIgnoreCase)) { throw 'invalid' }
54
+ foreach ($path in @($source, $target)) {
55
+ $attributes = [IO.File]::GetAttributes($path)
56
+ if (($attributes -band [IO.FileAttributes]::Directory) -ne 0 -or ($attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0) { throw 'invalid' }
57
+ }
58
+ $sections = [Security.AccessControl.AccessControlSections]::Access
59
+ $sourceAcl = [IO.File]::GetAccessControl($source, $sections)
60
+ $expected = $sourceAcl.GetSecurityDescriptorSddlForm($sections)
61
+ $initial = [IO.File]::GetAccessControl($target, $sections).GetSecurityDescriptorSddlForm($sections)
62
+ # A sibling stage normally inherits the same ACL. Avoid reapplying inherited
63
+ # ACEs when the full DACL descriptor (including protection) already matches.
64
+ if ($expected -ceq $initial) { [Console]::Out.Write('private'); exit 0 }
65
+ $acl = [Security.AccessControl.FileSecurity]::new()
66
+ $acl.SetSecurityDescriptorSddlForm($expected, $sections)
67
+ [IO.File]::SetAccessControl($target, $acl)
68
+ $actual = [IO.File]::GetAccessControl($target, $sections).GetSecurityDescriptorSddlForm($sections)
69
+ if ($expected -cne $actual) { throw 'invalid' }
70
+ [Console]::Out.Write('private')
71
+ } catch { [Console]::Out.Write('rejected'); exit 1 }
72
+ `;
73
+ function runScript(scriptText, input, options) {
74
+ try {
75
+ const executable = windowsPowerShellPath();
76
+ const result = (options.execute ?? run)(executable, ['-NoLogo', '-NoProfile', '-NonInteractive', '-EncodedCommand', Buffer.from(scriptText, 'utf16le').toString('base64')], JSON.stringify(input));
77
+ if (result !== 'private')
78
+ throw new Error();
79
+ }
80
+ catch {
81
+ throw new Error('windows_private_permissions');
82
+ }
83
+ }
84
+ function invoke(path, action, options) {
85
+ if (typeof path !== 'string' || !path || path.includes('\0'))
86
+ throw new Error('windows_private_permissions');
87
+ runScript(script, { path, action }, options);
88
+ }
89
+ /** Preserve the existing file DACL, including inheritance protection, before writing stage content. */
90
+ export function copyWindowsFilePermissions(source, target, options = {}) {
91
+ if ([source, target].some(path => typeof path !== 'string' || !path || path.includes('\0')))
92
+ throw new Error('windows_private_permissions');
93
+ runScript(copyScript, { source, target }, options);
94
+ }
95
+ /** Existing paths are inspected, never repaired or granted new permissions. */
96
+ export function assertWindowsPrivatePath(path, options = {}) { invoke(path, 'assert-private', options); }
97
+ /** Only call immediately after this process exclusively created the directory. */
98
+ export function protectNewWindowsPrivateDirectory(path, options = {}) { invoke(path, 'protect-new-directory', options); }
package/docs/guide/en.md CHANGED
@@ -4,47 +4,158 @@
4
4
 
5
5
  [Back to README](../../README.en.md)
6
6
 
7
- Korean is the canonical documentation. This guide covers the local 0.1.0 package draft. It does not indicate publication to the npm registry or model availability for every account. Claude Code or Codex controls native subagent execution, approvals, sandboxing, and MCP permissions.
7
+ Korean is the canonical documentation. This guide covers general Aptly installation and usage. Package availability does not establish model access for every account. Claude Code or Codex controls native subagent execution, approvals, sandboxing, and MCP permissions.
8
8
 
9
9
  ## Installation
10
10
 
11
- You need Node.js 24 or later and Claude Code or Codex. Specify a reviewed archive and installation location.
11
+ Requires Node.js 24 or later and Claude Code or Codex. Install the latest stable npm release globally:
12
+
13
+ **Before upgrading an existing persistent ledger to 0.1.2 or later**, stop every host, MCP server, standalone CLI, and older process that can access it. The persistent gate cannot coexist with older writers. Fresh installations need no legacy-lock quarantine. If an old empty lock remains, follow the [ledger transition procedure](#interrupted-processes-and-ledger-locks): quarantine only a confirmed legacy lock while offline and preserve the JSON ledger, `unknown` records, costs, and completed cache.
12
14
 
13
15
  ```sh
14
- aptly_install=/absolute/aptly-local
15
- npm install --prefix "$aptly_install" /absolute/path/aptly-0.1.0.tgz
16
- "$aptly_install/node_modules/.bin/aptly" setup --client all --mode defaults
16
+ npm install -g aptly@latest
17
+ aptly setup --client all --mode defaults
18
+ aptly --version
17
19
  ```
18
20
 
19
- In later examples, `aptly` refers to the executable in this installation. Use its full path if it is not on PATH. Do not execute TypeScript source directly from an installed `node_modules` directory.
21
+ Use `aptly --version` to check the installed version. Switching Node.js environments may require reinstalling.
20
22
 
21
23
  `postinstall` registers the MCP server and a `PreToolUse` Hook in the current user's existing host configuration directories. If you use `CODEX_HOME` or `CLAUDE_CONFIG_DIR`, pass the same environment to installation. Automatic registration skips host directories that do not exist; explicit `setup` can create them. To register only one host, use `--client codex` or `--client claude`.
22
24
 
23
25
  New registrations set `ROUTER_PROVIDER=decisions`, the Hook's `defaults` policy, and a private ledger for each host. Other settings and Hooks, and existing Aptly commands, environments, providers, and ledger paths, are preserved. Existing fixture settings are not automatically switched to paid mode.
24
26
 
27
+ ### Configure with the CLI
28
+
29
+ The `set` commands require 0.1.1 or later. Commands do not automatically switch existing launch paths. API-key setup accepts only the current package’s standard Node/server registration with a verified OS-source-compatible runtime. Custom wrappers, older versions, and other installation prefixes are rejected with `credential_launcher_unverified`; explicitly switch the registration or upgrade it before trying again.
30
+
31
+ ```sh
32
+ aptly set default
33
+ aptly set env APTLY_CODEX_MODELS
34
+ aptly set env OPENAI_API_KEY
35
+ ```
36
+
37
+ The first command registers Claude Code and Codex and adds missing default environment settings. It preserves existing launch commands, environment whitelists, providers, custom candidates, and advisory policies. The second prompts for a value with hidden input and changes only the named variable. The third stores the API key in the OS credential store and connects it to the selected host at startup. Do not pass values as command arguments.
38
+
39
+ Both hosts are selected by default. Use `--client codex`, `--client claude`, or `--client all` to select targets. For example, enter a JSON string such as `["haiku","sonnet","opus"]` for Claude candidates.
40
+
41
+ ```sh
42
+ aptly set env APTLY_CLAUDE_MODELS --client claude
43
+ ```
44
+
45
+ Use explicit `--stdin` for automation. This is a public-variable example; never put a real key in a `printf` command or shell history.
46
+
47
+ ```sh
48
+ printf '%s\n' '["gpt-6.1-terra"]' | aptly set env APTLY_CODEX_MODELS --stdin
49
+ ```
50
+
51
+ Allowed public keys are `ROUTER_PROVIDER` (`fixture` or `decisions`), `APTLY_CLAUDE_MODELS`, `APTLY_CODEX_MODELS`, `CLAUDE_MODELS`, `CODEX_MODELS`, and `APTLY_MODEL_INFO_PATH`. Candidates must be JSON array strings of supported models. Invalid values, duplicates, and primary-name/alias conflicts are rejected. `[]` disables that host; a model-cache path must be absolute and inside a private directory. Managed ledger and Hook-policy paths cannot be changed through this command.
52
+
53
+ API keys are stored through macOS Keychain, an installed Linux `secret-tool`, or Windows Credential Manager. The Windows backend uses PowerShell and Win32 Generic credentials in the current-user scope, with a limit of 2,560 UTF-8 bytes. Windows CI verified synthetic key storage, lookup, update and deletion, Unicode paths, isolated host configuration, key references, and ACL preservation. It did not run actual Claude Code or Codex sessions on Windows. Unavailable credential stores return an error; there is no plaintext-file fallback. Keys use separate host accounts, while MCP files contain only `APTLY_SECRET_BACKEND=os` and `APTLY_SECRET_CLIENT` references. Once the OS source is selected, a missing or unreadable key does not silently fall back to another environment key. `set default` preserves existing wrapper/environment authentication; running `set env OPENAI_API_KEY` explicitly selects OS-store authentication.
54
+
55
+ Results contain host statuses and fixed error codes, never key values. Inspect each host when the result is `partial`. `credential_store_failed` requires checking store access/support; `credential_rollback_failed` requires checking whether the previous key was restored. A failed host restores its old key where possible. Do not interpret success on another host as an atomic success across all hosts.
56
+
57
+ A store timeout or malformed acknowledgement may occur after the OS has written the new value. On failure, Aptly attempts to restore the previous value (or remove the new entry if none existed), then reads it back. `credential_rollback_failed` is added to the original failure codes when recovery cannot be confirmed; do not assume either key is active. Check the OS credential store before retrying. This is best-effort recovery, not an atomic transaction across processes or hosts.
58
+
59
+ Restart MCP, then inspect `provider`, candidates, and `credential_status` with `get_routing_capabilities`. `os_store` means a key was loaded, not that the API authenticated it. Default and public-variable setup continue to preserve custom wrappers. API-key setup does not change host settings for an unverified launch path and restores that host's previous key where possible if storage was already attempted. Do not assume a new key automatically takes effect through an existing wrapper. These commands do not bypass Codex `/hooks` trust or host permissions.
60
+
61
+ ### Recover an incompatible launcher
62
+
63
+ These options require Aptly 0.1.2 or later and are not in npm 0.1.1. To test from source, run `npm ci --ignore-scripts` and `npm run build`, then replace `aptly` below with `node bin/aptly.ts`.
64
+
65
+ Key setup diagnoses selected hosts before input or storage. If any host fails, it does not prompt or change any host. `--check` does not access keys or the credential store. `credential_node_mismatch`, `credential_server_mismatch`, and `credential_runtime_unsupported` identify incompatible Node paths, server paths, and OS-store support respectively.
66
+
67
+ If only Claude failed, diagnose that host and review migration only when `launcher_migration_available` is returned:
68
+
69
+ ```sh
70
+ aptly set env OPENAI_API_KEY --client claude --check
71
+ aptly setup --client claude --migrate-launcher --dry-run
72
+ aptly setup --client claude --migrate-launcher
73
+ aptly set env OPENAI_API_KEY --client claude
74
+ ```
75
+
76
+ `--dry-run` makes no changes. Migration replaces the Node/server paths of a receipt-matching, Aptly-owned standard registration with the current build. It preserves environment settings, Hooks, policy and other hosts, accesses no credentials, and never executes the old launcher. Custom wrappers and missing or modified ownership records are rejected. Review such registrations locally; use the wrapper's secure environment or a separately reviewed standard registration. Do not edit or delete ownership records to bypass validation.
77
+
78
+ A successful Codex configuration needs no reset. Retry only the affected host and restart MCP. Codex still requires `/hooks` trust review. Changes after diagnosis stop with `concurrent_configuration_change`. If recovery observes another key or cannot read the current key, it leaves the store unchanged and reports `credential_rollback_failed`. Files and the OS store do not form an atomic transaction; avoid concurrent configuration operations.
79
+
25
80
  ### Keys and restarting
26
81
 
27
- `OPENAI_API_KEY` is the classification key. It is separate from the existing login or subscription authentication used to run subagent models. **Inject it into the MCP process's environment** through your credential store or a secure execution environment. Do not put keys in command arguments, tool arguments, request JSON, MCP configuration JSON, source files, logs, or temporary files. No particular credential store is required.
82
+ Pass the classification key, `OPENAI_API_KEY`, through a secure environment or credential launcher to the MCP process. It is separate from host subscription login. Never write key values into command arguments, requests, configuration files, source, logs, or temporary files.
28
83
 
29
- Start a new host process or restart the MCP server to load registration and environment changes. Changing environment variables in another terminal does not update an already-running GUI or MCP process. **Codex requires you to review and trust the exact new Hook definition through `/hooks`.** The installer does not bypass trust review.
84
+ ### MCP environment setup, step by step
30
85
 
31
- Missing keys and connection, authentication, or timeout errors leave the original call unchanged. Direct Decisions calls require the following environment.
86
+ Run `setup`, then merge the examples below into the existing `aptly` registration. Preserve `command`, `args`, credential launchers, `ROUTER_LEDGER_PATH`, and `APTLY_HOOK_POLICY_PATH`. Setup creates the private ledger/policy directories; a manual ledger path must be absolute and inside a user-owned directory with mode 700.
32
87
 
33
- | Environment variable | Purpose |
88
+ #### Configuration file locations
89
+
90
+ | Host | User registration used by Aptly |
34
91
  |---|---|
35
- | `OPENAI_API_KEY` | Classification key injected through a secure execution environment |
36
- | `ROUTER_PROVIDER=decisions` | Uses the actual classification API |
37
- | `ROUTER_LEDGER_PATH` | Absolute ledger path inside a user-owned private directory with mode 700 |
92
+ | Codex | `$CODEX_HOME/config.toml`; `~/.codex/config.toml` when `CODEX_HOME` is unset |
93
+ | Claude Code | `mcpServers.aptly` in `~/.claude.json`; `.claude.json` inside `CLAUDE_CONFIG_DIR` when that directory is configured |
94
+
95
+ Claude Code stores Hooks in `~/.claude/settings.json` by default, or `$CLAUDE_CONFIG_DIR/settings.json` when `CLAUDE_CONFIG_DIR` is set. This is separate from MCP registration. If project `.mcp.json`, managed settings, or account-specific directories are in use, check the active entry in `/mcp` before editing or adding a registration.
96
+
97
+ #### Passing the API key
98
+
99
+ For a direct launch, enter the key in **Bash** without displaying or saving its value, then start the host from that shell. In zsh, run `bash` first:
100
+
101
+ ```sh
102
+ read -rsp 'OpenAI API key: ' OPENAI_API_KEY
103
+ printf '\n'
104
+ export OPENAI_API_KEY
105
+ codex
106
+ ```
107
+
108
+ Replace `codex` with `claude` for Claude Code. This does not save the key permanently. A running GUI does not inherit another terminal’s environment; use its secure environment mechanism or preserve the existing credential launcher.
109
+
110
+ #### Codex example
111
+
112
+ Edit the existing `[mcp_servers.aptly]` tables; do not append duplicate tables. Add `OPENAI_API_KEY` to `env_vars` without removing existing entries. `env_vars` forwards named host variables; `.env` supplies fixed MCP values.
113
+
114
+ ```toml
115
+ [mcp_servers.aptly]
116
+ env_vars = ["OPENAI_API_KEY"]
117
+
118
+ [mcp_servers.aptly.env]
119
+ ROUTER_PROVIDER = "decisions"
120
+ APTLY_CLAUDE_MODELS = '["haiku","sonnet","opus"]'
121
+ APTLY_CODEX_MODELS = '["gpt-6-luna","gpt-6.1-terra","gpt-6.1-sol"]'
122
+ ```
123
+
124
+ #### Claude Code example
125
+
126
+ Merge these values into the existing `mcpServers.aptly.env` object:
127
+
128
+ ```json
129
+ {
130
+ "ROUTER_PROVIDER": "decisions",
131
+ "APTLY_CLAUDE_MODELS": "[\"haiku\",\"sonnet\",\"opus\"]",
132
+ "APTLY_CODEX_MODELS": "[\"gpt-6-luna\",\"gpt-6.1-terra\",\"gpt-6.1-sol\"]"
133
+ }
134
+ ```
135
+
136
+ For direct launches, this optional reference forwards a key already present in the Claude Code environment. Do not add it to a credential-launcher setup. If the variable is missing, the key will not be supplied and the reference may remain unexpanded:
137
+
138
+ ```json
139
+ {
140
+ "OPENAI_API_KEY": "${OPENAI_API_KEY}"
141
+ }
142
+ ```
143
+
144
+ Model candidates are **JSON strings**: leave them unset for defaults or use `[]` to disable that host’s recommendations. Aptly reads the MCP startup environment and does not load project `.env` files.
145
+
146
+ Restart the host or MCP server after changes. **In Codex, review and trust the new Hook through `/hooks`.** Check `/mcp` and `get_routing_capabilities` for `provider`, candidates, and `automatic_hook.mode`. This free query does not authenticate the key; `recommend_agent` may incur a charge. For `unauthorized`, check key forwarding; for `needs_context`, check the task summary.
147
+
148
+ See the official [Codex configuration reference](https://learn.chatgpt.com/docs/config-file/config-reference) and [Claude Code MCP guide](https://code.claude.com/docs/en/mcp) for configuration locations and environment forwarding.
38
149
 
39
150
  ### Skipping registration and removing it
40
151
 
41
152
  ```sh
42
- APTLY_SKIP_SETUP=1 npm install --prefix "$aptly_install" /absolute/path/aptly-0.1.0.tgz
153
+ APTLY_SKIP_SETUP=1 npm install -g aptly@latest
43
154
  aptly setup --client codex --mode defaults
44
155
  aptly remove --client all
45
156
  ```
46
157
 
47
- A `CI` environment or npm `--ignore-scripts` also skips automatic registration. You can register explicitly with `setup`. The `advisory` mode passes recommendations without changing arguments. To change policy, remove the existing registration, then register again with the desired `--mode`. For a `partial` result, check the error codes for each client. Removal only targets entries that match Aptly's ownership records; it preserves user changes and reports conflicts.
158
+ `CI` or npm `--ignore-scripts` also skips registration. Use `setup` to register later. To change mode, remove the registration and run setup with the desired `--mode`; `advisory` only supplies advice. For `partial`, check each host’s error codes. Removal preserves user-modified entries and reports conflicts.
48
159
 
49
160
  ## Automatic Hook
50
161
 
@@ -66,6 +177,20 @@ The CLI without additional configuration, and existing manual configurations, de
66
177
  | `get_routing_precedence` | Explains preserved choices and unverified configuration sources for a call |
67
178
  | `route_spawn_hook` | Selects defaults for eligible calls or passes advice only, according to the installed policy |
68
179
 
180
+ ### Outbound data and disabling automatic sharing
181
+
182
+ For automatic classification, `summary` is an **excerpt assembled from an accepted task label and the clipped first paragraph** of the body. It is not a semantic rewrite or anonymization. Its UTF-8 size is at most `min(4000, max_summary_bytes)`; this is not a limit on the whole HTTP request. The opaque-message fallback in Codex uses only a sufficiently descriptive `task_name`, without decoding or transmitting that body. Labels can also contain sensitive information.
183
+
184
+ Decisions receives `client`, `task.summary`, `task.operation`, `task.context_sufficient`, candidate profile IDs, model/effort descriptions, and configured model facts. The classification API key is sent in `Authorization`; the request ID is sent in `X-Client-Request-Id`. Automatic IDs are hashes; direct recommendation IDs are caller-supplied. Do not put secrets in IDs or candidate descriptions.
185
+
186
+ To disable automatic sharing, change **only `enabled` to `false`** in the existing private policy file referenced by `APTLY_HOOK_POLICY_PATH`. Preserve the other fields and ownership record. Subsequent automatic Hook calls preserve the original call before classification or creation of a call-binding file. This cannot undo a transmission already started or recall information received by the server. Editing a generated policy can cause ownership conflicts in setup, configuration, or removal commands; those commands do not overwrite the disabled policy. Do not edit ownership records to bypass the checks.
187
+
188
+ `mode: advisory` still transmits classification data; it is not a privacy opt-out. Removing `APTLY_HOOK_POLICY_PATH` can activate the older strict path, so do not use removal as an alternative. `enabled: false` does not block explicit `recommend_agent` or `aptly recommend` requests.
189
+
190
+ For control over the shared summary, disable automatic sharing and write only the information needed for a direct recommendation. Aptly does not automatically fetch documents at URLs or attach source files mentioned in the summary. Checks for known key and Bearer patterns do not provide complete secret detection or establish that text is safe to transmit.
191
+
192
+ The ledger and Hook bindings store request metadata, hashes, and cached results, not task excerpts. Aptly diagnostics also exclude raw task text. The local Hook's `updatedInput` retains the original task so the host can continue execution. Aptly does not control the host's own transcript or log retention.
193
+
69
194
  ## Direct recommendations
70
195
 
71
196
  The `recommend_agent` MCP tool and CLI `recommend` accept a task summary and recommend a profile. They do not spawn subagents. Pass this JSON as MCP tool arguments, or save it to a file and provide it to the CLI through standard input.
@@ -164,6 +289,14 @@ These are USD conversions of measured native tokens at the [official Standard AP
164
289
 
165
290
  The measured workflow was **explicit MCP recommendation → native execution with the recommended profile passed as arguments**. It did not test automatic Hook URL retrieval or forwarding. The prior benchmark was reused as the control, with three samples per condition. Full-document reads, matching source quotes, and output structure were checked, but the results do not establish equal quality, statistical significance, or savings for other tasks.
166
291
 
292
+ ### Interrupted processes and ledger locks
293
+
294
+ This locking design requires Aptly `0.1.2` or later and is not included in npm `0.1.1`. The JSON ledger keeps its request IDs, `unknown` records, completed cache, call counts, and reserved costs. Only persistent ledgers use Node.js 24's built-in SQLite. The adjacent `ledger.json.lock` remains after a normal exit; its presence alone does not mean an active lock. With the new design, process exit releases the OS lock so a later request can use the same ledger. It does not retry an existing uncertain ID or refund reserved costs.
295
+
296
+ Keep the ledger and lock in a private directory on a local filesystem that supports hard links, and use one canonical path for each ledger. Network shares, cloud-synced directories, and path aliases are outside the supported scope. Do not delete or replace the new lock or SQLite journal, or open them with another database tool while in use. Age and PID alone do not establish that an owner has exited.
297
+
298
+ An old empty `.lock` cannot establish whether its owner is still alive, so it is never initialized or removed automatically. Stop every relevant host, MCP process, and ledger-accessing process, including older versions, and upgrade all writers. Only after confirming that the file belongs to the legacy locking design may you quarantine that file and restart against the same JSON ledger path. Do not reset or delete the ledger, `unknown` records, costs, or completed results. If ownership or format is uncertain, leave access blocked and investigate.
299
+
167
300
  ## Diagnostics and development
168
301
 
169
302
  ```sh
package/docs/guide/ja.md CHANGED
@@ -4,47 +4,158 @@
4
4
 
5
5
  [README に戻る](../../README.ja.md)
6
6
 
7
- 韓国語版が正本です。このガイドは 0.1.0 ローカルパッケージ案を対象とします。npm registry で公開済みであることや、すべてのアカウントでモデルにアクセスできることを意味しません。子エージェントの実行・承認・サンドボックス・MCP 権限は、Claude Code または Codex が管理します。
7
+ 韓国語版が正本です。このガイドでは Aptly の基本的なインストール方法と使い方を説明します。パッケージをインストールできることは、すべてのアカウントでモデルにアクセスできることを意味しません。子エージェントの実行・承認・サンドボックス・MCP 権限は、Claude Code または Codex が管理します。
8
8
 
9
9
  ## インストール
10
10
 
11
- Node.js 24 以上と Claude Code または Codex が必要です。確認済みのアーカイブとインストール先を指定してください。
11
+ Node.js 24 以上と Claude Code または Codex が必要です。npm の最新安定版をグローバルにインストールするには、次のコマンドを実行してください:
12
+
13
+ **既存の永続台帳を 0.1.2 以降に更新する前に**、その台帳にアクセスする可能性があるすべてのホスト、MCP サーバー、単独 CLI、旧版プロセスを停止してください。新しい永続ロックは旧版の書き込み側と併用できません。新規インストールでは旧ロックの隔離は不要です。旧版の空のロックが残る場合は、[台帳の移行手順](#プロセス中断と台帳ロック)に従い、確認済みの旧ロックだけをオフラインで隔離し、JSON 台帳、`unknown` 記録、費用、完了済みキャッシュを保持してください。
12
14
 
13
15
  ```sh
14
- aptly_install=/absolute/aptly-local
15
- npm install --prefix "$aptly_install" /absolute/path/aptly-0.1.0.tgz
16
- "$aptly_install/node_modules/.bin/aptly" setup --client all --mode defaults
16
+ npm install -g aptly@latest
17
+ aptly setup --client all --mode defaults
18
+ aptly --version
17
19
  ```
18
20
 
19
- 以降の例の `aptly` は、上記のインストール先にある実行ファイルを指します。PATH に追加していない場合はフルパスで実行してください。インストール済みの `node_modules` 内で TypeScript ソースを直接実行しないでください。
21
+ `aptly --version` でインストール済みのバージョンを確認してください。Node.js 環境を切り替えると再インストールが必要な場合があります。
20
22
 
21
23
  `postinstall` は、現在のユーザーの既存のホスト設定ディレクトリに MCP と `PreToolUse` Hook を登録します。`CODEX_HOME`・`CLAUDE_CONFIG_DIR` を使う場合は、インストールプロセスにも同じ環境を渡してください。まだ存在しないホストディレクトリは自動登録ではスキップし、明示的な `setup` で作成できます。必要なホストだけを登録するには、`--client codex` または `--client claude` を使用してください。
22
24
 
23
25
  新規登録では `ROUTER_PROVIDER=decisions`、Hook の `defaults` ポリシー、ホストごとの非公開台帳を指定します。他の既存設定・Hook と、既存の Aptly のコマンド・環境・provider・台帳パスは保持します。既存の fixture 設定を自動で有料モードに切り替えることはありません。
24
26
 
27
+ ### CLI による設定
28
+
29
+ `set` コマンドは 0.1.1 以降で利用できます。既存の起動パスは自動で切り替えません。API キー設定は、現在のパッケージの標準 Node/サーバー登録で、OS 資格情報の参照に対応する実行環境だと確認できた場合にのみ使用できます。独自ラッパー、旧バージョン、別のインストール先は `credential_launcher_unverified` で拒否します。登録を明示的に切り替えるかアップグレードしてから、再度実行してください。
30
+
31
+ ```sh
32
+ aptly set default
33
+ aptly set env APTLY_CODEX_MODELS
34
+ aptly set env OPENAI_API_KEY
35
+ ```
36
+
37
+ 最初のコマンドは Claude Code と Codex を登録し、未設定のデフォルト環境設定を追加します。既存の起動コマンド、環境変数の許可リスト、分類方式、独自の候補、advisory ポリシーは保持します。次のコマンドは入力を表示せずに値を尋ね、指定した変数だけを変更します。最後のコマンドは API キーを OS の資格情報ストアに保存し、選択したホストの起動時に読み込むよう設定します。値をコマンド引数に渡さないでください。
38
+
39
+ 既定では両ホストを選択します。`--client codex`、`--client claude`、`--client all` で対象を指定できます。Claude 候補には `["haiku","sonnet","opus"]` のような JSON 文字列を入力してください。
40
+
41
+ ```sh
42
+ aptly set env APTLY_CLAUDE_MODELS --client claude
43
+ ```
44
+
45
+ 自動化では `--stdin` を明示してください。以下は公開変数の例です。実際のキーを `printf` コマンドやシェル履歴に含めないでください。
46
+
47
+ ```sh
48
+ printf '%s\n' '["gpt-6.1-terra"]' | aptly set env APTLY_CODEX_MODELS --stdin
49
+ ```
50
+
51
+ 設定できる公開キーは `ROUTER_PROVIDER`(`fixture` または `decisions`)、`APTLY_CLAUDE_MODELS`、`APTLY_CODEX_MODELS`、`CLAUDE_MODELS`、`CODEX_MODELS`、`APTLY_MODEL_INFO_PATH` です。候補は対応モデルの JSON 配列文字列で、無効な値、重複、正式名と別名の競合は拒否します。`[]` は該当ホストの推薦を無効にします。モデルキャッシュには非公開ディレクトリ内の絶対パスが必要です。台帳や Hook ポリシーの管理パスはこのコマンドで変更できません。
52
+
53
+ API キーは macOS Keychain、インストール済みの Linux `secret-tool`、または Windows Credential Manager に保存します。Windows バックエンドは PowerShell と、現在のユーザー範囲の Win32 Generic 資格情報を使用し、UTF-8 データの上限は 2,560 バイトです。Windows CI では、合成キーの保存・読み取り・更新・削除、Unicode パス、分離したホスト設定、キー参照、ACL の保持を確認しました。Windows 上の実際の Claude Code・Codex セッションを実行して検証したものではありません。ストアを利用できない場合はエラーとなり、平文ファイルには切り替えません。キーはホスト別アカウントに分け、MCP ファイルには `APTLY_SECRET_BACKEND=os` と `APTLY_SECRET_CLIENT` の参照だけを保存します。OS ストアを選択した後、キーがない場合や読み取りに失敗した場合も、別の環境変数キーに黙って切り替えません。`set default` は既存のラッパーや環境による認証を保持し、`set env OPENAI_API_KEY` が OS ストア認証を明示的に選択します。
54
+
55
+ 結果にはホストごとの状態と固定エラーコードだけを返し、キーの値は表示しません。`partial` の場合は各ホストを確認してください。`credential_store_failed` ではストアへのアクセスや対応状況を、`credential_rollback_failed` では以前のキーを復元できたかを確認します。失敗したホストの旧キーは可能な場合に復元します。他のホストの成功を、全ホストでの原子的な成功とみなさないでください。
56
+
57
+ 保存のタイムアウトや応答解析の失敗は、OS が新しい値を書き込んだ後にも起こり得ます。失敗時は以前の値を復元し、以前の値がなければ新しい項目を削除して、再読み込みで確認します。復旧を確認できない場合は、元のエラーコードに `credential_rollback_failed` を追加します。どちらのキーが有効かを断定せず、再試行前に OS の資格情報ストアを確認してください。これは可能な範囲での復旧であり、プロセスやホストをまたぐ原子的なトランザクションではありません。
58
+
59
+ 変更後は MCP を再起動し、`get_routing_capabilities` の `provider`、候補、`credential_status` を確認してください。`os_store` はキーを読み込めたことを示し、API 認証の成功を示すものではありません。デフォルト設定と公開変数の設定では、引き続き独自ラッパーを保持します。API キー設定は未確認の起動パスのホスト設定を変更せず、保存を試みていた場合は可能な限りそのホストの旧キーを復元します。既存のラッパーにも新しいキーが自動で適用されるとは想定しないでください。Codex の `/hooks` 信頼承認やホスト権限は迂回しません。
60
+
61
+ ### 起動パスのエラーからの復旧
62
+
63
+ 以下のオプションは Aptly 0.1.2 以降で利用でき、npm 0.1.1 には含まれません。ソースから試す場合は `npm ci --ignore-scripts` と `npm run build` を実行し、以下の `aptly` を `node bin/aptly.ts` に置き換えます。
64
+
65
+ キー設定は入力・保存前に対象ホストを診断します。いずれかが失敗すると入力を求めず、全ホストを変更しません。`--check` はキーや資格情報ストアにアクセスしません。`credential_node_mismatch`、`credential_server_mismatch`、`credential_runtime_unsupported` はそれぞれ Node パス、サーバーパス、OS ストア対応の不一致を示します。
66
+
67
+ Claude だけが失敗した場合は次の順序で診断し、`launcher_migration_available` の場合にのみ移行を検討してください。
68
+
69
+ ```sh
70
+ aptly set env OPENAI_API_KEY --client claude --check
71
+ aptly setup --client claude --migrate-launcher --dry-run
72
+ aptly setup --client claude --migrate-launcher
73
+ aptly set env OPENAI_API_KEY --client claude
74
+ ```
75
+
76
+ `--dry-run` は変更しません。移行は所有記録と一致する Aptly の標準登録に限り、Node・サーバーパスを現在のビルドへ更新します。環境設定・Hook・ポリシー・他のホストを保持し、資格情報へのアクセスや古い起動プログラムの実行は行いません。独自ラッパー、所有記録のない登録、変更済みの登録は拒否します。その場合は登録をローカルで確認し、ラッパーの安全な環境変数受け渡しを使うか、別途確認した標準登録へ切り替えてください。検証を回避するために所有記録を編集・削除しないでください。
77
+
78
+ 成功した Codex の再設定は不要です。失敗したホストだけを指定して再試行し、MCP を再起動してください。Codex では引き続き `/hooks` の信頼確認が必要です。診断後の設定変更は `concurrent_configuration_change` で中断します。復旧時に別のキーを検出した場合や現在値を読めない場合は上書きせず、`credential_rollback_failed` を返します。ファイルと OS ストアは原子的なトランザクションではないため、設定操作を同時に実行しないでください。
79
+
25
80
  ### キーと再起動
26
81
 
27
- `OPENAI_API_KEY` は分類用のキーです。子エージェントのモデルが使用する既存のログイン・サブスクリプション認証とは別のものです。ユーザーの認証情報ストアや安全な実行環境から、**MCP プロセスの環境変数として渡して**ください。キーをコマンドライン・ツール引数・リクエスト JSON・MCP 設定 JSON・ソース・ログ・一時ファイルに含めないでください。特定の認証情報ストアは必須ではありません。
82
+ 分類用の `OPENAI_API_KEY` は、安全な環境や資格情報ランチャーを通して MCP プロセスに渡してください。ホストのサブスクリプション認証とは別です。キーの値をコマンド引数・リクエスト・設定ファイル・ソース・ログ・一時ファイルに記録しないでください。
28
83
 
29
- ホストを新しく起動するか MCP サーバーを再起動して、登録と環境を読み込ませてください。別のターミナルで環境変数を変更するだけでは、実行中の GUI・MCP の環境は更新されません。**Codex では `/hooks` で新しい Hook の正確な定義を確認し、信頼を承認する必要があります。** インストーラーがこの承認を迂回することはありません。
84
+ ### MCP 環境変数の設定手順
30
85
 
31
- キーがない場合や、接続・認証・タイムアウトのエラーが発生した場合は、元の呼び出しの引数を変更しません。直接 Decisions を呼び出すには、次の環境が必要です。
86
+ `setup` 後、以下の例を既存の `aptly` 登録に統合してください。`command`、`args`、資格情報ランチャー、`ROUTER_LEDGER_PATH`、`APTLY_HOOK_POLICY_PATH` は保持します。Setup は非公開の台帳・ポリシーディレクトリを作成します。台帳を手動指定する場合は、ユーザー所有で権限 700 のディレクトリ内の絶対パスを使ってください。
32
87
 
33
- | 環境変数 | 用途 |
88
+ #### 設定ファイルの場所
89
+
90
+ | ホスト | Aptly が使用するユーザー登録の場所 |
34
91
  |---|---|
35
- | `OPENAI_API_KEY` | 安全な実行環境から注入する分類用キー |
36
- | `ROUTER_PROVIDER=decisions` | 実際の分類 API を使用 |
37
- | `ROUTER_LEDGER_PATH` | ユーザー所有・権限 700 の非公開ディレクトリ内にある台帳の絶対パス |
92
+ | Codex | `$CODEX_HOME/config.toml`。`CODEX_HOME` 未指定の場合は `~/.codex/config.toml` |
93
+ | Claude Code | `~/.claude.json` の `mcpServers.aptly`。`CLAUDE_CONFIG_DIR` 指定時は、そのディレクトリの `.claude.json` |
94
+
95
+ Claude Code の Hook ファイルは通常 `~/.claude/settings.json` ですが、`CLAUDE_CONFIG_DIR` を指定すると `$CLAUDE_CONFIG_DIR/settings.json` になります。MCP 登録とは別です。プロジェクトの `.mcp.json`、管理設定、アカウント別ディレクトリを使う場合は、編集・追加前に `/mcp` で有効な登録を確認してください。
96
+
97
+ #### API キーの渡し方
98
+
99
+ 直接起動する場合は、**Bash** でキーを画面やコマンド履歴に残さず入力し、同じシェルからホストを起動します。zsh では先に `bash` を実行してください:
100
+
101
+ ```sh
102
+ read -rsp 'OpenAI API key: ' OPENAI_API_KEY
103
+ printf '\n'
104
+ export OPENAI_API_KEY
105
+ codex
106
+ ```
107
+
108
+ Claude Code では `codex` を `claude` に置き換えます。キーは永続保存されません。起動済み GUI は別のターミナルの環境を継承しないため、アプリの安全な環境受け渡し機能か既存の資格情報ランチャーを使ってください。
109
+
110
+ #### Codex の例
111
+
112
+ 既存の `[mcp_servers.aptly]` テーブルを編集し、同じテーブルを重複追加しないでください。`env_vars` の既存項目を保持して `OPENAI_API_KEY` を追加します。`env_vars` はホストの環境変数を名前で転送し、`.env` は MCP に固定値を渡します。
113
+
114
+ ```toml
115
+ [mcp_servers.aptly]
116
+ env_vars = ["OPENAI_API_KEY"]
117
+
118
+ [mcp_servers.aptly.env]
119
+ ROUTER_PROVIDER = "decisions"
120
+ APTLY_CLAUDE_MODELS = '["haiku","sonnet","opus"]'
121
+ APTLY_CODEX_MODELS = '["gpt-6-luna","gpt-6.1-terra","gpt-6.1-sol"]'
122
+ ```
123
+
124
+ #### Claude Code の例
125
+
126
+ 既存の `mcpServers.aptly.env` オブジェクトに以下を統合してください:
127
+
128
+ ```json
129
+ {
130
+ "ROUTER_PROVIDER": "decisions",
131
+ "APTLY_CLAUDE_MODELS": "[\"haiku\",\"sonnet\",\"opus\"]",
132
+ "APTLY_CODEX_MODELS": "[\"gpt-6-luna\",\"gpt-6.1-terra\",\"gpt-6.1-sol\"]"
133
+ }
134
+ ```
135
+
136
+ 直接起動では、次の任意の参照で Claude Code の環境にあるキーを渡せます。資格情報ランチャーを使う設定には追加しないでください。変数がない場合はキーが渡らず、参照文字列が未展開のまま残ることがあります:
137
+
138
+ ```json
139
+ {
140
+ "OPENAI_API_KEY": "${OPENAI_API_KEY}"
141
+ }
142
+ ```
143
+
144
+ モデル候補は **JSON 文字列**です。未設定ならデフォルトを使い、`[]` はそのホストの推薦を無効にします。Aptly は MCP 起動時の環境を読み、プロジェクトの `.env` ファイルは自動で読み込みません。
145
+
146
+ 変更後はホストまたは MCP を再起動してください。**Codex では `/hooks` で新しい Hook を確認して信頼を承認します。** `/mcp` と `get_routing_capabilities` で `provider`、候補、`automatic_hook.mode` を確認してください。この無料照会はキーの認証を検証せず、`recommend_agent` には料金が発生する場合があります。`unauthorized` ではキーの受け渡し、`needs_context` ではタスク要約を確認してください。
147
+
148
+ 設定場所と受け渡し方式の公式説明は、[Codex 構成リファレンス](https://learn.chatgpt.com/docs/config-file/config-reference)と [Claude Code MCP ガイド](https://code.claude.com/docs/en/mcp)を参照してください。
38
149
 
39
150
  ### スキップ・削除
40
151
 
41
152
  ```sh
42
- APTLY_SKIP_SETUP=1 npm install --prefix "$aptly_install" /absolute/path/aptly-0.1.0.tgz
153
+ APTLY_SKIP_SETUP=1 npm install -g aptly@latest
43
154
  aptly setup --client codex --mode defaults
44
155
  aptly remove --client all
45
156
  ```
46
157
 
47
- `CI` 環境や npm の `--ignore-scripts` でも自動登録をスキップします。直接 `setup` を実行して登録できます。`advisory` モードは推薦だけを伝え、引数は変更しません。ポリシーを変える場合は、既存の登録を削除した後、希望する `--mode` で再登録してください。結果が `partial` の場合は client ごとのエラーコードを確認してください。削除の対象は、登録時の所有情報と一致する項目だけです。ユーザーが変更した項目は保持し、競合があることを通知します。
158
+ `CI` や npm `--ignore-scripts` でも登録をスキップします。後から `setup` で登録できます。モード変更時は登録を削除して希望の `--mode` で再設定します。`advisory` は推薦のみを返します。`partial` ではホスト別のエラーコードを確認してください。削除時はユーザー変更済みの項目を保持し、競合を報告します。
48
159
 
49
160
  ## 自動 Hook
50
161
 
@@ -66,6 +177,20 @@ aptly remove --client all
66
177
  | `get_routing_precedence` | 呼び出しを保持する根拠と、未確認の設定元を説明します |
67
178
  | `route_spawn_hook` | インストールしたポリシーに応じて、対象呼び出しのデフォルト値を選択するか、推薦だけを伝えます |
68
179
 
180
+ ### 外部送信と自動共有の無効化
181
+
182
+ 自動分類の `summary` は、有効なタスクラベルと本文の**最初の段落を切り詰めて組み合わせた抜粋**です。意味を再要約したものでも、匿名化したものでもありません。UTF-8 のサイズは `min(4000, max_summary_bytes)` 以下ですが、HTTP リクエスト全体の上限ではありません。Codex の opaque メッセージへのフォールバックでは、本文を復号・送信せず、十分に具体的な `task_name` だけを使用します。ラベルにも機密情報が含まれる可能性があります。
183
+
184
+ Decisions には `client`、`task.summary`、`task.operation`、`task.context_sufficient` と、候補プロファイル ID・モデルと effort の説明・設定したモデル特性を送信します。分類用 API キーは `Authorization` ヘッダー、リクエスト ID は `X-Client-Request-Id` ヘッダーで送信します。自動経路の ID はハッシュで、直接推薦の ID は呼び出し元が指定します。ID や候補の説明にも秘密情報を含めないでください。
185
+
186
+ 自動共有を無効にするには、`APTLY_HOOK_POLICY_PATH` が指す既存の非公開ポリシーファイルで、**`enabled` だけを `false` に変更**してください。他のフィールドと所有権の記録は保持してください。その後の自動 Hook 呼び出しは、分類や呼び出しバインディングファイルの作成前に元の呼び出しを保持します。開始済みの送信を取り消したり、サーバーが受信した情報を回収したりすることはできません。生成済みのポリシーを変更すると、登録・設定・削除コマンドの所有権検査で競合する場合があり、それらのコマンドは無効化したポリシーを上書きしません。検査を回避するために所有権の記録を編集しないでください。
187
+
188
+ `mode: advisory` でも分類データは送信されるため、プライバシー上の共有停止にはなりません。`APTLY_HOOK_POLICY_PATH` を削除すると従来の strict 経路が有効になる場合があるため、共有停止の代わりにしないでください。`enabled: false` は、明示的な `recommend_agent` や `aptly recommend` のリクエストを止めるものではありません。
189
+
190
+ 共有する要約を自分で選ぶ場合は、自動共有を無効にしてから直接推薦に必要な情報だけを記述してください。Aptly は要約中の URL の文書や、言及されたソースファイルを自動取得・添付しません。既知のキーや Bearer のパターン検査は、完全な秘密情報の検出や送信の安全性を保証しません。
191
+
192
+ 台帳と Hook バインディングにはリクエストのメタデータ・ハッシュ・キャッシュ結果を保存し、タスクの抜粋は保存しません。Aptly の診断にもタスク原文を含めません。ただし、ローカルの Hook の `updatedInput` は、ホストが実行を続けられるよう元のタスクを保持します。ホスト自身の会話やログの保存は Aptly の制御対象ではありません。
193
+
69
194
  ## 直接推薦
70
195
 
71
196
  `recommend_agent` MCP と CLI の `recommend` は、タスク要約を受け取ってプロファイルを推薦します。子エージェントを直接生成することはありません。以下の JSON を MCP ツールの引数として渡すか、ファイルに保存して CLI の標準入力に渡してください。
@@ -164,6 +289,14 @@ Codex が子エージェントの `message` を暗号文として Hook に渡す
164
289
 
165
290
  測定経路は、**明示的な MCP 推薦 → 推薦プロファイルを引数として渡したホスト上での実行**です。自動 Hook の URL 収集・伝達機能を検証したものではありません。基準群は直前の実測を再利用しており、条件ごとの標本は 3 回です。ドキュメント全体の読み取り・原文の引用一致・出力形式は確認しましたが、品質の同等性、統計的有意性、他のタスクでの費用削減を保証するものではありません。
166
291
 
292
+ ### プロセス中断と台帳ロック
293
+
294
+ このロック方式は Aptly `0.1.2` 以降で利用でき、npm `0.1.1` には含まれません。JSON 台帳のリクエスト ID・`unknown`・完了済みキャッシュ・呼び出し数・予約費用の形式は維持します。永続台帳だけが Node.js 24 内蔵の SQLite を使用します。隣接する `ledger.json.lock` は正常終了後も残るため、ファイルの存在だけではロックが使用中かどうかを判断できません。新方式ではプロセス終了時に OS のロックが解放され、後続のリクエストが同じ台帳を使用できます。結果が不明な既存 ID の再試行や予約費用の返却は行いません。
295
+
296
+ 台帳とロックは、ハードリンクに対応したローカルファイルシステムの非公開ディレクトリに置き、各台帳に同一の正規パスを使用してください。ネットワーク共有・クラウド同期ディレクトリ・パスの別名はサポート範囲外です。新しいロックや SQLite journal を削除・置換したり、使用中に他の DB ツールで開いたりしないでください。ファイルの古さや PID だけでは所有プロセスの終了を確認できません。
297
+
298
+ 旧版の空の `.lock` ファイルでは所有プロセスが生存しているかを確認できないため、自動で初期化・削除しません。旧版を含む関連ホスト・MCP・台帳にアクセスするすべてのプロセスを停止し、書き込み側をすべて新しい版に更新してください。旧方式のロックであると確認できた場合だけ、そのファイルを別途隔離し、同じ JSON 台帳パスで再開します。台帳・`unknown`・費用・完了結果を初期化・削除しないでください。所有者や形式を確認できない場合は、遮断状態を保って調査してください。
299
+
167
300
  ## 診断と開発
168
301
 
169
302
  ```sh