Files
redmine/docs/superpowers/specs/2026-10-18-cli-runtime-design.md
T
2026-10-18 02:15:58 -04:00

7.6 KiB

Portable Redmine and NMI CLI runtime

Approved direction

Improve the existing tools without migrating caches or weakening mutation gates. Preserve existing unrelated working-tree changes. No package installations, production mutations, cron changes, credential rewrites, or cache migration.

Commands and configuration

  • Keep bin/rcontacts-live; introduce bin/rtickets-live; make bin/rtl the short alias using the same resolver. Keep rtd explicitly development-oriented.
  • Centralize Redmine wrapper setup in a Python standard-library module. Shell entry points locate their checkout reliably and invoke it. Resolve paths independently of the calling working directory.
  • Precedence: explicit CLI options > exported environment > selected env file > documented defaults. Empty required values are missing. Preserve legacy JSON/YAML ticket config support with documented interactions; explicit CLI still wins.
  • Select env file explicitly with --env-file or REDMINE_ENV_FILE; otherwise search the checkout .env, legacy bin/.env, then ~/.redmine.live (development uses a distinct file, never silently loads live credentials). Report searched/selected paths. An explicitly selected missing/malformed file is an error, not permission to fall back. Parse key/value data, never execute arbitrary shell code.
  • Expose --show-config (redacted resolved values and sources, no API calls) and --doctor (offline validation with nonzero status for problems). Help works without credentials; cache-only operations retain current credential requirements rather than globally demanding credentials at startup.
  • Default live Redmine URL: https://rm.ldrprep.com, project customer-service; default NMI mode production. Explicit dev selection is visible on stderr. Normal machine-readable stdout is not polluted by banners.
  • Preserve existing --apply, --allow-prod-writes, customer-email confirmations and NMI mutation gates.

Cache contract

  • REDMINE_BASE_CACHE selects a common root, default <checkout>/cache for live wrappers and <checkout>/cache-dev for development. The existing live ticket default remains <checkout>/cache/redmine_tickets for customer-service.
  • Tickets: <root>/redmine_tickets for the default production customer-service project; other endpoints/projects use distinct deterministic paths unless explicitly overridden. Exact --cache-dir wins. Contacts CLI: <root>/redmine_contacts/<host>_<project>.json; exact --cache wins and must be a file path.
  • Tickets' internal contacts mirror remains <ticket-cache>/contacts; it is a different schema from the contacts CLI snapshot. Diagnostics name both and explain this distinction. Printed handoffs retain resolved endpoint, project, contacts cache, and selected configuration context without printing credentials.
  • Reuse the resolved API key consistently, including --api-key-file branches. No secret goes in a subprocess argument or diagnostic output.

NMI contract

  • Improve mrscClient-php/bin/nmi and shared client configuration only as necessary. Preserve existing uncommitted edits in bin/nmi, AgentApi.php, MutationGuard.php, and .gitignore.
  • nmi --show-config / nmi --doctor are offline and use the same configuration resolver as actual commands. Also support natural command forms if convenient. Report mode, endpoint, selected config/env paths, key names/source/presence (not values).
  • Explicit CLI wins over exported environment; exported variables win over env-file values. Canonical production keys include NMI_ENDPOINT_PRODUCTION, NMI_MRSC_ACCESS_CODE_PRODUCTION, NMI_SECRETKEY_PRODUCTION; existing generic/alternate names stay supported and are documented.
  • Default production mode is consistent across direct CLI and Redmine bridge; dev must be explicit and visible. Discover default env file relative to checkout (examples/modern/.env, with documented user/repo candidates), not /home/jason.
  • Redmine bridge discovers NMI via NMI_CLI, PATH, or sibling checkout. NMI_ENV_FILE is optional when valid exported credentials exist; explicit file errors remain fatal. rtickets-live --doctor-nmi delegates to the actual NMI diagnostic path without API requests, using the same command/env as real lookups. Bound subprocess timeouts and report nonzero failures clearly.

Concurrent cache safety

  • Use standard-library POSIX advisory locking on persistent lock files; do not infer liveness from the existence of a file and do not unlink a live lock inode.
  • Protect read-modify-write transactions by taking the lock before loading current manifest state. Do not merely lock the final write of an old in-memory manifest.
  • Use unique temporary files in the destination directory, flush/fsync as appropriate, then atomic replace for manifests and record JSON. Clean up failed temporary files. OS closes/releases locks on exit, including forced termination.
  • A contending writer reports that it is waiting on stderr, then fails with an actionable error on a bounded configurable timeout (REDMINE_LOCK_TIMEOUT, default 60 seconds). Invalid timeouts fail explicitly. Avoid whole-session TUI locks.
  • Full contacts snapshots require serialization across fetch/write where necessary to avoid an older fetch replacing a newer one. Preserve snapshot semantics (filtered caches are deliberate), not an accidental append-only merge.
  • No manifest chunking or SQLite migration in this work. Regression tests cover concurrent distinct updates, reader-safe atomic replacement, timeout visibility, and process-death release.

Implementation plan

  1. Add failing clean-environment wrapper tests in tests/test_cli_runtime.py: help, missing/empty config, env precedence, paths with spaces, working-directory independence, cache override, redaction, explicit dev, and alias behavior. Implement redmine_cli_runtime.py and wrapper entries. Extend completion and operator docs.
  2. Add NMI PHP subprocess tests (standalone PHP or Python unittest; no new packages). Exercise actual CLI with test-only config and no network. Implement diagnostic/config changes and update completion/help. Ensure pre-existing mutation logic is unchanged.
  3. Add tests/test_cache_concurrency.py with subprocess writers and killed lock holders. Implement shared redmine_cache_io.py and integrate ticket state store, record writes, ticket contacts cache, and full CRM snapshot writes. Run existing focused cache tests.
  4. Integrate Redmine-to-NMI profile resolution and diagnostics after NMI CLI contract exists. Test with a fake NMI executable recording argv/env; separately run real NMI offline doctor. Correct contacts handoff propagation and API-key-file consistency.
  5. Run focused existing suites plus new tests, Python compile checks, PHP lint, shell syntax/completion smoke probes. Execute wrappers from an unrelated directory under a minimal environment using isolated temporary config/cache fixtures. Never touch real production data for tests.
  6. Review diffs, preserve baseline edits, record exact verification and any residual limitations in docs/cli_runtime.md; update relevant procedural skill.

Acceptance checklist

  • Contacts and tickets wrappers run/help cleanly and consistently.
  • Missing configuration explains required variables and lookup paths.
  • Cache root and per-tool override semantics are verified.
  • Tickets contact handoffs preserve runtime context and expose distinct formats.
  • Direct NMI and bridge agree on mode, credentials and file precedence.
  • Offline bridge preflight exercises actual NMI resolver.
  • Concurrent cache updates and abandoned-lock recovery pass real process tests.
  • Existing safety gates remain unchanged.
  • Usage, completion and verification results are documented.