Skip to content

feat: add CLI issue state command#281

Open
duncanleo wants to merge 1 commit into
mainfrom
codex/cli-derived-state
Open

feat: add CLI issue state command#281
duncanleo wants to merge 1 commit into
mainfrom
codex/cli-derived-state

Conversation

@duncanleo
Copy link
Copy Markdown
Member

@duncanleo duncanleo commented May 30, 2026

Summary

  • Split the CLI package into smaller argument, usage, and command modules while keeping index.ts as the thin dispatcher.
  • Added mrtdown issue state <id> to display an issue bundle's current derived state as formatted JSON.
  • Exposed @mrtdown/triage/helpers/deriveCurrentState as a narrow package subpath and wired the CLI package/test script to build against it.

Impact

CLI maintainability improves by moving command-specific logic out of the single large entrypoint. Operators can now inspect an issue's derived service/facility state directly from the data repository without writing an ad hoc script.

Validation

  • npm run build:cli
  • npm run test:cli
  • npm run lint
  • npm run check:boundaries

Summary by CodeRabbit

  • New Features
    • Added CLI commands for creating entities and issues with various options
    • Added CLI commands for data management—validate, list, and view stored entities
    • Added CLI command to display current issue state information
    • Added CLI commands for schematic map operations including list, show, select, statistics, diff, generate, and preview
    • Added CLI commands for building and outputting manifests and pages indexes

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 30, 2026

📝 Walkthrough

Walkthrough

The CLI entrypoint is refactored from a monolithic module into a thin dispatcher that imports command handlers from separate modules. New argument parsing utilities extract flags, options, and positional tokens from argv. Nine command modules implement validate, list, show, create, id, issue state, manifest, pages-index, and schematic-map operations. The schematic-map module includes subcommands for version selection, coordinate counting, snapshot diffing with geometry/topology comparisons, and SVG preview rendering.

Changes

CLI Refactoring and Feature Expansion

Layer / File(s) Summary
CLI type system and argument parsing
packages/cli/src/types.ts, packages/cli/src/args.ts
New types define CliIO (stdout/stderr callbacks), GlobalOptions (cwd, dataDir), and ParsedArgs (globals + command tokens). Parsing functions extract --data-dir, validate collection/scope names, and mutate argv to remove consumed flags and options.
CLI entrypoint and command routing
packages/cli/src/index.ts, packages/cli/src/usage.ts
Refactored entrypoint becomes a thin dispatcher that imports per-command handlers from modules; issue verb added to route to runIssue. Usage string documents all supported subcommands and shared --data-dir flag.
Data validation, listing, and manifest export commands
packages/cli/src/commands/data.ts, packages/cli/src/commands/manifest.ts
runValidate collects repeated --scope options and reports validation results; runList outputs entity IDs newline-delimited; runShow reads and JSON-prints entities; runManifest and runPagesIndex build manifests and export as JSON or rendered HTML, optionally writing files.
Entity creation and issue state commands
packages/cli/src/commands/create.ts, packages/cli/src/commands/issue.ts
runCreate branches on entity kind to create issues (requiring date/title) or generic entities (requiring file path), writing bundles to data directory; runId prints computed issue IDs; runIssue state loads issue bundles and outputs derived current state via deriveCurrentState.
Schematic map query, analysis, diff, generation, and preview
packages/cli/src/commands/schematicMap.ts
Implements seven subcommands: list/show for manifest/versions/constraints, select picks latest version by effective date, stats counts coordinate classes and constraint types, diff compares two snapshots for frame/layer/entity/geometry/topology changes and coordinate deltas, generate produces new snapshots with optional manifest updates, preview renders segment geometry and station/label placement as SVG.
Package configuration and test coverage
packages/cli/package.json, packages/triage/package.json, packages/cli/src/index.test.ts
CLI test script now builds @mrtdown/triage before Vitest; @mrtdown/triage dependency bumped to 2.0.0-alpha.28 and exports new ./helpers/deriveCurrentState subpath; new test verifies issue state command outputs derived service effects and impact event IDs.

Sequence Diagram

sequenceDiagram
  participant CliUser as User
  participant runSchematicMap
  participant VersionOps as Version Selection
  participant DiffEngine as Snapshot Diffing
  participant SvgRenderer as SVG Preview
  participant FileStore as Filesystem
  CliUser->>runSchematicMap: schematic-map list/show/select/stats/diff/generate/preview
  alt list or show
    runSchematicMap->>FileStore: read manifest/versions/constraints
  else select
    runSchematicMap->>VersionOps: normalize date, find latest version
    VersionOps->>FileStore: load selected version
  else stats
    runSchematicMap->>FileStore: read snapshot and optional constraints
    runSchematicMap->>runSchematicMap: count coordinates by class
  else diff
    runSchematicMap->>FileStore: read two snapshots
    runSchematicMap->>DiffEngine: compare frame, layers, entities, geometry
    DiffEngine->>runSchematicMap: semantic diff result
  else generate
    runSchematicMap->>FileStore: generate snapshot (external)
    runSchematicMap->>FileStore: optionally write snapshot and update manifest
  else preview
    runSchematicMap->>FileStore: read snapshot
    runSchematicMap->>SvgRenderer: render SVG with segment geometry, stations, labels
    runSchematicMap->>FileStore: optionally write SVG file
  end
  runSchematicMap->>CliUser: stdout result or file path
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • foldaway/mrtdown-data#223: The main PR refactors and extends the CLI entrypoint (packages/cli/src/index.ts) with new command routing and modular handlers, while the retrieved PR introduced the original runCli entrypoint and command dispatch logic.
  • foldaway/mrtdown-data#245: The main PR exports deriveCurrentState from @mrtdown/triage and adds a CLI issue state command that calls it; the retrieved PR implements an LLM tool that also uses deriveCurrentState for issue analysis.
  • foldaway/mrtdown-data#225: The main PR adds the issue state CLI command that depends on deriveCurrentState (a triage helper), and updates @mrtdown/triage dependency and exports accordingly.

Poem

🐰 Hop, skip, and parse those CLI args—
The rabbit refactors monolith to modular stars,
Schematic diffs now dance in SVG grace,
Nine commands find their proper place!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding a new CLI command for displaying issue state. It directly reflects the primary feature introduced across the changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/cli-derived-state

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@duncanleo duncanleo changed the title [codex] feat: add CLI issue state command feat: add CLI issue state command May 30, 2026
@duncanleo duncanleo marked this pull request as ready for review May 30, 2026 03:46
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
packages/cli/src/commands/issue.ts (1)

10-23: 💤 Low value

Optional: clarify the fallback error message.

issue requires state is thrown both when no action is given and when an unrecognized action is passed (e.g. mrtdown issue foo). A message echoing the invalid action would aid users.

♻️ Suggested wording
-  throw new Error('issue requires state');
+  throw new Error(
+    `Unsupported issue action${action ? `: ${action}` : ''}. Expected 'state'.`,
+  );
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/cli/src/commands/issue.ts` around lines 10 - 23, The fallback error
message is ambiguous — update the final throw to include the actual invalid or
missing action for clarity: inspect the local variable action (from const action
= args.shift()) and throw a descriptive error such as "unknown issue action
'<action>'" when action is present or "issue requires state: no action provided"
when action is undefined; keep the existing branch that handles
'state'/'current-state' and reference variables action, readIssueBundle and
deriveCurrentState to locate the logic to modify.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/cli/src/commands/create.ts`:
- Around line 49-58: runId currently builds the issue id using only the title
(calling buildIssueId(date, title)) which diverges from runCreate that uses slug
?? title; fix by reading the optional --slug via readOption(args, '--slug')
(like runCreate does) and pass slug ?? title to buildIssueId so the printed id
matches what create issue will produce; update the use of readOption and the
call to buildIssueId accordingly (symbols: runId, readOption, buildIssueId,
--slug, --title).

In `@packages/cli/src/commands/schematicMap.ts`:
- Around line 29-38: The function effectiveDateFromDate currently accepts any
two-digit day in the YYYY-MM-DD branch and then slices off the day, allowing
impossible dates like 2026-02-99; update that branch to validate the full date
is a real calendar date before slicing. Specifically, in effectiveDateFromDate
when the /^\d{4}-(0[1-9]|1[0-2])-\d{2}$/ case matches, parse the year, month and
day and confirm they form a valid date (e.g., construct a Date and verify year,
month and day round-trip) and only then return value.slice(0,7); otherwise throw
the same error.

---

Nitpick comments:
In `@packages/cli/src/commands/issue.ts`:
- Around line 10-23: The fallback error message is ambiguous — update the final
throw to include the actual invalid or missing action for clarity: inspect the
local variable action (from const action = args.shift()) and throw a descriptive
error such as "unknown issue action '<action>'" when action is present or "issue
requires state: no action provided" when action is undefined; keep the existing
branch that handles 'state'/'current-state' and reference variables action,
readIssueBundle and deriveCurrentState to locate the logic to modify.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 9d99c34c-ca40-482e-bfcf-1a54605534bb

📥 Commits

Reviewing files that changed from the base of the PR and between d1a1311 and e344d26.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (12)
  • packages/cli/package.json
  • packages/cli/src/args.ts
  • packages/cli/src/commands/create.ts
  • packages/cli/src/commands/data.ts
  • packages/cli/src/commands/issue.ts
  • packages/cli/src/commands/manifest.ts
  • packages/cli/src/commands/schematicMap.ts
  • packages/cli/src/index.test.ts
  • packages/cli/src/index.ts
  • packages/cli/src/types.ts
  • packages/cli/src/usage.ts
  • packages/triage/package.json

Comment on lines +49 to +58
export async function runId(args: string[], io: CliIO): Promise<number> {
const kind = args.shift();
if (kind !== 'issue') {
throw new Error('Only id issue is supported');
}
const date = readOption(args, '--date', { required: true }) as string;
const title = readOption(args, '--title', { required: true }) as string;
io.stdout(buildIssueId(date, title));
return 0;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

id issue ignores --slug, diverging from create issue.

runCreate derives the id via buildIssueId(date, slug ?? title), but runId only uses title. When an operator passes --slug, the id printed by id issue will not match the id that create issue actually produces, defeating the purpose of previewing the id.

🐛 Proposed fix to honor --slug
 export async function runId(args: string[], io: CliIO): Promise<number> {
   const kind = args.shift();
   if (kind !== 'issue') {
     throw new Error('Only id issue is supported');
   }
   const date = readOption(args, '--date', { required: true }) as string;
   const title = readOption(args, '--title', { required: true }) as string;
-  io.stdout(buildIssueId(date, title));
+  const slug = readOption(args, '--slug');
+  io.stdout(buildIssueId(date, slug ?? title));
   return 0;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export async function runId(args: string[], io: CliIO): Promise<number> {
const kind = args.shift();
if (kind !== 'issue') {
throw new Error('Only id issue is supported');
}
const date = readOption(args, '--date', { required: true }) as string;
const title = readOption(args, '--title', { required: true }) as string;
io.stdout(buildIssueId(date, title));
return 0;
}
export async function runId(args: string[], io: CliIO): Promise<number> {
const kind = args.shift();
if (kind !== 'issue') {
throw new Error('Only id issue is supported');
}
const date = readOption(args, '--date', { required: true }) as string;
const title = readOption(args, '--title', { required: true }) as string;
const slug = readOption(args, '--slug');
io.stdout(buildIssueId(date, slug ?? title));
return 0;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/cli/src/commands/create.ts` around lines 49 - 58, runId currently
builds the issue id using only the title (calling buildIssueId(date, title))
which diverges from runCreate that uses slug ?? title; fix by reading the
optional --slug via readOption(args, '--slug') (like runCreate does) and pass
slug ?? title to buildIssueId so the printed id matches what create issue will
produce; update the use of readOption and the call to buildIssueId accordingly
(symbols: runId, readOption, buildIssueId, --slug, --title).

Comment on lines +29 to +38
function effectiveDateFromDate(value: string): string {
if (/^\d{4}-(0[1-9]|1[0-2])$/.test(value)) {
return value;
}

if (/^\d{4}-(0[1-9]|1[0-2])-\d{2}$/.test(value)) {
return value.slice(0, 7);
}

throw new Error(`Expected YYYY-MM or YYYY-MM-DD, got: ${value}`);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Reject impossible YYYY-MM-DD inputs.

The second branch accepts any two-digit day, so values like 2026-02-99 or 2026-04-00 are silently normalized to 2026-02 / 2026-04 instead of failing. That makes typos look valid and can select the wrong version. Tighten validation before slicing off the day.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/cli/src/commands/schematicMap.ts` around lines 29 - 38, The function
effectiveDateFromDate currently accepts any two-digit day in the YYYY-MM-DD
branch and then slices off the day, allowing impossible dates like 2026-02-99;
update that branch to validate the full date is a real calendar date before
slicing. Specifically, in effectiveDateFromDate when the
/^\d{4}-(0[1-9]|1[0-2])-\d{2}$/ case matches, parse the year, month and day and
confirm they form a valid date (e.g., construct a Date and verify year, month
and day round-trip) and only then return value.slice(0,7); otherwise throw the
same error.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant