agent-folder-workspace 0.1.2__tar.gz → 0.1.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/PKG-INFO +33 -20
  2. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/README.md +32 -19
  3. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/docs/architecture.md +15 -6
  4. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/docs/formats.md +6 -0
  5. agent_folder_workspace-0.1.4/docs/superpowers/plans/2026-07-24-workspace-local-cache.md +300 -0
  6. agent_folder_workspace-0.1.4/docs/superpowers/specs/2026-07-24-workspace-local-cache-design.md +108 -0
  7. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/docs/windows.md +14 -7
  8. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/pyproject.toml +1 -1
  9. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/scripts/check_wheel.py +18 -1
  10. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/src/agent_folder_workspace/__init__.py +1 -1
  11. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/src/agent_folder_workspace/cache.py +35 -7
  12. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/src/agent_folder_workspace/installer.py +7 -6
  13. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/src/agent_folder_workspace/mcp_server.py +8 -3
  14. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/src/agent_folder_workspace/models.py +1 -0
  15. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/src/agent_folder_workspace/opencode/skill/SKILL.md +3 -1
  16. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/src/agent_folder_workspace/registry.py +11 -0
  17. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/src/agent_folder_workspace/schemas/WorkspaceHandleV1.schema.json +13 -0
  18. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/src/agent_folder_workspace/schemas/WorkspaceListV1.schema.json +13 -0
  19. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/src/agent_folder_workspace/workspace.py +21 -6
  20. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/src/agent_folder_workspace/workspace_manager.py +52 -5
  21. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/tests/test_alternative_backends.py +2 -0
  22. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/tests/test_contracts.py +2 -0
  23. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/tests/test_distribution.py +8 -2
  24. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/tests/test_mcp_cli_installer.py +50 -0
  25. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/tests/test_search_cache.py +64 -1
  26. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/tests/test_windows_reparse_security.py +2 -4
  27. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/tests/test_workspace_manager.py +67 -3
  28. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/.github/workflows/ci.yml +0 -0
  29. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/.gitignore +0 -0
  30. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/CONTRIBUTING.md +0 -0
  31. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/LICENSE +0 -0
  32. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/SECURITY.md +0 -0
  33. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/docs/acceptance.md +0 -0
  34. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/docs/superpowers/plans/2026-07-22-console-logging.md +0 -0
  35. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/docs/superpowers/plans/2026-07-23-runtime-workspace-selection.md +0 -0
  36. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/docs/superpowers/specs/2026-07-22-console-logging-design.md +0 -0
  37. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/docs/superpowers/specs/2026-07-23-runtime-workspace-selection-design.md +0 -0
  38. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/scripts/generate_json_schemas.py +0 -0
  39. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/scripts/generate_probe_fixtures.py +0 -0
  40. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/src/agent_folder_workspace/__main__.py +0 -0
  41. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/src/agent_folder_workspace/adapters/__init__.py +0 -0
  42. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/src/agent_folder_workspace/adapters/base.py +0 -0
  43. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/src/agent_folder_workspace/adapters/legacy_office.py +0 -0
  44. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/src/agent_folder_workspace/adapters/manager.py +0 -0
  45. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/src/agent_folder_workspace/adapters/odf.py +0 -0
  46. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/src/agent_folder_workspace/adapters/ooxml.py +0 -0
  47. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/src/agent_folder_workspace/adapters/pdf.py +0 -0
  48. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/src/agent_folder_workspace/adapters/sqlite.py +0 -0
  49. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/src/agent_folder_workspace/adapters/structured.py +0 -0
  50. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/src/agent_folder_workspace/adapters/text.py +0 -0
  51. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/src/agent_folder_workspace/alternative_backends.py +0 -0
  52. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/src/agent_folder_workspace/backend_conversion_worker.py +0 -0
  53. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/src/agent_folder_workspace/backend_probe_worker.py +0 -0
  54. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/src/agent_folder_workspace/backends.py +0 -0
  55. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/src/agent_folder_workspace/cfb.py +0 -0
  56. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/src/agent_folder_workspace/cli.py +0 -0
  57. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/src/agent_folder_workspace/config.py +0 -0
  58. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/src/agent_folder_workspace/console_logging.py +0 -0
  59. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/src/agent_folder_workspace/cursors.py +0 -0
  60. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/src/agent_folder_workspace/errors.py +0 -0
  61. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/src/agent_folder_workspace/ids.py +0 -0
  62. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/src/agent_folder_workspace/legacy.py +0 -0
  63. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/src/agent_folder_workspace/opencode/plugin/agent-folder-workspace.ts.tmpl +0 -0
  64. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/src/agent_folder_workspace/parser_workers.py +0 -0
  65. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/src/agent_folder_workspace/path_safety.py +0 -0
  66. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/src/agent_folder_workspace/probe_fixtures/probe.doc +0 -0
  67. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/src/agent_folder_workspace/probe_fixtures/probe.ppt +0 -0
  68. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/src/agent_folder_workspace/probe_fixtures/probe.xls +0 -0
  69. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/src/agent_folder_workspace/py.typed +0 -0
  70. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/src/agent_folder_workspace/schemas/CapabilityReportV1.schema.json +0 -0
  71. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/src/agent_folder_workspace/schemas/ContentPageV1.schema.json +0 -0
  72. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/src/agent_folder_workspace/schemas/DiagnosticV1.schema.json +0 -0
  73. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/src/agent_folder_workspace/schemas/NodePageV1.schema.json +0 -0
  74. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/src/agent_folder_workspace/schemas/NodeV1.schema.json +0 -0
  75. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/src/agent_folder_workspace/schemas/SearchPageV1.schema.json +0 -0
  76. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/src/agent_folder_workspace/schemas/WorkspaceCloseV1.schema.json +0 -0
  77. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/tests/__init__.py +0 -0
  78. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/tests/cfb_fixture.py +0 -0
  79. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/tests/test_backends.py +0 -0
  80. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/tests/test_cfb.py +0 -0
  81. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/tests/test_console_logging.py +0 -0
  82. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/tests/test_content_adapters.py +0 -0
  83. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/tests/test_legacy_adapters.py +0 -0
  84. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/tests/test_legacy_fuzz.py +0 -0
  85. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/tests/test_legacy_parsers.py +0 -0
  86. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/tests/test_mcp_pdf_survival.py +0 -0
  87. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/tests/test_odf_pdf_adapters.py +0 -0
  88. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/tests/test_ooxml_adapters.py +0 -0
  89. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/tests/test_package.py +0 -0
  90. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/tests/test_parser_workers.py +0 -0
  91. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/tests/test_structured_text.py +0 -0
  92. {agent_folder_workspace-0.1.2 → agent_folder_workspace-0.1.4}/tests/test_workspace_registry.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agent-folder-workspace
3
- Version: 0.1.2
3
+ Version: 0.1.4
4
4
  Summary: Offline, lazy, agent-oriented exploration of heterogeneous folders
5
5
  Author-email: Dark Light <darklight@noreply.com>
6
6
  License-Expression: Apache-2.0
@@ -37,7 +37,7 @@ hierarchies for agents. It discovers file metadata first and opens document
37
37
  bodies only when a caller expands or reads a node. The default parser path is
38
38
  local Python code: Microsoft Office and LibreOffice are not required.
39
39
 
40
- > **Project status:** `0.1.2` is an alpha contract. Read-only exploration and
40
+ > **Project status:** `0.1.4` is an alpha contract. Read-only exploration and
41
41
  > byte access are implemented; editing, rendering, OCR, and complete semantic
42
42
  > coverage of every supported container are not.
43
43
 
@@ -91,7 +91,7 @@ adapters, and caching:
91
91
  4. Parser workers block Python socket/DNS entry points before importing risky
92
92
  adapters and enforce one deadline across startup, queueing, IPC, and parsing.
93
93
  5. `SizedLru` retains bounded decoded pages in memory. `ContentIndex` stores an
94
- optional per-workspace SQLite search index in the application cache.
94
+ optional SQLite search index in each workspace's owned local cache.
95
95
  6. `WorkspaceManager` gives one FastMCP process a bounded registry of active and
96
96
  explicitly addressed workspace roots.
97
97
  7. The Python API, Typer CLI, and FastMCP stdio server share the same workspace
@@ -175,9 +175,15 @@ The server exposes exactly these tools:
175
175
  - `probe_backends`
176
176
 
177
177
  `open_workspace(path)` accepts an explicit absolute directory, returns an opaque,
178
- process-lifetime `workspace_id`, and makes that workspace active. Existing tools
179
- accept an optional `workspace_id`; omission uses the active workspace. Opening
180
- the same canonical root again reuses its ID. `list_workspaces`,
178
+ process-lifetime `workspace_id`, and makes that workspace active. The compatible
179
+ extended form `open_workspace(path, cache_directory=None)` accepts an optional
180
+ absolute cache directory for that document basis. Without it, MCP checks for and
181
+ reuses an existing owned cache at
182
+ `path/.agent-folder-workspace-cache`; a foreign directory with that name is
183
+ rejected. The selected path is returned as `workspace_info.cache_directory`.
184
+ Existing tools accept an optional `workspace_id`; omission uses the active
185
+ workspace. Opening the same canonical root again reuses its ID; attempting to
186
+ reopen it with a different cache returns an input error. `list_workspaces`,
181
187
  `activate_workspace`, and `close_workspace` manage the registry. One process
182
188
  holds at most eight workspaces and never evicts one silently.
183
189
 
@@ -221,7 +227,7 @@ folderws doctor --probe-office --json --timeout-seconds 10
221
227
  # Build the complete semantic content index
222
228
  folderws index --root DIR
223
229
 
224
- # Remove only the selected application cache tree
230
+ # Remove the current document directory's owned cache, or an explicit cache
225
231
  folderws clear-cache [--cache-dir PATH]
226
232
 
227
233
  # Materialize the bundled, managed OpenCode skill and plugin
@@ -236,9 +242,10 @@ two ownership-marked resources atomically and always refuses foreign files,
236
242
  symbolic links, and Windows reparse points. The compatibility `--force` flag
237
243
  never bypasses ownership checks.
238
244
 
239
- `clear-cache` removes only a non-link directory containing the package's cache
240
- ownership marker; an arbitrary directory supplied through `--cache-dir` is
241
- refused.
245
+ Without `--cache-dir`, `clear-cache` selects
246
+ `./.agent-folder-workspace-cache`. It removes only a non-link directory
247
+ containing the package's cache ownership marker; an arbitrary directory
248
+ supplied through `--cache-dir` is refused.
242
249
 
243
250
  ## Lazy loading and cache
244
251
 
@@ -270,13 +277,16 @@ Decoded `ContentPageV1` values use a byte-sized in-memory LRU capped by
270
277
  best-effort basis. `index_content()` performs a full supported semantic walk
271
278
  without retaining all decoded bodies in RAM.
272
279
 
273
- The persistent index is an application-owned SQLite database below the
274
- platform cache directory, separated by a hash of the workspace root. It uses
275
- FTS5 when available and a plain-table fallback otherwise. `max_disk_cache_bytes`
276
- is checked after read-through chunks and completed files; least-recently-used
277
- indexed files are evicted and SQLite WAL/database pages are compacted. Embedded
278
- and alternative views enforce the same application-cache budget at their own
279
- materialization boundaries.
280
+ The persistent index is an application-owned SQLite database below
281
+ `WORKSPACE_ROOT/.agent-folder-workspace-cache`, separated by a hash of the
282
+ workspace root. Existing owned caches are reused, while the cache subtree is
283
+ excluded from document discovery. `WorkspaceConfig.cache_directory` and MCP's
284
+ optional `cache_directory` argument can select another cache root explicitly.
285
+ The index uses FTS5 when available and a plain-table fallback otherwise.
286
+ `max_disk_cache_bytes` is checked after read-through chunks and completed
287
+ files; least-recently-used indexed files are evicted and SQLite WAL/database
288
+ pages are compacted. Embedded and alternative views enforce the same cache
289
+ budget at their own materialization boundaries.
280
290
  Read-through index failures do not make an otherwise successful content read
281
291
  fail. Embedded mounting, by contrast, requires a writable bounded cache. Use
282
292
  `folderws clear-cache` to remove an explicitly selected cache tree safely.
@@ -308,8 +318,8 @@ successfully discovered file has been indexed. The affected directory node is
308
318
  Optional conversion is performed by a timeout-bounded child Python process.
309
319
  The result is verified as OOXML and mounted through the normal OOXML adapter.
310
320
  Its cache key includes the backend, adapter version, and SHA-256 of the source,
311
- and both conversion scratch and the retained result stay below the
312
- `platformdirs` per-workspace application cache rather than `%TEMP%` or `/tmp`.
321
+ and both conversion scratch and the retained result stay below the selected
322
+ workspace cache rather than `%TEMP%` or `/tmp`.
313
323
  Microsoft automation disables macros, UI, events, external-link updates, and
314
324
  recent-file writes where the application API exposes those controls.
315
325
  LibreOffice uses a disposable cache-local profile, headless/safe-mode flags,
@@ -324,6 +334,9 @@ documents and does not execute document macros. Important controls include:
324
334
  - roots that are symbolic links or reparse points are rejected;
325
335
  - external roots are opened only by an explicit `open_workspace` call, are
326
336
  bounded to eight per process, and receive process-lifetime opaque IDs;
337
+ - workspace caches default to an owned `.agent-folder-workspace-cache` below
338
+ the selected root, are excluded from source discovery, and can be overridden
339
+ only with an explicit absolute MCP path;
327
340
  - child links are not followed by default, and resolved sources must remain
328
341
  under the selected root;
329
342
  - SQLite files are opened with `mode=ro` and `PRAGMA query_only=ON`;
@@ -410,7 +423,7 @@ The detailed coverage and per-format caveats are in
410
423
  rendering. A hard parent timeout cannot guarantee cleanup of a vendor process
411
424
  that is itself stuck below COM or LibreOffice process control.
412
425
  - The stated 100,000-node and warm p95 performance figures are design targets,
413
- not certified guarantees in `0.1.2`; CI covers correctness across Windows,
426
+ not certified guarantees in `0.1.4`; CI covers correctness across Windows,
414
427
  Linux, and macOS but does not emulate a specific Windows-x64 SSD workload.
415
428
 
416
429
  ## Development
@@ -5,7 +5,7 @@ hierarchies for agents. It discovers file metadata first and opens document
5
5
  bodies only when a caller expands or reads a node. The default parser path is
6
6
  local Python code: Microsoft Office and LibreOffice are not required.
7
7
 
8
- > **Project status:** `0.1.2` is an alpha contract. Read-only exploration and
8
+ > **Project status:** `0.1.4` is an alpha contract. Read-only exploration and
9
9
  > byte access are implemented; editing, rendering, OCR, and complete semantic
10
10
  > coverage of every supported container are not.
11
11
 
@@ -59,7 +59,7 @@ adapters, and caching:
59
59
  4. Parser workers block Python socket/DNS entry points before importing risky
60
60
  adapters and enforce one deadline across startup, queueing, IPC, and parsing.
61
61
  5. `SizedLru` retains bounded decoded pages in memory. `ContentIndex` stores an
62
- optional per-workspace SQLite search index in the application cache.
62
+ optional SQLite search index in each workspace's owned local cache.
63
63
  6. `WorkspaceManager` gives one FastMCP process a bounded registry of active and
64
64
  explicitly addressed workspace roots.
65
65
  7. The Python API, Typer CLI, and FastMCP stdio server share the same workspace
@@ -143,9 +143,15 @@ The server exposes exactly these tools:
143
143
  - `probe_backends`
144
144
 
145
145
  `open_workspace(path)` accepts an explicit absolute directory, returns an opaque,
146
- process-lifetime `workspace_id`, and makes that workspace active. Existing tools
147
- accept an optional `workspace_id`; omission uses the active workspace. Opening
148
- the same canonical root again reuses its ID. `list_workspaces`,
146
+ process-lifetime `workspace_id`, and makes that workspace active. The compatible
147
+ extended form `open_workspace(path, cache_directory=None)` accepts an optional
148
+ absolute cache directory for that document basis. Without it, MCP checks for and
149
+ reuses an existing owned cache at
150
+ `path/.agent-folder-workspace-cache`; a foreign directory with that name is
151
+ rejected. The selected path is returned as `workspace_info.cache_directory`.
152
+ Existing tools accept an optional `workspace_id`; omission uses the active
153
+ workspace. Opening the same canonical root again reuses its ID; attempting to
154
+ reopen it with a different cache returns an input error. `list_workspaces`,
149
155
  `activate_workspace`, and `close_workspace` manage the registry. One process
150
156
  holds at most eight workspaces and never evicts one silently.
151
157
 
@@ -189,7 +195,7 @@ folderws doctor --probe-office --json --timeout-seconds 10
189
195
  # Build the complete semantic content index
190
196
  folderws index --root DIR
191
197
 
192
- # Remove only the selected application cache tree
198
+ # Remove the current document directory's owned cache, or an explicit cache
193
199
  folderws clear-cache [--cache-dir PATH]
194
200
 
195
201
  # Materialize the bundled, managed OpenCode skill and plugin
@@ -204,9 +210,10 @@ two ownership-marked resources atomically and always refuses foreign files,
204
210
  symbolic links, and Windows reparse points. The compatibility `--force` flag
205
211
  never bypasses ownership checks.
206
212
 
207
- `clear-cache` removes only a non-link directory containing the package's cache
208
- ownership marker; an arbitrary directory supplied through `--cache-dir` is
209
- refused.
213
+ Without `--cache-dir`, `clear-cache` selects
214
+ `./.agent-folder-workspace-cache`. It removes only a non-link directory
215
+ containing the package's cache ownership marker; an arbitrary directory
216
+ supplied through `--cache-dir` is refused.
210
217
 
211
218
  ## Lazy loading and cache
212
219
 
@@ -238,13 +245,16 @@ Decoded `ContentPageV1` values use a byte-sized in-memory LRU capped by
238
245
  best-effort basis. `index_content()` performs a full supported semantic walk
239
246
  without retaining all decoded bodies in RAM.
240
247
 
241
- The persistent index is an application-owned SQLite database below the
242
- platform cache directory, separated by a hash of the workspace root. It uses
243
- FTS5 when available and a plain-table fallback otherwise. `max_disk_cache_bytes`
244
- is checked after read-through chunks and completed files; least-recently-used
245
- indexed files are evicted and SQLite WAL/database pages are compacted. Embedded
246
- and alternative views enforce the same application-cache budget at their own
247
- materialization boundaries.
248
+ The persistent index is an application-owned SQLite database below
249
+ `WORKSPACE_ROOT/.agent-folder-workspace-cache`, separated by a hash of the
250
+ workspace root. Existing owned caches are reused, while the cache subtree is
251
+ excluded from document discovery. `WorkspaceConfig.cache_directory` and MCP's
252
+ optional `cache_directory` argument can select another cache root explicitly.
253
+ The index uses FTS5 when available and a plain-table fallback otherwise.
254
+ `max_disk_cache_bytes` is checked after read-through chunks and completed
255
+ files; least-recently-used indexed files are evicted and SQLite WAL/database
256
+ pages are compacted. Embedded and alternative views enforce the same cache
257
+ budget at their own materialization boundaries.
248
258
  Read-through index failures do not make an otherwise successful content read
249
259
  fail. Embedded mounting, by contrast, requires a writable bounded cache. Use
250
260
  `folderws clear-cache` to remove an explicitly selected cache tree safely.
@@ -276,8 +286,8 @@ successfully discovered file has been indexed. The affected directory node is
276
286
  Optional conversion is performed by a timeout-bounded child Python process.
277
287
  The result is verified as OOXML and mounted through the normal OOXML adapter.
278
288
  Its cache key includes the backend, adapter version, and SHA-256 of the source,
279
- and both conversion scratch and the retained result stay below the
280
- `platformdirs` per-workspace application cache rather than `%TEMP%` or `/tmp`.
289
+ and both conversion scratch and the retained result stay below the selected
290
+ workspace cache rather than `%TEMP%` or `/tmp`.
281
291
  Microsoft automation disables macros, UI, events, external-link updates, and
282
292
  recent-file writes where the application API exposes those controls.
283
293
  LibreOffice uses a disposable cache-local profile, headless/safe-mode flags,
@@ -292,6 +302,9 @@ documents and does not execute document macros. Important controls include:
292
302
  - roots that are symbolic links or reparse points are rejected;
293
303
  - external roots are opened only by an explicit `open_workspace` call, are
294
304
  bounded to eight per process, and receive process-lifetime opaque IDs;
305
+ - workspace caches default to an owned `.agent-folder-workspace-cache` below
306
+ the selected root, are excluded from source discovery, and can be overridden
307
+ only with an explicit absolute MCP path;
295
308
  - child links are not followed by default, and resolved sources must remain
296
309
  under the selected root;
297
310
  - SQLite files are opened with `mode=ro` and `PRAGMA query_only=ON`;
@@ -378,7 +391,7 @@ The detailed coverage and per-format caveats are in
378
391
  rendering. A hard parent timeout cannot guarantee cleanup of a vendor process
379
392
  that is itself stuck below COM or LibreOffice process control.
380
393
  - The stated 100,000-node and warm p95 performance figures are design targets,
381
- not certified guarantees in `0.1.2`; CI covers correctness across Windows,
394
+ not certified guarantees in `0.1.4`; CI covers correctness across Windows,
382
395
  Linux, and macOS but does not emulate a specific Windows-x64 SSD workload.
383
396
 
384
397
  ## Development
@@ -18,7 +18,7 @@ flowchart LR
18
18
  Workspace --> Index["ContentIndex SQLite cache"]
19
19
  Registry --> Files["Selected local root"]
20
20
  Adapters --> Files
21
- Index --> Cache["Platform application cache"]
21
+ Index --> Cache["Workspace-local owned cache"]
22
22
  CLI["folderws doctor --probe-office --json"] --> Probe["Isolated backend probe worker"]
23
23
  Probe --> Apps["Office COM or LibreOffice"]
24
24
  ```
@@ -143,9 +143,13 @@ offset, limit, and representation. It estimates serialized page bytes and
143
143
  evicts least-recently-used values until `max_content_cache_bytes` is respected.
144
144
  The cache disappears with the workspace process or refresh.
145
145
 
146
- `ContentIndex` is created on first content-index/search use beneath the
147
- platform-specific `agent-folder-workspace` cache directory. Each root gets a
148
- directory named with a truncated SHA-256 of the normalized absolute root. The
146
+ `ContentIndex` is created on first content-index/search use beneath
147
+ `WORKSPACE_ROOT/.agent-folder-workspace-cache` by default. Each root gets a
148
+ directory named with a truncated SHA-256 of the normalized absolute root. An
149
+ existing owned cache is reused; a same-named directory without the ownership
150
+ marker is rejected, and `NodeRegistry` excludes the cache subtree from source
151
+ discovery. An explicit `WorkspaceConfig.cache_directory` or MCP
152
+ `open_workspace(..., cache_directory=...)` selects another cache root. The
149
153
  index records relative path, modification time, size, completion, content byte
150
154
  count, and last access. Synchronization removes entries whose source metadata
151
155
  changed.
@@ -159,7 +163,7 @@ filesystem inventory. If any directory scan failed, the unknown denominator
159
163
  forces `coverage=0` and `complete=false`; the partial directory node contains a
160
164
  `directory_scan_failed` diagnostic.
161
165
 
162
- After every read-through chunk and completed file, the physical application
166
+ After every read-through chunk and completed file, the physical workspace
163
167
  cache size is checked. Least-recently-used indexed files are evicted and the
164
168
  SQLite WAL/database is checkpointed and compacted. Embedded and optional
165
169
  alternative materializers separately check the same total cache root before
@@ -181,7 +185,7 @@ replaces a hash-derived target. The target is accepted only below a cache path
181
185
  whose relevant components are not symbolic links.
182
186
 
183
187
  `max_embedded_depth` and `max_embedded_files` bound recursive mounts per
184
- workspace. `max_disk_cache_bytes` accounts for the application cache before a
188
+ workspace. `max_disk_cache_bytes` accounts for the selected cache before a
185
189
  new materialization and evicts inactive derived files by modification time;
186
190
  materializations in active use are protected. The target retains only the
187
191
  embedded suffix, while node metadata records nested adapter path, depth, and
@@ -200,6 +204,11 @@ workspace ID and otherwise route to the active root. Its capability dispatcher
200
204
  resolves workspace methods at call time so reduced injected implementations
201
205
  return a structured `capability_unavailable` error.
202
206
 
207
+ `open_workspace` also accepts an optional absolute `cache_directory`. Omission
208
+ uses and validates the fixed workspace-local cache; an explicit path belongs
209
+ only to the new workspace. Reopening a live root with a conflicting cache is
210
+ rejected rather than migrating active indexes or materialized nodes.
211
+
203
212
  MCP tool boundaries translate public workspace errors to stable error codes and
204
213
  sanitize unexpected failures behind a correlation ID. This leaves the stdio
205
214
  transport usable for a later request. Risky PDF and container semantics remain
@@ -67,6 +67,12 @@ package/attachment roles, and mounted semantic content is not included in the
67
67
  outer format's default full-index branches. Cached copies can outlive a
68
68
  workspace instance until eviction or `folderws clear-cache`.
69
69
 
70
+ By default, indexes and derived copies live below
71
+ `WORKSPACE_ROOT/.agent-folder-workspace-cache`. The owned directory is excluded
72
+ from the document inventory, so split or converted artifacts do not reappear
73
+ as source files. `WorkspaceConfig.cache_directory` or MCP's optional absolute
74
+ `cache_directory` can select a different cache for one document basis.
75
+
70
76
  ## Resource controls
71
77
 
72
78
  Defaults from `WorkspaceConfig` that have active enforcement paths include:
@@ -0,0 +1,300 @@
1
+ # Workspace-Local Cache Implementation Plan
2
+
3
+ > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
4
+
5
+ **Goal:** Store split and derived document data in an owned cache beside each document basis by default and let MCP choose an absolute cache path per newly opened workspace.
6
+
7
+ **Architecture:** Resolve every real `FolderWorkspace` to one explicit cache root during open, defaulting to `WORKSPACE_ROOT/.agent-folder-workspace-cache`, and exclude that root from the source inventory. Extend `open_workspace` with one backward-compatible optional cache argument; validate and pass it through `WorkspaceManager`, while reporting the selected cache in `WorkspaceInfoV1`.
8
+
9
+ **Tech Stack:** Python 3.11-3.14, Pydantic v2, FastMCP, pytest, Ruff, strict Mypy, Hatchling/build, Twine.
10
+
11
+ ---
12
+
13
+ ### Task 1: Cache path resolution and source-inventory exclusion
14
+
15
+ **Files:**
16
+ - Modify: `src/agent_folder_workspace/cache.py`
17
+ - Modify: `src/agent_folder_workspace/workspace.py`
18
+ - Modify: `src/agent_folder_workspace/registry.py`
19
+ - Modify: `src/agent_folder_workspace/installer.py`
20
+ - Modify: `src/agent_folder_workspace/cli.py`
21
+ - Modify: `tests/test_search_cache.py`
22
+ - Modify: `tests/test_mcp_cli_installer.py`
23
+
24
+ - [ ] **Step 1: Write failing default, reuse, collision, and exclusion tests**
25
+
26
+ Add tests that open a real workspace and assert:
27
+
28
+ ```python
29
+ expected = root / ".agent-folder-workspace-cache"
30
+ assert workspace.config.cache_directory == expected
31
+ assert workspace.workspace_info().cache_directory == str(expected)
32
+ ```
33
+
34
+ Create an owned `expected` directory with `.agent-folder-workspace-cache-v1`, verify it is reused,
35
+ and create an unowned collision to verify `FolderWorkspace.open` raises `InputValidationError`.
36
+ After indexing, refresh and assert `.agent-folder-workspace-cache` is absent from the root node's
37
+ children. Add a CLI test proving `clear-cache` defaults to the selected root's local cache.
38
+
39
+ - [ ] **Step 2: Run the focused tests and verify RED**
40
+
41
+ Run:
42
+
43
+ ```console
44
+ .venv/bin/python -m pytest tests/test_search_cache.py tests/test_mcp_cli_installer.py -q
45
+ ```
46
+
47
+ Expected: failures because the current default uses `platformdirs`, `WorkspaceInfoV1` has no cache
48
+ field, and the registry inventories a cache nested below the workspace.
49
+
50
+ - [ ] **Step 3: Implement deterministic local cache resolution**
51
+
52
+ In `cache.py`, add:
53
+
54
+ ```python
55
+ DEFAULT_CACHE_DIRECTORY_NAME = ".agent-folder-workspace-cache"
56
+
57
+
58
+ def default_cache_directory(workspace_root: Path) -> Path:
59
+ return workspace_root / DEFAULT_CACHE_DIRECTORY_NAME
60
+
61
+
62
+ def validate_cache_directory(cache_root: Path, workspace_root: Path) -> Path:
63
+ ...
64
+ ```
65
+
66
+ The validator returns a normalized absolute path, rejects link/reparse components, rejects a cache
67
+ that equals or contains the workspace root, and requires the regular ownership marker when the
68
+ target already exists. Missing targets remain lazy.
69
+
70
+ Resolve `WorkspaceConfig.cache_directory` in `FolderWorkspace.open` before constructing the
71
+ workspace. In `NodeRegistry`, normalize the configured cache path once and skip the exact matching
72
+ entry during scans. Make `installer.default_cache_dir(root=Path("."))` return the same fixed local
73
+ path and let `clear-cache` accept that resolved default through the CLI.
74
+
75
+ - [ ] **Step 4: Run focused tests and verify GREEN**
76
+
77
+ Run:
78
+
79
+ ```console
80
+ .venv/bin/python -m pytest tests/test_search_cache.py tests/test_mcp_cli_installer.py -q
81
+ ```
82
+
83
+ Expected: all selected tests pass.
84
+
85
+ - [ ] **Step 5: Commit the cache slice**
86
+
87
+ Stage the listed source and test files and commit through the repository commit policy with title
88
+ `Store document caches beside sources`.
89
+
90
+ ### Task 2: Per-workspace MCP cache selection
91
+
92
+ **Files:**
93
+ - Modify: `src/agent_folder_workspace/models.py`
94
+ - Modify: `src/agent_folder_workspace/workspace_manager.py`
95
+ - Modify: `src/agent_folder_workspace/mcp_server.py`
96
+ - Modify: `src/agent_folder_workspace/__init__.py`
97
+ - Modify: `src/agent_folder_workspace/schemas/WorkspaceInfoV1.schema.json`
98
+ - Modify: `src/agent_folder_workspace/schemas/WorkspaceHandleV1.schema.json`
99
+ - Modify: `src/agent_folder_workspace/schemas/WorkspaceListV1.schema.json`
100
+ - Modify: `tests/test_workspace_manager.py`
101
+ - Modify: `tests/test_mcp_cli_installer.py`
102
+ - Modify: `tests/test_contracts.py`
103
+
104
+ - [ ] **Step 1: Write failing manager and MCP contract tests**
105
+
106
+ Extend the fake factory to accept an optional `WorkspaceConfig` and record it. Prove:
107
+
108
+ ```python
109
+ opened = manager.open_workspace(documents, cache_directory=cache)
110
+ assert factory.configs[-1].cache_directory == cache
111
+ ```
112
+
113
+ Add failures for relative cache paths and for reopening one root with a different cache. Assert the
114
+ active workspace remains unchanged after failure. Inspect the FastMCP schema and assert
115
+ `cache_directory` is optional on `open_workspace`, then call it and verify the returned
116
+ `workspace_info.cache_directory`.
117
+
118
+ - [ ] **Step 2: Run manager and MCP tests and verify RED**
119
+
120
+ Run:
121
+
122
+ ```console
123
+ .venv/bin/python -m pytest tests/test_workspace_manager.py tests/test_mcp_cli_installer.py -q
124
+ ```
125
+
126
+ Expected: failures because `open_workspace` does not accept or route `cache_directory`.
127
+
128
+ - [ ] **Step 3: Implement the optional cache argument**
129
+
130
+ Change the manager method to:
131
+
132
+ ```python
133
+ def open_workspace(
134
+ self,
135
+ path: str | Path,
136
+ cache_directory: str | Path | None = None,
137
+ ) -> WorkspaceHandleV1:
138
+ ...
139
+ ```
140
+
141
+ Require an explicit cache to be absolute. When present, call the factory with
142
+ `WorkspaceConfig(cache_directory=requested_cache)`; otherwise retain the single-argument factory
143
+ call for compatibility. Compare the selected cache when the root is already open and reject
144
+ conflicts with `InputValidationError`.
145
+
146
+ Change the FastMCP handler to:
147
+
148
+ ```python
149
+ @server.tool(name="open_workspace")
150
+ def open_workspace(path: str, cache_directory: str | None = None) -> object:
151
+ return _call_safely(
152
+ lambda: workspace_manager.open_workspace(path, cache_directory=cache_directory)
153
+ )
154
+ ```
155
+
156
+ Add `cache_directory: str | None = None` to `WorkspaceInfoV1` and populate it for real workspaces.
157
+ Regenerate JSON schemas with `scripts/generate_json_schemas.py`.
158
+
159
+ - [ ] **Step 4: Run focused tests and schema checks**
160
+
161
+ Run:
162
+
163
+ ```console
164
+ .venv/bin/python -m pytest tests/test_workspace_manager.py tests/test_mcp_cli_installer.py tests/test_contracts.py -q
165
+ .venv/bin/python scripts/generate_json_schemas.py --check
166
+ ```
167
+
168
+ Expected: all selected tests pass and all committed schemas are current.
169
+
170
+ - [ ] **Step 5: Commit the MCP slice**
171
+
172
+ Stage the listed source, schema, and test files and commit through the repository commit policy
173
+ with title `Select cache paths per MCP workspace`.
174
+
175
+ ### Task 3: Documentation, version, and package assertions
176
+
177
+ **Files:**
178
+ - Modify: `README.md`
179
+ - Modify: `docs/architecture.md`
180
+ - Modify: `docs/formats.md`
181
+ - Modify: `docs/windows.md`
182
+ - Modify: `src/agent_folder_workspace/opencode/skill/SKILL.md`
183
+ - Modify: `pyproject.toml`
184
+ - Modify: `src/agent_folder_workspace/__init__.py`
185
+ - Modify: `scripts/check_wheel.py`
186
+ - Modify: `tests/test_distribution.py`
187
+
188
+ - [ ] **Step 1: Write failing distribution assertions for version 0.1.3 and cache docs**
189
+
190
+ Update distribution tests to require version `0.1.3` and the terms:
191
+
192
+ ```python
193
+ for term in (
194
+ ".agent-folder-workspace-cache",
195
+ "cache_directory",
196
+ "existing owned cache",
197
+ ):
198
+ assert term in metadata.get_payload()
199
+ ```
200
+
201
+ Update `scripts/check_wheel.py` to expect `0.1.3` and all currently exported schemas.
202
+
203
+ - [ ] **Step 2: Run the focused distribution tests and verify RED**
204
+
205
+ Run:
206
+
207
+ ```console
208
+ .venv/bin/python -m pytest tests/test_distribution.py -q
209
+ ```
210
+
211
+ Expected: failures because metadata, version, and documentation still describe `0.1.2` and the
212
+ platform cache.
213
+
214
+ - [ ] **Step 3: Update user and architecture documentation**
215
+
216
+ Document the local default, ownership-based reuse, scanner exclusion, optional absolute MCP cache
217
+ path, conflict behavior, and `clear-cache` default. Update the bundled OpenCode skill to pass
218
+ `cache_directory` only when the user explicitly supplies or approves it.
219
+
220
+ Set both package versions to `0.1.3`:
221
+
222
+ ```toml
223
+ version = "0.1.3"
224
+ ```
225
+
226
+ ```python
227
+ __version__ = "0.1.3"
228
+ ```
229
+
230
+ - [ ] **Step 4: Run the focused distribution tests and verify GREEN**
231
+
232
+ Run:
233
+
234
+ ```console
235
+ .venv/bin/python -m pytest tests/test_distribution.py -q
236
+ ```
237
+
238
+ Expected: all distribution tests pass.
239
+
240
+ - [ ] **Step 5: Commit the release slice**
241
+
242
+ Stage the documentation, version, script, and test files and commit through the repository commit
243
+ policy with title `Prepare agent folder workspace 0.1.3`.
244
+
245
+ ### Task 4: Full verification and PyPI-only release
246
+
247
+ **Files:**
248
+ - Verify: entire repository
249
+ - Build: temporary clean output outside tracked `dist/`
250
+
251
+ - [ ] **Step 1: Run all quality gates**
252
+
253
+ Run:
254
+
255
+ ```console
256
+ .venv/bin/python -m pytest -q
257
+ .venv/bin/ruff format --check .
258
+ .venv/bin/ruff check --no-cache .
259
+ .venv/bin/mypy src
260
+ .venv/bin/python scripts/generate_json_schemas.py --check
261
+ .venv/bin/python scripts/check_wheel.py --reproducible --install-smoke
262
+ git diff --check
263
+ ```
264
+
265
+ Expected: every command exits zero.
266
+
267
+ - [ ] **Step 2: Build and inspect clean release artifacts**
268
+
269
+ Build wheel and sdist into a new temporary directory:
270
+
271
+ ```console
272
+ SOURCE_DATE_EPOCH=315532800 .venv/bin/python -m build --no-isolation --outdir <temporary>
273
+ .venv/bin/python -m twine check <temporary>/*
274
+ .venv/bin/python scripts/check_wheel.py <temporary> --install-smoke
275
+ ```
276
+
277
+ Expected: one `0.1.3` wheel and one `0.1.3` sdist, valid metadata, successful isolated import and
278
+ CLI/MCP smoke checks.
279
+
280
+ - [ ] **Step 3: Commit any deterministic generated correction and tag**
281
+
282
+ Require a clean worktree, create annotated tag `v0.1.3`, and verify the tag points at the release
283
+ commit. Do not create any GitHub release or publish to another registry.
284
+
285
+ - [ ] **Step 4: Upload only to production PyPI**
286
+
287
+ Use the environment's configured production PyPI credentials:
288
+
289
+ ```console
290
+ .venv/bin/python -m twine upload --repository-url https://upload.pypi.org/legacy/ <temporary>/*
291
+ ```
292
+
293
+ Expected: PyPI accepts both `agent_folder_workspace-0.1.3` artifacts.
294
+
295
+ - [ ] **Step 5: Verify the public release**
296
+
297
+ Query PyPI's JSON endpoint and download the published wheel into a fresh temporary directory.
298
+ Verify version `0.1.3`, SHA-256 against the locally uploaded wheel, package import,
299
+ `agent_folder_workspace.__version__`, MCP tool schema, and the local-cache behavior. Confirm no
300
+ TestPyPI or other publication was performed.