apcore-cli 0.7.0__tar.gz → 0.9.0__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 (135) hide show
  1. apcore_cli-0.9.0/CHANGELOG.md +501 -0
  2. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/CLAUDE.md +13 -5
  3. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/PKG-INFO +65 -25
  4. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/README.md +62 -21
  5. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/examples/README.md +38 -54
  6. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/pyproject.toml +7 -6
  7. apcore_cli-0.9.0/src/apcore_cli/__init__.py +212 -0
  8. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/src/apcore_cli/__main__.py +6 -0
  9. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/src/apcore_cli/approval.py +52 -2
  10. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/src/apcore_cli/builtin_group.py +101 -12
  11. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/src/apcore_cli/cli.py +59 -17
  12. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/src/apcore_cli/config.py +31 -0
  13. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/src/apcore_cli/discovery.py +40 -11
  14. apcore_cli-0.9.0/src/apcore_cli/exit_codes.py +74 -0
  15. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/src/apcore_cli/factory.py +87 -115
  16. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/src/apcore_cli/init_cmd.py +1 -1
  17. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/src/apcore_cli/output.py +82 -31
  18. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/src/apcore_cli/ref_resolver.py +36 -6
  19. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/src/apcore_cli/schema_parser.py +10 -4
  20. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/src/apcore_cli/security/audit.py +17 -3
  21. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/src/apcore_cli/security/config_encryptor.py +41 -14
  22. apcore_cli-0.9.0/src/apcore_cli/security/sandbox.py +271 -0
  23. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/src/apcore_cli/shell.py +7 -7
  24. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/src/apcore_cli/strategy.py +6 -1
  25. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/src/apcore_cli/system_cmd.py +52 -25
  26. apcore_cli-0.9.0/src/apcore_cli/system_usage.py +167 -0
  27. apcore_cli-0.9.0/tests/conformance/test_snake_case_kwargs.py +82 -0
  28. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/tests/test_apcli_integration.py +21 -24
  29. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/tests/test_approval.py +21 -11
  30. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/tests/test_bugfixes.py +1 -0
  31. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/tests/test_builtin_group.py +87 -0
  32. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/tests/test_cli.py +98 -16
  33. apcore_cli-0.9.0/tests/test_exit_codes.py +58 -0
  34. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/tests/test_factory_fe13.py +43 -22
  35. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/tests/test_integration.py +5 -3
  36. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/tests/test_list_command_filters.py +14 -5
  37. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/tests/test_output_format_exec.py +31 -3
  38. apcore_cli-0.9.0/tests/test_output_format_markdown_skill.py +126 -0
  39. apcore_cli-0.9.0/tests/test_public_api.py +92 -0
  40. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/tests/test_ref_resolver.py +130 -0
  41. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/tests/test_schema_parser.py +8 -5
  42. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/tests/test_security/test_audit.py +63 -0
  43. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/tests/test_security/test_config_encryptor.py +111 -0
  44. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/tests/test_security/test_sandbox.py +132 -45
  45. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/tests/test_shell.py +1 -1
  46. apcore_cli-0.9.0/tests/test_system_usage.py +257 -0
  47. apcore_cli-0.9.0/tests/test_validate.py +227 -0
  48. apcore_cli-0.7.0/CHANGELOG.md +0 -253
  49. apcore_cli-0.7.0/commands/ops.py +0 -4
  50. apcore_cli-0.7.0/htmlcov/.gitignore +0 -2
  51. apcore_cli-0.7.0/htmlcov/class_index.html +0 -551
  52. apcore_cli-0.7.0/htmlcov/coverage_html_cb_bcae5fc4.js +0 -735
  53. apcore_cli-0.7.0/htmlcov/favicon_32_cb_58284776.png +0 -0
  54. apcore_cli-0.7.0/htmlcov/function_index.html +0 -1931
  55. apcore_cli-0.7.0/htmlcov/index.html +0 -306
  56. apcore_cli-0.7.0/htmlcov/keybd_closed_cb_ce680311.png +0 -0
  57. apcore_cli-0.7.0/htmlcov/status.json +0 -1
  58. apcore_cli-0.7.0/htmlcov/style_cb_a5a05ca4.css +0 -389
  59. apcore_cli-0.7.0/htmlcov/z_2a36de3398a45e2f___init___py.html +0 -105
  60. apcore_cli-0.7.0/htmlcov/z_2a36de3398a45e2f_audit_py.html +0 -169
  61. apcore_cli-0.7.0/htmlcov/z_2a36de3398a45e2f_auth_py.html +0 -171
  62. apcore_cli-0.7.0/htmlcov/z_2a36de3398a45e2f_config_encryptor_py.html +0 -249
  63. apcore_cli-0.7.0/htmlcov/z_2a36de3398a45e2f_sandbox_py.html +0 -241
  64. apcore_cli-0.7.0/htmlcov/z_4d276f71fa2bf6ca___init___py.html +0 -184
  65. apcore_cli-0.7.0/htmlcov/z_4d276f71fa2bf6ca__sandbox_runner_py.html +0 -122
  66. apcore_cli-0.7.0/htmlcov/z_4d276f71fa2bf6ca_approval_py.html +0 -352
  67. apcore_cli-0.7.0/htmlcov/z_4d276f71fa2bf6ca_builtin_group_py.html +0 -475
  68. apcore_cli-0.7.0/htmlcov/z_4d276f71fa2bf6ca_cli_py.html +0 -1173
  69. apcore_cli-0.7.0/htmlcov/z_4d276f71fa2bf6ca_config_py.html +0 -238
  70. apcore_cli-0.7.0/htmlcov/z_4d276f71fa2bf6ca_discovery_py.html +0 -545
  71. apcore_cli-0.7.0/htmlcov/z_4d276f71fa2bf6ca_display_helpers_py.html +0 -127
  72. apcore_cli-0.7.0/htmlcov/z_4d276f71fa2bf6ca_exposure_py.html +0 -227
  73. apcore_cli-0.7.0/htmlcov/z_4d276f71fa2bf6ca_factory_py.html +0 -833
  74. apcore_cli-0.7.0/htmlcov/z_4d276f71fa2bf6ca_init_cmd_py.html +0 -287
  75. apcore_cli-0.7.0/htmlcov/z_4d276f71fa2bf6ca_output_py.html +0 -467
  76. apcore_cli-0.7.0/htmlcov/z_4d276f71fa2bf6ca_ref_resolver_py.html +0 -227
  77. apcore_cli-0.7.0/htmlcov/z_4d276f71fa2bf6ca_schema_parser_py.html +0 -295
  78. apcore_cli-0.7.0/htmlcov/z_4d276f71fa2bf6ca_shell_py.html +0 -739
  79. apcore_cli-0.7.0/htmlcov/z_4d276f71fa2bf6ca_strategy_py.html +0 -307
  80. apcore_cli-0.7.0/htmlcov/z_4d276f71fa2bf6ca_system_cmd_py.html +0 -566
  81. apcore_cli-0.7.0/src/apcore_cli/__init__.py +0 -87
  82. apcore_cli-0.7.0/src/apcore_cli/security/sandbox.py +0 -168
  83. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/.github/CODEOWNERS +0 -0
  84. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/.github/copilot-ignore +0 -0
  85. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/.github/workflows/ci.yml +0 -0
  86. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/.gitignore +0 -0
  87. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/.gitmessage +0 -0
  88. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/.pre-commit-config.yaml +0 -0
  89. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/LICENSE +0 -0
  90. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/Makefile +0 -0
  91. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/examples/extensions/math/add.py +0 -0
  92. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/examples/extensions/math/multiply.py +0 -0
  93. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/examples/extensions/sysutil/disk.py +0 -0
  94. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/examples/extensions/sysutil/env.py +0 -0
  95. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/examples/extensions/sysutil/info.py +0 -0
  96. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/examples/extensions/text/reverse.py +0 -0
  97. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/examples/extensions/text/upper.py +0 -0
  98. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/examples/extensions/text/wordcount.py +0 -0
  99. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/examples/run_examples.sh +0 -0
  100. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/planning/approval-gate.md +0 -0
  101. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/planning/config-resolver.md +0 -0
  102. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/planning/core-dispatcher.md +0 -0
  103. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/planning/discovery.md +0 -0
  104. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/planning/exposure-filtering.md +0 -0
  105. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/planning/grouped-commands.md +0 -0
  106. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/planning/output-formatter.md +0 -0
  107. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/planning/overview.md +0 -0
  108. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/planning/schema-parser.md +0 -0
  109. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/planning/security-manager.md +0 -0
  110. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/planning/shell-integration.md +0 -0
  111. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/planning/state.json +0 -0
  112. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/src/apcore_cli/_sandbox_runner.py +0 -0
  113. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/src/apcore_cli/display_helpers.py +0 -0
  114. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/src/apcore_cli/exposure.py +0 -0
  115. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/src/apcore_cli/security/__init__.py +0 -0
  116. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/src/apcore_cli/security/auth.py +0 -0
  117. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/src/apcore_cli/validate.py +0 -0
  118. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/tests/__init__.py +0 -0
  119. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/tests/conformance/__init__.py +0 -0
  120. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/tests/conformance/test_apcli_visibility.py +0 -0
  121. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/tests/conftest.py +0 -0
  122. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/tests/test_config.py +0 -0
  123. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/tests/test_discovery.py +0 -0
  124. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/tests/test_discovery_fe13.py +0 -0
  125. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/tests/test_display_helpers.py +0 -0
  126. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/tests/test_e2e.py +0 -0
  127. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/tests/test_exposure.py +0 -0
  128. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/tests/test_init_cmd.py +0 -0
  129. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/tests/test_output.py +0 -0
  130. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/tests/test_sandbox_runner.py +0 -0
  131. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/tests/test_security/__init__.py +0 -0
  132. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/tests/test_security/test_auth.py +0 -0
  133. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/tests/test_strategy.py +0 -0
  134. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/tests/test_system_cmd.py +0 -0
  135. {apcore_cli-0.7.0 → apcore_cli-0.9.0}/tests/test_toolkit_integration.py +0 -0
@@ -0,0 +1,501 @@
1
+ # Changelog
2
+
3
+ All notable changes to apcore-cli (Python SDK) will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.9.0] - 2026-05-13
9
+
10
+ ### Added
11
+
12
+ - **`tests/conformance/test_snake_case_kwargs.py`** — runs the cross-language Algorithm C-SNAKE fixture (`apcore-cli/conformance/fixtures/snake-case-kwargs/cases.json`) against `build_module_command` via `click.testing.CliRunner`. Five cases verify that schema property names with underscores (`has_solution`, `sort_by`, `sort_order`) survive the round trip from CLI parse to the input dict received by `executor.call`. No source change required — click natively maps `--has-solution` to `has_solution`; the Python SDK is the parity reference for the parallel TypeScript fix. Surfaced as part of the cross-SDK regression coverage gap audit.
13
+
14
+ ### Fixed (2026-05-13 — cross-SDK audit D10/D11/D1)
15
+
16
+ - **`ConfigEncryptor` LOGNAME key-derivation chain** (D10-001 / D11-003) — PBKDF2 username fallback was `USER → USERNAME → "unknown"` (3-tier); now `USER → LOGNAME → USERNAME → "unknown"` (4-tier) matching the spec and Rust. On hosts where `USER` is unset but `LOGNAME` is set (cron, `sudo -i`, container init), ciphertext written by the Python SDK now round-trips correctly with the Rust SDK. `src/apcore_cli/security/config_encryptor.py:96, 165`.
17
+ - **`ConfigEncryptor.store` keyring write-failure not wrapped** (D11-004) — raw `keyring.set_password` exceptions now caught and re-raised as `ConfigDecryptionError`, matching TypeScript and Rust. `src/apcore_cli/security/config_encryptor.py:31`.
18
+ - **`ref_resolver` only descended into `properties`** (D11-001) — recursive schema walk now visits every dict-valued child (items, additionalProperties, patternProperties, if/then/else, not, contains, propertyNames), matching TypeScript and Rust. `$ref` under array schemas and conditional schemas is now resolved. `src/apcore_cli/ref_resolver.py:142`.
19
+ - **`ref_resolver` copy-on-write visited-set** (D11-002) — now uses a single mutable set with remove-on-unwind, allowing diamond `$ref` patterns (two sibling schemas referencing the same `$def`) to resolve correctly. `src/apcore_cli/ref_resolver.py:71`.
20
+ - **`AuditLogger._get_user` uses real UID instead of effective UID** (D11-010) — switched from `os.getuid()` to `os.geteuid()` so audit records reflect the privileges the process actually runs with under `sudo` / setuid binaries. Matches Rust (`geteuid`) and TypeScript (`os.userInfo`). `src/apcore_cli/security/audit.py:77`.
21
+ - **`check_approval` ignores `APCORE_CLI_APPROVAL_TIMEOUT` env var** (D11-012) — `CliApprovalHandler` and the legacy `check_approval()` wrapper now honor the env var when no explicit timeout is passed (precedence: constructor arg > env var > 60 s default). Matches TypeScript. `src/apcore_cli/approval.py`.
22
+ - **`exec --dry-run` crashes with `AttributeError` when executor lacks `validate`** (D11-013) — guarded with `hasattr(executor, "validate")`; falls back to synthetic `{"valid": True}` matching TypeScript. `src/apcore_cli/discovery.py:378`.
23
+ - **`CliApprovalHandler.request_approval` missing `requires_approval=False` short-circuit** (D11-014) — now returns `approved/not_required` when the request explicitly carries `requires_approval=False`, matching Rust. `src/apcore_cli/approval.py:66`.
24
+ - **CLI brand string in auth error messages** (D11-006) — remediation strings now say `apcli config set auth.api_key` (canonical FE-13 name) instead of `apcore-cli config set auth.api_key`. `src/apcore_cli/security/auth.py`.
25
+ - **`reconvert_enum_values` missing from public re-export** (D1-W2) — added to `__init__.py` import block and `__all__`. Embedders can now `from apcore_cli import reconvert_enum_values` for parity with TypeScript and Rust.
26
+ - **Ref-resolver error hierarchy missing from public re-export** (D1-W3) — `CircularRefError`, `MaxDepthExceededError`, `UnresolvableRefError`, `RefResolverError` added to `__init__.py` import block and `__all__`. Parity with TypeScript `index.ts:82-84`.
27
+ - **`DEFAULT_BUILTIN_GROUP_NAME` missing from public re-export** (D1 re-audit) — added to `__init__.py`. Parity with Rust `lib.rs:190`.
28
+ - **Dead exit-code constants removed** (D9-W1) — `EXIT_CONFIG_ENV_PREFIX_CONFLICT` and `EXIT_CONFIG_ENV_MAP_CONFLICT` (both = 78, zero callers) deleted from `src/apcore_cli/exit_codes.py`.
29
+ - **Unused `pytest-asyncio` dev dependency removed** (D6) — the package was declared but never exercised; no async tests exist. Removed from `[project.optional-dependencies].dev`.
30
+
31
+ ### Fixed
32
+
33
+ - **CSV `--format csv` Python-repr bug** — `csv.DictWriter` was called with `{k: str(v) for k, v in row.items()}` which emitted Python repr `{'k': 'v'}` (single quotes) for nested dict/list values. The output was not valid JSON and any downstream JSON parser would fail. Now delegates to `apcore_toolkit.format_csv(rows)` which emits canonical compact JSON. `src/apcore_cli/output.py:149, 378`.
34
+ - **CSV heterogeneous-keys data loss** — header is now the union of keys across all rows (was first-row only via `list(rows[0].keys())`).
35
+ - **CSV line terminator** — now `\r\n` per RFC 4180.
36
+ - **JSONL canonical form** — now compact (no spaces between separators), matching the cross-SDK contract. Tests updated.
37
+
38
+ ### Changed
39
+
40
+ - **User-visible help/man/completion text no longer leaks the `apcore` framework name** to end users of downstream CLIs built on apcore-cli. Affected strings: `init` group description (`Scaffold new apcore modules` → `Scaffold new modules`, `init_cmd.py:45`), `--extensions-dir` option help (`Path to apcore extensions directory.` → `Path to extensions directory.`, `factory.py:460`), zsh/fish completion descriptions for `exec` (`Execute an apcore module` → `Execute a module`, `shell.py:130, 211`), and man-page `ENVIRONMENT` section text (`shell.py:299, 314, 319, 458`) — drops `apcore` from the descriptive copy (`Path to the apcore extensions directory` → `Path to the extensions directory`, `Global apcore logging verbosity` → `Global logging verbosity`, `API key for authenticating with the apcore registry` → `API key for authenticating with the registry`). Logger names, source comments, module docstrings, and environment-variable identifiers (`APCORE_*`) are unchanged — only descriptive copy that appears in `--help`, shell completion, and `man` output. Cross-SDK parity with TypeScript 0.8.2 and Rust 0.8.1.
41
+
42
+ ### Changed (breaking CLI surface)
43
+
44
+ - **Global `--verbose` flag renamed to `--all-options`** — The help-display flag is now `--all-options`; use `apcore-cli module --help --all-options` to reveal hidden built-in options. `verbose` is removed from the reserved schema property names set — module schemas may now freely define `verbose: boolean` for runtime output control. Internal API: `set_verbose_help()` renamed to `set_all_options_help()`; module-level global `_verbose_help` renamed to `_all_options_help`. Tracked in [apcore-cli#21](https://github.com/aiperceivable/apcore-cli/issues/21).
45
+
46
+ ### Changed (breaking dependency semantics)
47
+
48
+ - **`apcore-toolkit` promoted from optional extra to REQUIRED runtime dependency** (`>=0.7.0`). The previous `pip install 'apcore-cli[toolkit]'` extras pattern is retained as a no-op for backward compat with install scripts, but the toolkit is now always installed alongside apcore-cli. All `--format` operations route through the toolkit's reference implementation for csv/jsonl/markdown/skill.
49
+
50
+ ### Why
51
+
52
+ See ADR-09 in `apcore-cli/docs/tech-design.md` for the byte-equivalent toolkit-delegated tier rationale.
53
+
54
+
55
+ ## [0.8.0] - 2026-05-08
56
+
57
+ ### Removed
58
+
59
+ - **D9-001 — FE-13 §11.2 deprecation shims removed**. The 13 hidden root-level
60
+ shims (`list`, `describe`, `exec`, `init`, `validate`, `health`, `usage`,
61
+ `enable`, `disable`, `reload`, `config`, `completion`, `describe-pipeline`)
62
+ installed by `_register_deprecation_shims` and the `__is_deprecation_shim__`
63
+ collision-handling path in `extra_commands` wiring have been deleted along
64
+ with the `_DEPRECATED_ROOT_COMMANDS` table. Use the canonical
65
+ `apcli <command>` paths instead. Calls like `apcore-cli list` now exit
66
+ non-zero with Click's "No such command" message — the warning window
67
+ documented as "removed in v0.8" is closed.
68
+
69
+ ### Deprecated
70
+
71
+ - **`CliModuleNotFoundError` alias** — the symbol still resolves to
72
+ `ModuleNotFoundError` (see D1-002 in Changed) but is scheduled for
73
+ removal in v0.10.0. Update imports to
74
+ `from apcore_cli import ModuleNotFoundError`.
75
+
76
+ ### Security
77
+
78
+ - **D10-001 — `Sandbox` per-stream output cap** (`sandbox.py:155`). The previous
79
+ implementation summed `stdout + stderr` against a single `max_output_bytes`
80
+ budget — a runaway child writing only to stderr could starve the stdout
81
+ budget and vice versa, and the diagnostic on overflow did not name the
82
+ offending stream. Each stream now has an independent byte budget matching
83
+ Rust and TypeScript; the overflow error names the stream that tripped the
84
+ cap.
85
+ - **D11-W2 — `Sandbox` switched from `subprocess.run` to `subprocess.Popen`
86
+ with threaded chunked reads** (`sandbox.py:155`). `capture_output=True`
87
+ buffered the entire child stdio into parent memory before the cap was
88
+ checked, so a child producing GBs of output could OOM the parent before
89
+ the limit was enforced. The new implementation streams stdout/stderr
90
+ through reader threads with bounded buffers and kills the child as soon
91
+ as either stream exceeds its cap. Memory consumption is now bounded by
92
+ `2 × max_output_bytes` regardless of child output volume.
93
+ - **D11-003 — `ConfigEncryptor` v1 decryption honours
94
+ `APCORE_CLI_CONFIG_PASSPHRASE`** (`config_encryptor.py:128`). `_aes_decrypt_v1`
95
+ hard-coded the host:user material, so v1 ciphertext encrypted by the Rust
96
+ or TypeScript SDKs under a passphrase failed to decrypt on Python.
97
+ Decryption now tries the passphrase-derived key first when the env var is
98
+ set, falling back to host:user material — matching TypeScript
99
+ `aesDecryptV1`. Cross-SDK config bundles are now portable.
100
+ - **D11-008 — `AuditLogger._get_user` fallback chain now includes `LOGNAME`**
101
+ (`audit.py:66`). The canonical chain per `security.md` (D11-W1) is
102
+ `getlogin → pwd.getpwuid → USER → LOGNAME → USERNAME → unknown`. Python
103
+ previously skipped `LOGNAME`, so audit-log `user` fields diverged from
104
+ Rust/TS on hosts where only `LOGNAME` is set (some container runtimes,
105
+ cron jobs).
106
+
107
+ ### Added
108
+
109
+ - **`builtin_group_name="apcli"` kwarg on `create_cli`** — downstream branded CLIs that embed apcore-cli can now expose the built-in commands under a custom namespace (e.g. `mycorp-cli admin health` instead of `mycorp-cli apcli health`). `ApcliGroup` gains a `name` parameter (with property accessor) threaded through `from_cli_config` / `from_yaml` / `_build`. Default `"apcli"` is unchanged. Validated against `/^[a-z][a-z0-9_-]*$/`; invalid values exit 2. `RESERVED_GROUP_NAMES` collision check now consults `GroupedModuleGroup._reserved_group_names` (instance attribute, defaults to the static frozenset; factory replaces with the resolved name). Env var `APCORE_CLI_APCLI` and config keys `apcli.*` deliberately do NOT rename — they are apcore-cli-internal toggles, not user-facing. Cross-SDK parity with TypeScript `createCli({ builtinGroupName })`. New `DEFAULT_BUILTIN_GROUP_NAME` constant exported from `apcore_cli.builtin_group`.
110
+ - **`_exit_on_system_error(e)` helper in `system_cmd.py`** — centralizes the canonical error→exit-code mapping for system-management subcommands, replacing 7 sites that previously used bare `sys.exit(1)` (audit D11-B-002, see Fixed).
111
+ - **5 new tests in `tests/test_builtin_group.py`** — `TestBuiltinGroupRename` class covers default name, custom name via both factories, validation of valid/invalid name shapes (5 valid + 6 invalid forms each).
112
+ - **D1-001 — 13 `register_*_command` factories + `configure_man_help`
113
+ re-exported from `apcore_cli` package root**. Embedders that compose
114
+ their own root command tree no longer need to reach into private
115
+ submodules (`apcore_cli.commands.list_cmd`, etc.). All TS/Rust
116
+ `register_*` counterparts now have a Python public-API equivalent.
117
+ - **D1-003 — `apcore_cli.exit_codes` module** with 24 `EXIT_*` integer
118
+ constants, an `EXIT_CODES` mapping dict, and an `exit_code_for_error()`
119
+ helper. Mirrors TS `errors.ts` `EXIT_CODES` + `exitCodeForError` and
120
+ Rust `src/lib.rs` `EXIT_*` constants. Embedders can now map exceptions
121
+ to documented exit codes without re-implementing the table.
122
+ - **D1-007 — `format_module_list`, `format_module_detail`,
123
+ `resolve_format` re-exported from package root**. The
124
+ output-formatter feature spec declares these as Contracts; previously
125
+ only `format_exec_result` was public.
126
+ - **D1-W1 — `APCLI_SUBCOMMAND_NAMES` re-exported from `apcore_cli`**.
127
+ Matches Rust `lib.rs` and is now in `__all__` for static-analysis
128
+ tooling.
129
+ - **D1-W2 — `ApcliConfig` TypedDict** added to the public surface,
130
+ mirroring the TypeScript type alias and Rust struct so embedders have
131
+ a static contract for the `apcli.*` config block.
132
+ - **D1-W3 — `register_config_namespace()` helper + module-level
133
+ `DEFAULTS` constant** in `config.py`. The package still registers the
134
+ namespace at import time, but embedders can now invoke the helper
135
+ explicitly (parity with `apcore-cli-typescript`).
136
+ - **D1-W5 — Core dispatcher embedder API re-exported from package
137
+ root**: `build_module_command`, `collect_input`, `validate_module_id`,
138
+ `set_audit_logger`, `set_verbose_help`, `set_docs_url`. Embedders no
139
+ longer have to import from `apcore_cli.cli` directly. Matches Rust
140
+ `lib.rs:186-190` and TS `index.ts:18`. New `tests/test_public_api.py`
141
+ pins the surface against future drift.
142
+ - **D1-info-1 — typed `ApcliGroupError` exception**
143
+ (`builtin_group.py:107`). Cross-SDK parity with Rust `ApcliGroupError`;
144
+ embedders previously had no stable error class to match on for
145
+ built-in-group config validation. `ApcliGroupError(ValueError)`
146
+ preserves backwards compat — existing `except ValueError` callers
147
+ still catch it. The invalid-name regex check in `__init__` now raises
148
+ `ApcliGroupError`. Re-exported from `apcore_cli`.
149
+
150
+ ### Fixed
151
+
152
+ - **D11-B-006 — `discovery.py:208` sort direction inverted**. `apcli list --sort calls|errors|latency` now defaults to DESCENDING (highest call count first) per spec T-LST-04, matching Rust `discovery.rs:209` and TypeScript `discovery.ts:186`. Previously the user's raw `--reverse` flag (default False) was passed directly to `sort_modules_by_usage(..., reverse=...)`, producing ASCENDING output by default — the inverse of the spec. Fix passes `reverse=not reverse` for the data path AND adds a re-sort at the call site for the audit-log-empty fallback so id-fallback continues to default ASCENDING per spec.
153
+ - **D11-B-002 — `system_cmd.py` collapsed every error to exit 1**. The 7 `except Exception as e: sys.exit(1)` sites bypassed Python's own `_ERROR_CODE_MAP` (canonical 44/46/47/77) — scripted operators could not distinguish "module not found" from "ACL denied" from generic failure. All 7 sites now route through the new `_exit_on_system_error(e)` helper which calls `exit_code_for_error(e)` from `apcore_cli.exit_codes`. The 4 audit-log entries previously hardcoding `exit_code=1` now log the resolved code.
154
+ - **D11-NEW-005 — RESERVED_PROPERTY_NAMES no longer raises generic `ValueError`**. `schema_to_click_options` previously raised `ValueError` when a schema property collided with a built-in CLI option — opaque to scripted callers and inconsistent with the neighbour flag-collision branch (which already exited 48). Now writes a user-facing `Error:` line to stderr and calls `sys.exit(48)` per spec, matching TS `process.exit(EXIT_CODES.SCHEMA_CIRCULAR_REF)` and Rust `CliError::SchemaParserFailure → EXIT_SCHEMA_CIRCULAR_REF`. Tests tightened from `pytest.raises((ValueError, Exception))` to `pytest.raises(SystemExit)` with `code == 48` assertion.
155
+ - **D9-NEW-002 — `ref_resolver.py` `allOf required` not deduplicated**. `_resolve_node`'s `allOf` branch concatenated parent `required` + each branch's `required` without dedup, producing duplicate entries in the merged schema's `required` array. JSON Schema validators ignore duplicates so observable validation behaviour was unchanged, but cross-SDK byte-comparison tooling (and the `anyOf`/`oneOf` paths, which already deduped) flagged the divergence. Fix: explicit seen-set dedup preserving first-seen order, matching TS `[...new Set(...)]` and Rust `merge_allof`.
156
+ - **D10-003 — `build_module_command` leaked `RefResolverError`
157
+ tracebacks** (`cli.py:538`). The `resolve_refs` catch clause re-raised
158
+ unchanged, so callers saw a Python traceback instead of a clean
159
+ documented exit code. Now translates `CircularRefError` /
160
+ `MaxDepthExceededError` to `sys.exit(48)` and `UnresolvableRefError`
161
+ (plus generic `RefResolverError`) to `sys.exit(45)`, mirroring
162
+ `schema_parser.py:111` and the Rust/TS contracts.
163
+ - **D11-NEW-003 — `ref_resolver` `max_depth` over-counted plain nested
164
+ `properties`** (`ref_resolver.py`). `_resolve_node` previously
165
+ incremented `depth + 1` when recursing into nested `properties`
166
+ values, so a schema with >32 levels of nested objects (no `$ref` at
167
+ all) was rejected with `MaxDepthExceededError`. The spec wording is
168
+ "Maximum `$ref` resolution recursion depth" — `$ref` hops along a
169
+ single chain, not total stack depth. `depth` is now only incremented
170
+ on `$ref` traversal, aligning with Rust `ref_resolver.rs:297`. Also
171
+ adds 4 regression tests for `anyOf`/`oneOf` sibling-required
172
+ preservation and `anyOf` overlap dedup.
173
+ - **D10-info-1 — `APCORE_CLI_APCLI` env var not trimmed on read**
174
+ (`builtin_group.py:414`). Spec invariant 2 requires the parser to be
175
+ case-insensitive AND trim-on-read. Surrounding whitespace previously
176
+ caused a silent Tier-3/Tier-4 fall-through. Now strips before
177
+ lowercasing, matching Rust/TS.
178
+ - **D11-010 — `AuditLogger` write-failure warnings deduplicated**
179
+ (`audit.py:55`). Previously warned on every failed write, flooding
180
+ logs when an audit dir is unwritable. An instance flag now gates the
181
+ warning so it fires once per logger instance, matching the TS
182
+ `writeFailureWarned` flag.
183
+
184
+ ### Changed
185
+
186
+ - **`apcli system *` and `apcli strategy describe-pipeline` `--format` choices**
187
+ expanded from `[table, json]` to `[table, json, csv, yaml, jsonl]`, matching
188
+ the existing `apcli list` / `apcli exec` choice set. `markdown` and `skill`
189
+ are deliberately excluded from these subcommands — their payloads are
190
+ health / strategy results, not `ScannedModule` data. Issue
191
+ [#20](https://github.com/aiperceivable/apcore-cli/issues/20).
192
+ - **Dependency bump**: requires `apcore >= 0.21.0` (was `>= 0.19.0`) and the
193
+ optional `[toolkit]` extra now requires `apcore-toolkit >= 0.6` (was `>= 0.5`).
194
+ Aligns with upstream `apcore 0.21.0` (Module.preview / PreflightResult.predicted_changes,
195
+ ephemeral.* namespace pilot) and `apcore-toolkit 0.6.0` (surface-aware formatters).
196
+ No CLI-visible behavioural breaks — apcore 0.20→0.21 deprecations
197
+ (`TaskStore.put`/`save`, `TaskStatus.RETRYING`, `CircuitOpenError`) keep
198
+ legacy aliases for one minor release; the cli does not call those surfaces directly.
199
+ - **D1-002 — `CliModuleNotFoundError` renamed to `ModuleNotFoundError`**
200
+ for cross-language port-ability with TS / Rust `ModuleNotFoundError`.
201
+ The class intentionally shadows `builtins.ModuleNotFoundError` inside
202
+ the `apcore_cli` namespace. A deprecation alias
203
+ `CliModuleNotFoundError = ModuleNotFoundError` is kept for backwards
204
+ compatibility and will be removed in v0.10.0. Reverses the D2-001
205
+ rename which predated the cross-SDK parity policy.
206
+ - **Issue #19 — drop "apcore" branding from embedded-mode `--help`**:
207
+ `create_cli()` now resolves the top-level CLI description from the new
208
+ `description=` parameter (defaults to `f"{prog_name} CLI"`), the `apcli`
209
+ subgroup advertises itself as `Built-in commands` rather than
210
+ `apcore-cli built-in commands`, and the `--verbose` option / footer drop
211
+ the trailing `apcore` from `(including built-in apcore options)`. Standalone
212
+ bin entry (`apcore_cli/__main__.py:main()`) passes
213
+ `description="<prog> — execute apcore modules from the command line"`
214
+ explicitly so the standalone surface is unchanged.
215
+
216
+ ### Added
217
+
218
+ - **`--format markdown` and `--format skill`** for `apcli list` and `apcli describe`
219
+ (issue [#20](https://github.com/aiperceivable/apcore-cli/issues/20)). Both
220
+ delegate to `apcore_toolkit.format_module(s)` (≥0.6) so the output is
221
+ byte-identical to the same toolkit call in the TypeScript and Rust SDKs.
222
+ `--format skill` produces vendor-neutral SKILL.md content directly loadable
223
+ by Claude Code (`.claude/skills/<id>/SKILL.md`) and Gemini CLI
224
+ (`.gemini/skills/<id>/SKILL.md`):
225
+
226
+ ```bash
227
+ apcli describe users.create --format skill > .claude/skills/users.create/SKILL.md
228
+ ```
229
+
230
+ A new internal adapter `_descriptor_to_scanned()` maps `ModuleDescriptor`
231
+ (apcore registry) to `ScannedModule` (apcore-toolkit). A `ClickException` with
232
+ a clear install hint is raised if the optional `[toolkit]` extra is missing.
233
+ - **Issue #18 — host-app `--version` opt-in**: new `version: str | None = None`
234
+ parameter on `create_cli()`. When supplied, registers `-V/--version` with
235
+ the host's version string. **When omitted, the `--version` flag is no
236
+ longer registered** — embedded CLIs that do not opt in stop leaking the
237
+ SDK's own version through `-V/--version`. The standalone bin entry
238
+ passes `version=apcore_cli.__version__` explicitly so the
239
+ `apcore-cli` binary's behaviour is preserved.
240
+ - **Issue #19 — `description: str | None = None`** on `create_cli()`.
241
+ - **Issue #17 — `system.usage` aggregator + `list --sort calls|errors|latency`**:
242
+ new module `apcore_cli.system_usage` reads `~/.apcore-cli/audit.jsonl`,
243
+ filters by period (default 24h), and returns per-module aggregates
244
+ (`calls`, `errors`, `avg latency_ms`). `list --sort {calls,errors,latency}`
245
+ now consults the aggregator instead of falling back to id-sort with a
246
+ buried `logger.warning`. When the audit log has no entries in the period
247
+ window the discovery layer prints a user-visible note to stderr
248
+ (`note: no usage data available for --sort <field>; sorted by id. ...`)
249
+ and falls back to id-sort. Module-protocol registration of
250
+ `system.usage.summary` / `system.usage.module` as registry-callable
251
+ built-ins is tracked as a follow-up — today the readers are invoked
252
+ directly by the discovery layer.
253
+ - New file: `apcore_cli/system_usage.py`.
254
+
255
+ ---
256
+
257
+ ## [0.7.0] - 2026-04-23
258
+
259
+ ### Changed
260
+
261
+ - **Dependency bump**: requires `apcore >= 0.18.0` (was `>= 0.17.1`). Aligns with upstream `apcore 0.18.0` and `apcore-toolkit 0.4.2` breaking changes.
262
+ - **`MAX_MODULE_ID_LENGTH` 128 → 192**: `validate_module_id()` and all references updated to the new 192-character limit introduced in `apcore 0.18.0` (`apcore.registry.registry.MAX_MODULE_ID_LENGTH`).
263
+ - **`describe-pipeline` renders `StrategyInfo`**: `executor.describe_pipeline(strategy)` now returns a `StrategyInfo` dataclass (`name`, `step_count`, `step_names`, `description`). `strategy.py` updated to use `StrategyInfo` fields; header line is `Pipeline: {info.name} ({info.step_count} steps)`. Falls back gracefully to the legacy `_resolve_strategy_name` path when `describe_pipeline` is unavailable.
264
+ - **CI — spec-repo checkout**: `.github/workflows/ci.yml` now checks out `aiperceivable/apcore-cli` into `.apcore-cli-spec/` and exposes it to `pytest` via `APCORE_CLI_SPEC_REPO`. Mirrors the pattern established in `apcore-python` / `apcore-cli-typescript`.
265
+
266
+ ### Added
267
+
268
+ - **`create_cli(app=...)` parameter**: `create_cli()` accepts an optional `app: APCore` unified client (introduced in `apcore 0.18.0`). `app` is mutually exclusive with `registry`/`executor` (raises `ValueError`). When `app` is provided, `registry` and `executor` are extracted from `app.registry` and `app.executor`. Filesystem discovery is skipped if `app.registry` already contains registered modules; otherwise normal discovery proceeds into `app.registry`.
269
+ - **Cross-language conformance test harness** (`tests/conformance/`) consuming the shared apcli-visibility fixtures from the `aiperceivable/apcore-cli` spec repo (`conformance/fixtures/apcli-visibility/`). Behavioral assertions (apcli group visibility, registered subcommand set for `include`/`exclude` modes, always-registered `exec`) run today across all five canonical scenarios (`standalone-default`, `embedded-default`, `cli-override`, `env-override`, `yaml-include`). Byte-matching against `expected_help.txt` is marked `xfail` until Click's `HelpFormatter` is replaced with a canonical clap v4 / GNU-style emitter, tracked for parity with `apcore-cli-typescript/src/canonical-help.ts`.
270
+ - **`APCORE_CLI_SPEC_REPO` env var** — overrides the spec-repo lookup path for conformance fixtures. Defaults to a sibling checkout (`../apcore-cli/`). Tests are skipped (not failed) when the spec repo is absent.
271
+ - **FE-12: Module Exposure Filtering** — Declarative control over which discovered modules are exposed as CLI commands.
272
+ - `ExposureFilter` class in `exposure.py` with `is_exposed(module_id)` and `filter_modules(ids)` methods.
273
+ - Three modes: `all` (default), `include` (whitelist), `exclude` (blacklist) with glob-pattern matching.
274
+ - `ExposureFilter.from_config(dict)` classmethod for loading from `apcore.yaml` `expose` section.
275
+ - `create_cli(expose=...)` parameter accepting `dict` or `ExposureFilter` instance.
276
+ - `list --exposure {exposed,hidden,all}` filter flag in discovery commands.
277
+ - `GroupedModuleGroup._build_group_map()` integration: calls `ExposureFilter.is_exposed()` to filter command registration.
278
+ - `ConfigResolver` gains `expose.*` config keys.
279
+ - 4-tier config precedence: `CliConfig.expose` > `--expose-mode` CLI flag > env var > `apcore.yaml`.
280
+ - Hidden modules remain invocable via `exec <module_id>`.
281
+ - New file: `exposure.py`.
282
+
283
+ ---
284
+
285
+ ## [0.6.0] - 2026-04-06
286
+
287
+ ### Changed
288
+
289
+ - **Dependency bump**: requires `apcore >= 0.17.1` (was `>= 0.15.1`). Adds Execution Pipeline Strategy, Config Bus enhancements, Pipeline v2 declarative step metadata, `minimal` strategy preset.
290
+ - **Schema parser**: Required schema properties now correctly enforced at CLI option level (was silently optional).
291
+ - **Approval gate**: Fixed inverted logic in annotation type guard; `check_approval()` now accepts `timeout` parameter.
292
+
293
+ ### Added
294
+
295
+ - **FE-11: Usability Enhancements** — 11 new capabilities:
296
+ - `--dry-run` preflight mode via `Executor.validate()`. Standalone `validate` command.
297
+ - System management commands: `health`, `usage`, `enable`, `disable`, `reload`, `config get`/`config set`. Graceful no-op when system modules unavailable.
298
+ - Enhanced error output: structured JSON with `ai_guidance`, `suggestion`, `retryable`, `user_fixable`, `details`. TTY hides machine-only fields.
299
+ - `--trace` pipeline visualization via `call_with_trace()`.
300
+ - `CliApprovalHandler` class implementing apcore `ApprovalHandler` protocol, wired to `Executor.set_approval_handler()`. `--approval-timeout`, `--approval-token` flags.
301
+ - `--stream` JSONL output via `Executor.stream()`.
302
+ - Enhanced `list` command: `--search`, `--status`, `--annotation`, `--sort`, `--reverse`, `--deprecated`, `--deps`.
303
+ - `--strategy` selection: `standard`, `internal`, `testing`, `performance`, `minimal`. `describe-pipeline` command.
304
+ - Output format extensions: `--format csv|yaml|jsonl`, `--fields` dot-path field selection.
305
+ - Multi-level grouping: `cli.group_depth` config key.
306
+ - Custom command extension: `create_cli(extra_commands=[...])` with collision detection.
307
+ - New error code: `CONFIG_ENV_MAP_CONFLICT`.
308
+ - New config keys: `cli.approval_timeout` (60), `cli.strategy` ("standard"), `cli.group_depth` (1).
309
+ - New environment variables: `APCORE_CLI_APPROVAL_TIMEOUT`, `APCORE_CLI_STRATEGY`, `APCORE_CLI_GROUP_DEPTH`.
310
+ - New files: `system_cmd.py`, `strategy.py`.
311
+
312
+ ---
313
+
314
+ ## [0.5.1] - 2026-04-03
315
+
316
+ ### Added
317
+ - **Pre-populated registry support** — `create_cli()` accepts optional `registry` and `executor` parameters. When a pre-populated `Registry` is provided, filesystem discovery is skipped entirely. This enables frameworks that register modules at runtime (e.g. apflow's bridge) to generate CLI commands from their existing registry without requiring an extensions directory.
318
+ - Passing `registry` alone auto-builds an `Executor`; passing `executor` without `registry` raises `ValueError`.
319
+
320
+ ---
321
+
322
+ ## [0.4.1] - 2026-03-30
323
+
324
+ ### Fixed
325
+ - prevent click parameter mismatch by setting expose_value=False for the --man option
326
+
327
+ ## [0.4.0] - 2026-03-29
328
+
329
+ ### Added
330
+ - **Verbose help mode** — Built-in apcore options (`--input`, `--yes`, `--large-input`, `--format`, `--sandbox`) are now hidden from `--help` output by default. Pass `--help --verbose` to display the full option list including built-in options.
331
+ - **Universal man page generation** — `build_program_man_page()` generates a complete roff man page covering all registered commands. `configure_man_help()` adds `--help --man` support to any Click CLI, enabling downstream projects to get man pages for free.
332
+ - **Documentation URL support** — `set_docs_url()` sets a base URL for online docs. Per-command help shows `Docs: {url}/commands/{name}`, man page SEE ALSO includes `Full documentation at {url}`. No default — disabled when not set.
333
+
334
+ ### Changed
335
+ - `build_module_command()` respects the global verbose help flag to control built-in option visibility.
336
+ - `--sandbox` is now always hidden from help (not yet implemented). Only four built-in options (`--input`, `--yes`, `--large-input`, `--format`) toggle with `--verbose`.
337
+ - Improved built-in option descriptions for clarity.
338
+
339
+ ---
340
+
341
+ ## [0.3.1] - 2026-03-27
342
+
343
+ ### Added
344
+
345
+ - **DisplayResolver integration** — `__main__.py` integrates `DisplayResolver` from `apcore-toolkit` (optional) when `--binding` option is provided; gracefully skipped when not installed.
346
+ - **`init` to `BUILTIN_COMMANDS`** — `init` subcommand is now registered in the builtin commands set.
347
+ - **`APCORE_AUTH_API_KEY` to man page** — environment variable documented in generated roff man page.
348
+ - **Grouped shell completion with `_APCORE_GRP`** — bash/zsh/fish completion scripts now support two-level group/command completion via the `_APCORE_GRP` environment variable (`shell.py`).
349
+ - **Path traversal validation for `--dir` in `init` command** — rejects paths containing `..` segments to prevent directory escape (`init_cmd.py`).
350
+
351
+ ### Fixed
352
+
353
+ - **`RegistryWriter` API call** — constructor now called without parameters; fixes `TypeError` introduced by upstream API change.
354
+
355
+ ### Changed
356
+
357
+ - `apcore` dependency bumped to `>=0.14.0`.
358
+
359
+ ---
360
+
361
+ ## [0.3.0] - 2026-03-23
362
+
363
+ ### Added
364
+
365
+ - **Display overlay routing** (§5.13) — `LazyModuleGroup` now reads `metadata["display"]["cli"]` for alias and description when building the command list and routing `get_command()`. Commands are exposed under their CLI alias instead of raw module_id.
366
+ - `_alias_map`: built from `metadata["display"]["cli"]["alias"]` (with module_id fallback), enabling `apcore-cli alias-name` invocation.
367
+ - `_descriptor_cache`: populated during alias map build to avoid double `registry.get_definition()` calls in `get_command()`.
368
+ - `_alias_map_built` flag only set on successful build, allowing retry after transient registry errors.
369
+ - **Display overlay in JSON output** — `format_module_list(..., "json")` now reads `metadata["display"]["cli"]` for `id`, `description`, and `tags`, consistent with the table output branch.
370
+
371
+ ### Changed
372
+
373
+ - `_ERROR_CODE_MAP.get(error_code, 1)`: guarded with `isinstance(error_code, str)` to prevent `None`-key lookup.
374
+ - Runtime companion: `apcore-toolkit >= 0.4.0` enables `DisplayResolver` and `ConventionScanner` (graceful fallback when not installed).
375
+
376
+ ### Tests
377
+
378
+ - `TestDisplayOverlayAliasRouting` (6 tests): `list_commands` uses CLI alias, `get_command` by alias, cache hit path, module_id fallback, `build_module_command` alias and description.
379
+ - `test_format_list_json_uses_display_overlay`: JSON output uses display overlay alias/description/tags.
380
+ - `test_format_list_json_falls_back_to_scanner_when_no_overlay`: JSON output falls back to scanner values.
381
+
382
+ ### Added (Grouped Commands — FE-09)
383
+
384
+ - **`GroupedModuleGroup(LazyModuleGroup)`** — organizes modules into nested `click.Group` subcommands based on namespace prefixes. Auto-groups by first `.` segment, with `display.cli.group` override from binding.yaml.
385
+ - `_resolve_group()` — 3-tier group resolution: explicit `display.cli.group` > first `.` segment of CLI alias > top-level.
386
+ - `_build_group_map()` — lazy, idempotent group map builder with builtin collision detection and shell-safe group name validation.
387
+ - `format_help()` — collapsed root help with Commands, Modules, and Groups sections (with command counts).
388
+ - **`_LazyGroup(click.Group)`** — nested group that lazily builds subcommands from module descriptors.
389
+ - **`list --flat` flag** — opt-in flat display mode for `list` command; default is now grouped display.
390
+ - **`format_grouped_module_list()`** — Rich table output grouped by namespace.
391
+ - **Updated shell completions** — bash/zsh/fish completion scripts handle two-level group/command structure.
392
+
393
+ ### Changed (Grouped Commands)
394
+
395
+ - `create_cli()` now uses `GroupedModuleGroup` instead of `LazyModuleGroup`.
396
+
397
+ ### Tests (Grouped Commands)
398
+
399
+ - 48 new tests: `TestResolveGroup` (8+), `TestBuildGroupMap` (5+), `TestGroupedModuleGroupRouting` (7), `TestLazyGroupInner` (4), `TestGroupedHelpDisplay` (5), `TestCreateCliGrouped` (1), `TestGroupedE2E` (5), `TestGroupedDiscovery` (7+), `TestGroupedCompletion` (6).
400
+
401
+ ### Added (Convention Module Discovery — §5.14)
402
+
403
+ - **`apcore-cli init module <id>`** — scaffolding command with `--style` (decorator, convention, binding) and `--description` options. Generates module templates in the appropriate directory.
404
+ - **`--commands-dir` CLI option** — path to a convention commands directory. When set, `ConventionScanner` from `apcore-toolkit` scans for plain functions and registers them as modules.
405
+
406
+ ### Tests (Convention Module Discovery)
407
+
408
+ - 6 new tests in `tests/test_init_cmd.py` covering all three styles and options.
409
+
410
+ ---
411
+
412
+ ## [0.2.2] - 2026-03-22
413
+
414
+ ### Changed
415
+ - Rebrand: aipartnerup → aiperceivable
416
+
417
+ ## [0.2.1] - 2026-03-19
418
+
419
+ ### Changed
420
+ - Help text truncation limit increased from 200 to 1000 characters (configurable via `cli.help_text_max_length` config key)
421
+ - `_extract_help`: added `max_length: int = 1000` parameter (`schema_parser.py`)
422
+ - `schema_to_click_options`: added `max_help_length: int = 1000` parameter (`schema_parser.py`)
423
+ - `build_module_command`: added `help_text_max_length: int = 1000` parameter, threaded through to schema parser (`cli.py`)
424
+ - `LazyModuleGroup`: constructor accepts `help_text_max_length: int = 1000`, passes to `build_module_command` (`cli.py`)
425
+ - `create_cli`: resolves `cli.help_text_max_length` from `ConfigResolver` and passes to `LazyModuleGroup` (`__main__.py`)
426
+ - `format_exec_result`: nested dict/list values in table mode now rendered with `json.dumps` instead of `str()` (`output.py`)
427
+
428
+ ### Added
429
+ - `cli.help_text_max_length` config key (default: 1000) in `ConfigResolver.DEFAULTS` (`config.py`)
430
+ - `APCORE_CLI_HELP_TEXT_MAX_LENGTH` environment variable support for configuring help text max length
431
+ - `test_help_truncation_default`: tests default 1000-char truncation
432
+ - `test_help_no_truncation_within_limit`: tests no truncation at 999 chars
433
+ - `test_help_truncation_custom_max`: tests custom max_length parameter
434
+ - 263 tests (up from 261)
435
+
436
+ ## [0.2.0] - 2026-03-16
437
+
438
+ ### Added
439
+ - `APCORE_CLI_LOGGING_LEVEL` env var — CLI-specific log level that takes priority over `APCORE_LOGGING_LEVEL`; 3-tier precedence: `--log-level` flag > `APCORE_CLI_LOGGING_LEVEL` > `APCORE_LOGGING_LEVEL` > `WARNING` (`__main__.py`)
440
+ - `test_cli_logging_level_takes_priority_over_global` — verifies `APCORE_CLI_LOGGING_LEVEL=DEBUG` wins over `APCORE_LOGGING_LEVEL=ERROR`
441
+ - `test_cli_logging_level_fallback_to_global` — verifies fallback when CLI-specific var is unset
442
+ - `test_builtin_name_collision_exits_2` — schema property named `format` (or other reserved names) causes `build_module_command` to exit 2
443
+ - `test_exec_result_table_format` — `--format table` renders Rich Key/Value table to stdout
444
+ - `test_bash_completion_quotes_prog_name_in_directive` — verifies `shlex.quote()` applied to `complete -F` directive, not just embedded subshell
445
+ - `test_zsh_completion_quotes_prog_name_in_directives` — verifies `compdef` line uses quoted prog_name
446
+ - `test_fish_completion_quotes_prog_name_in_directives` — verifies `complete -c` lines use quoted prog_name
447
+ - 17 new tests (244 → 261 total)
448
+
449
+ ### Changed
450
+ - `--log-level` accepted choices: `WARN` → `WARNING` (`__main__.py`)
451
+ - `schema_to_click_options`: schema-derived options now always have `required=False`; required fields marked `[required]` in help text instead of Click enforcement — allows `--input -` STDIN to supply required values without Click rejecting first (`schema_parser.py`)
452
+ - `format_exec_result`: now routes through `resolve_format()` and renders Rich table when `--format table` is specified; previously ignored its `format` parameter (`output.py`)
453
+ - `_generate_bash_completion`, `_generate_zsh_completion`, `_generate_fish_completion`: `shlex.quote()` applied to ALL prog_name positions in generated scripts (complete directives, compdef, complete -c), not only embedded subshell commands (`shell.py`)
454
+ - `check_approval`: removed unused `ctx: click.Context` parameter (`approval.py`)
455
+ - `set_audit_logger`: broadened type annotation from `AuditLogger` to `AuditLogger | None` (`cli.py`)
456
+ - `collect_input`: simplified redundant condition `if not raw or raw_size == 0:` → `if not raw:` (`cli.py`)
457
+ - Example `Input` models: all 7 modules updated with `Field(description=...)` on every field so CLI `--help` shows descriptive text for each flag
458
+
459
+ ### Fixed
460
+ - **`--input -` STDIN blocked by Click required enforcement**: `schema_to_click_options` was generating `required=True` Click options; Click validated before the callback ran, rejecting STDIN-only invocations. Resolved by always using `required=False` and delegating required validation to `jsonschema.validate()` after input collection. Fixes all 6 `TestRealStdinPiping` failures.
461
+ - **`--log-level` had no effect**: `logging.basicConfig()` is a no-op after the first call; subsequent `create_cli()` calls in tests retained the prior handler's level. Fixed by calling `logging.getLogger().setLevel()` explicitly after `basicConfig()`.
462
+ - **`test_log_level_flag_takes_effect` false pass**: `--help` is an eager flag that exits before the group callback, so `--log-level DEBUG --help` never applied the log level. Test updated to use `completion bash` subcommand instead.
463
+ - **Shell completion directives not shell-safe**: prog names with spaces or special characters were unquoted in `complete -F`, `compdef`, and `complete -c` lines. Fixed by assigning `quoted = shlex.quote(prog_name)` and using it in all directive positions.
464
+ - **Audit `set_audit_logger(None)` type error**: type annotation rejected `None`; broadened to `AuditLogger | None`.
465
+ - **Test logger level leakage**: tests modifying root logger level affected subsequent tests; fixed with `try/finally` that restores the original level.
466
+
467
+ ### Security
468
+ - `AuditLogger._hash_input`: now uses `secrets.token_bytes(16)` per-invocation salt before hashing, preventing cross-invocation input correlation via SHA-256 rainbow tables
469
+ - `build_module_command`: added reserved-name collision guard — exits 2 if a schema property (`input`, `yes`, `large_input`, `format`, `sandbox`) conflicts with a built-in CLI option name
470
+ - `_prompt_with_timeout` (SIGALRM path): wrapped in `try/finally` to guarantee signal handler restoration regardless of exit path
471
+
472
+ ## [0.1.0] - 2026-03-15
473
+
474
+ ### Added
475
+ - `--sandbox` flag for subprocess-isolated module execution (FE-05)
476
+ - `ModuleExecutionError` exception class for sandbox failures
477
+ - Windows approval timeout support via `threading.Timer` + `ctypes` (FE-03)
478
+ - Approval timeout clamping to 1..3600 seconds range (FE-03)
479
+ - Tag format validation (`^[a-z][a-z0-9_-]*$`) in `list --tag` (FE-04)
480
+ - `cli.auto_approve` config key with `False` default (FE-07)
481
+ - Extensions directory readability check with exit code 47 (FE-01)
482
+ - Missing required property warning in schema parser (FE-02)
483
+ - DEBUG log `"Loading extensions from {path}"` before registry discovery (FE-01)
484
+ - `TYPE_CHECKING` imports for proper type annotations (`Registry`, `Executor`, `ModuleDescriptor`, `ConfigResolver`, `AuditLogger`)
485
+ - `_get_module_id()` helper for `canonical_id`/`module_id` resolution
486
+ - `APCORE_AUTH_API_KEY` and `APCORE_CLI_SANDBOX` to README environment variables table
487
+ - `--sandbox` to README module execution options table
488
+ - CHANGELOG.md
489
+ - Core Dispatcher (FE-01): `LazyModuleGroup`, `build_module_command`, `collect_input`, `validate_module_id`
490
+ - Schema Parser (FE-02): `schema_to_click_options`, `_map_type`, `_extract_help`, `reconvert_enum_values`
491
+ - Ref Resolver (FE-02): `resolve_refs`, `_resolve_node` with `$ref`, `allOf`, `anyOf`, `oneOf` support
492
+ - Config Resolver (FE-07): `ConfigResolver` with 4-tier precedence (CLI > Env > File > Default)
493
+ - Approval Gate (FE-03): `check_approval`, `_prompt_with_timeout` with TTY detection and Unix SIGALRM
494
+ - Discovery (FE-04): `list` and `describe` commands with tag filtering and TTY-adaptive output
495
+ - Output Formatter (FE-08): `format_module_list`, `format_module_detail`, `format_exec_result` with Rich rendering
496
+ - Security Manager (FE-05): `AuthProvider`, `ConfigEncryptor` (keyring + AES-256-GCM), `AuditLogger` (JSON Lines), `Sandbox` (subprocess isolation)
497
+ - Shell Integration (FE-06): bash/zsh/fish completion generators, roff man page generator
498
+ - 8 example modules: `math.add`, `math.multiply`, `text.upper`, `text.reverse`, `text.wordcount`, `sysutil.info`, `sysutil.env`, `sysutil.disk`
499
+ - 244 tests (unit, integration, end-to-end)
500
+ - CI workflow with pytest and coverage
501
+ - Pre-commit hooks configuration
@@ -28,15 +28,16 @@
28
28
 
29
29
  - Python >= 3.11
30
30
  - Key dependencies: click >= 8.1, rich >= 13.0, jsonschema >= 4.20, pyyaml >= 6.0, keyring >= 24, cryptography >= 41
31
- - Runtime: apcore >= 0.19.0 (v0.7.0 bump, was 0.17.1)
32
- - Optional: apcore-toolkit >= 0.4 (install via `pip install apcore-cli[toolkit]`)
31
+ - Runtime: apcore >= 0.21.0 (v0.9.0 bump, was 0.19.0 at v0.7.0)
32
+ - Runtime (required, v0.9.0+): apcore-toolkit >= 0.7.0. The `[toolkit]` extras group is retained as a no-op for downstream install scripts.
33
33
  - Dev: pytest, pytest-asyncio, pytest-cov, mypy, ruff
34
34
 
35
- ## v0.7.0 Conventions
35
+ ## v0.9.0 Conventions
36
36
 
37
37
  - Public surface (`__init__.py`): `__version__`, `create_cli`, `ExposureFilter`,
38
38
  `ApcliGroup`, `ApcliMode`, `RESERVED_GROUP_NAMES`, `CliApprovalHandler`,
39
39
  `resolve_refs`, `schema_to_click_options`, `format_exec_result`,
40
+ `set_verbose_help`, `set_docs_url`, `set_audit_logger`,
40
41
  `ConfigResolver`, `AuditLogger`, `AuthProvider`, `ConfigEncryptor`, `Sandbox`,
41
42
  plus error classes (AuthenticationError, ConfigDecryptionError,
42
43
  ModuleExecutionError, ApprovalTimeoutError, ApprovalDeniedError).
@@ -45,9 +46,16 @@
45
46
  - ExposureFilter + `expose=` kwarg on create_cli (FE-12).
46
47
  - `extra_commands=[...]` kwarg on create_cli as the FE-11 extension point (with
47
48
  collision detection against RESERVED_GROUP_NAMES — `BUILTIN_COMMANDS` retired v0.7.0).
49
+ - `builtin_group_name="apcli"` kwarg on create_cli (v0.8.0+) for embed-API renaming (FE-13).
48
50
  - Default click Group class is `GroupedModuleGroup` (multi-level grouping since v0.3.0).
49
- - All apcore-cli commands live under the `apcli` group (FE-13). Deprecation shims
50
- remain at root level for back-compat until v0.8.
51
+ - All apcore-cli commands live exclusively under the `apcli` group (FE-13). Root-level
52
+ deprecation shims were removed in v0.9.0 (deprecated in v0.8.x).
53
+ - `--verbose` global flag renamed to `--all-options` in v0.9.0; `set_verbose_help(bool)` is
54
+ the programmatic equivalent (internal name retained for back-compat; Rust uses `set_all_options_help`).
55
+ - `verbose` removed from reserved schema property names set in v0.9.0; modules may now
56
+ define `verbose: boolean` freely.
57
+ - apcore-toolkit promoted from optional to REQUIRED runtime dep in v0.9.0; csv/jsonl/markdown/skill
58
+ output formats now toolkit-delegated (ADR-09).
51
59
  - `system_cmd` module registers runtime system commands (health/usage/enable/disable/
52
60
  reload/config) — FE-11.
53
61
  - `strategy` module registers describe-pipeline + --strategy flag — FE-11.