7.6 KiB
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; introducebin/rtickets-live; makebin/rtlthe short alias using the same resolver. Keeprtdexplicitly 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-fileorREDMINE_ENV_FILE; otherwise search the checkout.env, legacybin/.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, projectcustomer-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_CACHEselects a common root, default<checkout>/cachefor live wrappers and<checkout>/cache-devfor development. The existing live ticket default remains<checkout>/cache/redmine_ticketsfor customer-service.- Tickets:
<root>/redmine_ticketsfor the default production customer-service project; other endpoints/projects use distinct deterministic paths unless explicitly overridden. Exact--cache-dirwins. Contacts CLI:<root>/redmine_contacts/<host>_<project>.json; exact--cachewins 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-filebranches. No secret goes in a subprocess argument or diagnostic output.
NMI contract
- Improve
mrscClient-php/bin/nmiand shared client configuration only as necessary. Preserve existing uncommitted edits inbin/nmi,AgentApi.php,MutationGuard.php, and.gitignore. nmi --show-config/nmi --doctorare 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_FILEis optional when valid exported credentials exist; explicit file errors remain fatal.rtickets-live --doctor-nmidelegates 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
- 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. Implementredmine_cli_runtime.pyand wrapper entries. Extend completion and operator docs. - 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.
- Add
tests/test_cache_concurrency.pywith subprocess writers and killed lock holders. Implement sharedredmine_cache_io.pyand integrate ticket state store, record writes, ticket contacts cache, and full CRM snapshot writes. Run existing focused cache tests. - 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.
- 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.
- 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.