Skip to content

Compact mutation output + smarter unknown-flag recovery (pm-ch59, pm-8nyc) - #72

Merged
unbraind merged 11 commits into
mainfrom
agent-ux-compact-mutation-output-and-flag-recovery
May 26, 2026
Merged

unbraind merged 11 commits into
mainfrom
agent-ux-compact-mutation-output-and-flag-recovery

Conversation

@unbraind

Copy link
Copy Markdown
Owner

Two agent-UX / token-efficiency items under pm-rnpb, combined into one PR. Both are implemented, fully covered, and dogfooded.

pm-ch59 — Compact mutation output

Every mutation echoed a verbose changed_fields array (~50% of a create payload), redundant with the item echo above it.

  • New pure helper src/core/output/mutation-projection.ts → projectMutationResult() replaces changed_fields with a deterministic changed_field_count (no lost mutation evidence).
  • Global --no-changed-fields CLI flag (threaded GlobalOptions → OutputOptions → printResult) — applies uniformly to every mutation command.
  • MCP agent path (create/update/close/append/update-many) is compact-by-default; pass options.full=true for the full delta. Tool descriptions updated.
  • Root-cause fix in extractCommandScopedOptions: the new global is stripped so it never leaks into per-command update fields (keeps update-many rollback + update no-op detection correct).
  • Discoverable via pm contracts + bash/zsh/fish completion.

pm-8nyc — Smarter unknown-option recovery

  • Cross-command flag awareness: an unknown flag valid elsewhere now points to the right command (e.g. --type on test-all → create, list, list-all; --fields on create → list, get).
  • Prefix/abbreviation suggestions (--desc → --description).
  • list --sort aliases: updated→updated_at, created→created_at, with updated help + invalid-value error.
  • Semantic map extended: update/create --add-tags/--add-tag/--tag → --tags (existing --body→--add retained).

Housekeeping

  • Refreshed pm-changelog 2026.5.25 → 2026.5.26 and regenerated CHANGELOG.md.
  • Docs updated (COMMANDS.md, AGENT_GUIDE.md).
  • Redacted an absolute-home-path a sub-agent quoted into a pm-rnpb comment (audited pm history-redact; check-secrets clean).

Verification

  • Full coverage gate: 1819 tests, 100% statements/branches/functions/lines.
  • quality:static, security:scan, version:check, tsc --noEmit, npm pack --dry-run, docs-skills gate, Claude + Codex plugin smokes, changelog:pm:check — all pass.
  • 16-check temp-dir dogfood (compact output on/off + backward-compat, all four recovery paths, sort aliases, health/validate) — all pass.

Closes pm-ch59, pm-8nyc.

🤖 Generated with Claude Code

…9, pm-8nyc)

Two agent-UX / token-efficiency improvements under pm-rnpb, combined into one PR.

pm-ch59 — compact mutation output:
- New pure helper src/core/output/mutation-projection.ts (projectMutationResult)
  replaces the verbose changed_fields array with a deterministic changed_field_count.
- Global --no-changed-fields CLI flag, threaded GlobalOptions -> OutputOptions ->
  printResult, so it applies uniformly to every mutation command.
- MCP agent path (create/update/close/append/update-many) is compact-by-default;
  pass options.full=true to restore the full changed_fields delta. pm_create/
  pm_update/pm_close tool descriptions document this.
- Root-cause fix in extractCommandScopedOptions: strip the new global so it never
  leaks into per-command update fields (keeps update-many rollback + update no-op
  detection correct).
- Discoverable via pm contracts (SUBCOMMAND_GLOBAL_FLAG_CONTRACTS) and bash/zsh/fish
  completion.

pm-8nyc — unknown-option recovery:
- Cross-command flag awareness: unknown flags valid elsewhere now hint the right
  command (e.g. --type on test-all -> create/list/list-all; --fields on create ->
  list/get), built from a declaration-ordered flag->commands index.
- Prefix/abbreviation-aware nearest-flag suggestions (--desc -> --description).
- list --sort aliases: updated->updated_at, created->created_at, with updated help
  and invalid-value error text.
- Semantic map extended: update/create --add-tags/--add-tag/--tag -> --tags
  (existing --body -> --add retained).

Also: refresh pm-changelog 2026.5.25 -> 2026.5.26 and regenerate CHANGELOG.md;
docs (COMMANDS.md, AGENT_GUIDE.md) updated; redact an absolute-home-path that a
sub-agent quoted into a pm-rnpb comment (audited pm history-redact).

Verification: full coverage gate green (1819 tests, 100% stmts/branches/funcs/lines),
quality:static, security:scan, version:check, typecheck, npm pack --dry-run,
docs-skills gate, claude/codex plugin smokes, changelog:pm:check, and a 16-check
temp-dir dogfood all pass.

Closes pm-ch59, pm-8nyc.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Credits must be used to enable repository wide code reviews.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Sorry @unbraind, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@unbraind

Copy link
Copy Markdown
Owner Author

@codex review
/gemini review
@gemini-code-assist please review

@coderabbitai

coderabbitai Bot commented May 25, 2026 •

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@unbraind, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 11 minutes and 16 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: 6827f257-5741-4e3b-a69a-7c7aaeb3c906

📥 Commits

Reviewing files that changed from the base of the PR and between c969db4 and e2be7ac.

📒 Files selected for processing (7)
  • .agents/pm/extensions/.managed-extensions.json
  • .agents/pm/history/pm-70mi.jsonl
  • .agents/pm/plans/pm-70mi.toon
  • CHANGELOG.md
  • src/core/output/mutation-projection.ts
  • src/mcp/server.ts
  • tests/unit/mutation-projection.spec.ts
📝 Walkthrough

Walkthrough

This PR delivers two feature completions: --no-changed-fields flag for compact mutation output (pm-ch59) and improved unknown-option error recovery with nearest-flag suggestions and cross-command hints (pm-8nyc). Both changes flow through CLI, MCP, tests, and documentation, coordinated by a new combined PR plan (pm-70mi).

Changes

Mutation Output Compaction

Layer / File(s) Summary
Mutation projection contract and core implementation
src/core/output/mutation-projection.ts, src/core/shared/command-types.ts, src/core/output/output.ts
New projectMutationResult function converts changed_fields arrays to compact changed_field_count for top-level envelopes and update-many rows; adds ChangedFieldsMode/options types and noChangedFields wiring into print path.
CLI flag registration and global option extraction
src/sdk/cli-contracts.ts, src/cli/main.ts, src/cli/registration-helpers.ts
--no-changed-fields added to global flag contracts and commander; extraction yields GlobalOptions.noChangedFields; scoped changedFields removed from command inputs.
MCP server compaction implementation
src/mcp/server.ts
MCP schemas gain fullChangedFields flag; withMutationCompaction() computes mode and runner call sites (create, update, close, append, update-many) are wrapped with projectMutationResult(..., { changedFields }).
Shell completion for mutation flag
src/cli/commands/completion.ts
Add --no-changed-fields to mutation flag templates and propagate to bash COMPREPLY branches, zsh _arguments, and fish completion lists across affected subcommands.
Documentation and tests for mutation compaction
docs/COMMANDS.md, docs/AGENT_GUIDE.md, tests/unit/mutation-projection.spec.ts, tests/unit/output.spec.ts, tests/integration/mcp-dynamic-package-actions.spec.ts, tests/unit/completion-command.spec.ts, tests/unit/registration-helpers.spec.ts, vitest.config.ts
Document compact output and MCP default; add comprehensive unit tests for projectMutationResult including edge cases; update output, completion, registration-helper tests; add MCP integration test and include new file in coverage.

Unknown Option Recovery Improvements

Layer / File(s) Summary
Semantic flag suggestions and nearest-flag ranking
src/cli/commander-usage.ts
Add semantic remaps (e.g., --add-tags→--tags) and refactor nearest-flag suggestion ranking to prefer prefix/abbrev matches over edit-distance typos.
Cross-command flag discovery and context threading
src/cli/commander-usage.ts
Build cached flag→command index over core command names; compute unknownOptionOtherCommands (up to 3 other commands) and thread through usage context for display and JSON envelopes.
Error guidance context and formatting for cross-command hints
src/cli/error-guidance.ts
Add optional unknownOptionOtherCommands?: string[] to CommanderGuidanceContext and append a next-step message indicating other commands where the flag is valid when present.
Sort field aliases and list parsing
src/cli/commands/list.ts, src/cli/register-list-query.ts
Support updated/update/modified → updated_at and created/create → created_at aliases; normalize input and include alias mappings in error messages and help text.
Documentation and tests for unknown option recovery
docs/COMMANDS.md, tests/unit/error-guidance.spec.ts, tests/integration/help-runtime.spec.ts, tests/unit/list-command.spec.ts
Document nearest-flag/abbrev suggestions, cross-command hints, and sort aliases; add unit and integration tests verifying JSON/human guidance and alias parsing.

PM Metadata, Plan Coordination, and Release

Layer / File(s) Summary
PM chore and issue completion records
.agents/pm/chores/pm-ch59.toon, .agents/pm/history/pm-ch59.jsonl, .agents/pm/issues/pm-8nyc.toon, .agents/pm/history/pm-8nyc.jsonl
Close pm-ch59 and pm-8nyc with implementation comments, title updates, and close reasons; append JSONL history entries documenting claims, comments, tests, and closure evidence.
PM feature reassignment, security review, and plan definition
.agents/pm/features/pm-rnpb.toon, .agents/pm/history/pm-rnpb.jsonl, .agents/pm/plans/pm-70mi.toon, .agents/pm/history/pm-70mi.jsonl
Reassign pm-rnpb to claude-code-agent, add security-sweep evidence, and create/update combined plan pm-70mi with completed step evidence for pm-ch59 and pm-8nyc and an in-progress Integrate/Verify step.
Release and changelog updates
.agents/pm/extensions/.managed-extensions.json, .agents/pm/extensions/pm-changelog/*, CHANGELOG.md
Bump pm-changelog version to 2026.5.26, add 2026.05.26 release note, restructure prior changelog section, update root CHANGELOG unreleased entries, and correct historical release dates.

Sequence Diagram(s)

sequenceDiagram
  participant CLI as CLI Command
  participant GlobalOpts as GlobalOptions
  participant Output as printResult
  participant Projection as projectMutationResult
  participant Stdout as Stdout

  CLI->>GlobalOpts: extract noChangedFields
  CLI->>Output: printResult(result, options)
  Output->>Projection: projectMutationResult(result, {changedFields: "compact"})
  alt Compact Mode
    Projection->>Projection: Remove changed_fields, add changed_field_count
  else Full Mode
    Projection->>Projection: Return unchanged
  end
  Projection->>Output: Projected result
  Output->>Stdout: Print formatted output
Loading
sequenceDiagram
  participant User as User
  participant CLI as CLI Parser
  participant Usage as CommanderUsageContext
  participant Index as Flag->Command Index
  participant Guidance as Error Guidance
  participant Envelope as JSON Error Envelope

  User->>CLI: Provide unknown --option
  CLI->>Usage: resolveCommanderUsageContext
  Usage->>Index: Query which commands accept --option
  Index->>Usage: Return up to 3 other commands
  Usage->>Guidance: Pass unknownOptionOtherCommands
  Guidance->>Envelope: Generate unknown_option with next_steps hint
  Envelope->>User: "flag valid on: create, list, list-all"
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

  • unbraind/pm-cli#38: Overlaps on getGlobalOptions changes in src/cli/registration-helpers.ts.
  • unbraind/pm-cli#71: Related changes to unknown-option suggestion logic in src/cli/commander-usage.ts.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.76% 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
Title check ✅ Passed The PR title clearly and specifically summarizes the main changes: compact mutation output (pm-ch59) and smarter unknown-flag recovery (pm-8nyc), which are the primary features implemented.
Description check ✅ Passed The PR description is comprehensive and directly related to the changeset, covering both main features (compact mutation output and unknown-option recovery), implementation details, and verification results.
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.


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.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Credits must be used to enable repository wide code reviews.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request introduces several agent-UX and token-efficiency enhancements. It adds a --no-changed-fields global CLI flag and compacts MCP mutation outputs by default to replace the verbose changed_fields array with a compact changed_field_count. It also improves unknown-option error recovery by suggesting nearest flags (including prefix/abbreviation matches like --desc to --description), providing cross-command flag hints, and mapping additive tag flags to --tags. Additionally, it introduces convenience aliases for list sorting (updated to updated_at and created to created_at). Comprehensive integration and unit tests have been added to verify these behaviors. There are no review comments to address.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request introduces several agent-UX and token-efficiency improvements. It adds a global --no-changed-fields CLI flag and makes MCP mutation tools compact-by-default, replacing the verbose changed_fields array with a changed_field_count unless options.full is requested. It also enhances unknown-option recovery by supporting prefix/abbreviation suggestions (e.g., --desc to --description), providing cross-command flag hints, and mapping tag-related flags to --tags. Additionally, it introduces convenience aliases for --sort fields (such as updated to updated_at). The code reviewer identified a potential prototype pollution vulnerability in the sort field alias lookup that should be addressed by ensuring the resolved alias is a string.

Comment thread src/cli/commands/list.ts Outdated
Comment on lines +210 to +213
const aliased = LIST_SORT_FIELD_ALIASES[normalized];
if (aliased) {
return aliased;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

Using direct property lookup on a plain object with user-controlled input (normalized) can lead to prototype pollution or unexpected behavior if the input matches standard Object.prototype properties (e.g., toString, constructor, hasOwnProperty). Since LIST_SORT_FIELD_ALIASES is a plain object, we should safely check if the resolved alias is a string before returning it.

Suggested change
const aliased = LIST_SORT_FIELD_ALIASES[normalized];
if (aliased) {
return aliased;
}
const aliased = LIST_SORT_FIELD_ALIASES[normalized];
if (typeof aliased === "string") {
return aliased;
}

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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 @.agents/pm/chores/pm-ch59.toon:
- Around line 2-18: This change directly edits PM system-of-record entries under
.agents/pm, which is prohibited; revert those direct edits and recreate the
chore via the PM command workflow instead (use the pm CLI to create or update
the chore so the tool records the entry), ensuring the new feature/flag work
remains in your branch; if you need to reference implementation symbols for
locating the logic to keep (not edit in .agents), keep changes confined to the
code under the feature branch (e.g., projectMutationResult,
GlobalOptions.noChangedFields, printResult,
registration-helpers.getGlobalOptions, SUBCOMMAND_GLOBAL_FLAG_CONTRACTS) and
only generate/update the .agents/pm record by running the pm create/update
commands so the system-of-record is authored by the pm tooling rather than by
direct file edits.

In `@src/core/output/mutation-projection.ts`:
- Around line 35-47: projectMutationResult currently only compacts when the
top-level object contains CHANGED_FIELDS_KEY, so nested results (e.g.,
runUpdateMany rows) are not transformed; change projectMutationResult to handle
arrays and nested objects recursively: if result is an Array, return
result.map(r => projectMutationResult(r, options)); if result is a plain object,
first check for CHANGED_FIELDS_KEY and apply the existing compaction (return
rest plus CHANGED_FIELD_COUNT_KEY), otherwise iterate object properties and
recursively call projectMutationResult on each value to ensure any nested
changed_fields are compacted; keep the existing mode logic
(options.changedFields) and use the same CHANGED_FIELDS_KEY and
CHANGED_FIELD_COUNT_KEY symbols.
🪄 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: Repository UI (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: cdbf051d-a846-456e-b52b-28f46e5305ae

📥 Commits

Reviewing files that changed from the base of the PR and between f301f99 and e9598ec.

⛔ Files ignored due to path filters (9)
  • .agents/pm/extensions/pm-changelog/dist/cli.js is excluded by !**/dist/**
  • .agents/pm/extensions/pm-changelog/dist/cli.js.map is excluded by !**/dist/**, !**/*.map
  • .agents/pm/extensions/pm-changelog/dist/extension.d.ts.map is excluded by !**/dist/**, !**/*.map
  • .agents/pm/extensions/pm-changelog/dist/extension.js is excluded by !**/dist/**
  • .agents/pm/extensions/pm-changelog/dist/extension.js.map is excluded by !**/dist/**, !**/*.map
  • .agents/pm/extensions/pm-changelog/dist/release-context.d.ts is excluded by !**/dist/**
  • .agents/pm/extensions/pm-changelog/dist/release-context.d.ts.map is excluded by !**/dist/**, !**/*.map
  • .agents/pm/extensions/pm-changelog/dist/release-context.js is excluded by !**/dist/**
  • .agents/pm/extensions/pm-changelog/dist/release-context.js.map is excluded by !**/dist/**, !**/*.map
📒 Files selected for processing (36)
  • .agents/pm/chores/pm-ch59.toon
  • .agents/pm/extensions/.managed-extensions.json
  • .agents/pm/extensions/pm-changelog/CHANGELOG.md
  • .agents/pm/extensions/pm-changelog/manifest.json
  • .agents/pm/extensions/pm-changelog/package.json
  • .agents/pm/features/pm-rnpb.toon
  • .agents/pm/history/pm-70mi.jsonl
  • .agents/pm/history/pm-8nyc.jsonl
  • .agents/pm/history/pm-ch59.jsonl
  • .agents/pm/history/pm-rnpb.jsonl
  • .agents/pm/issues/pm-8nyc.toon
  • .agents/pm/plans/pm-70mi.toon
  • CHANGELOG.md
  • docs/AGENT_GUIDE.md
  • docs/COMMANDS.md
  • src/cli/commander-usage.ts
  • src/cli/commands/completion.ts
  • src/cli/commands/list.ts
  • src/cli/error-guidance.ts
  • src/cli/main.ts
  • src/cli/register-list-query.ts
  • src/cli/registration-helpers.ts
  • src/core/output/mutation-projection.ts
  • src/core/output/output.ts
  • src/core/shared/command-types.ts
  • src/mcp/server.ts
  • src/sdk/cli-contracts.ts
  • tests/integration/help-runtime.spec.ts
  • tests/integration/mcp-dynamic-package-actions.spec.ts
  • tests/unit/completion-command.spec.ts
  • tests/unit/error-guidance.spec.ts
  • tests/unit/list-command.spec.ts
  • tests/unit/mutation-projection.spec.ts
  • tests/unit/output.spec.ts
  • tests/unit/registration-helpers.spec.ts
  • vitest.config.ts

Comment on lines +2 to +18
title: Add --no-changed-fields flag and compact MCP mutation output to drop the redundant changed_fields array
description: "Repro: PM_AUTHOR=x pm create issue 'probe' (default TOON). Observed: 21-line output where 11 lines are a 'changed_fields' array listing id,title,description,type,status,priority,tags,created_at,updated_at,author on EVERY create; update emits the same pattern. The item body directly above already shows the set values, so changed_fields is largely redundant for the create case. Expected: a way to trim mutation output for agent loops, e.g. accept --fields (already works on get, rejected on create with 'Unknown option --fields'), or a --quiet/--no-changed-fields, or drop the full changed_fields list on create (keep it on update where it signals the delta). Severity: low (token efficiency in high-volume agent create loops; ~50% of create payload is the redundant array). Note: prior verbosity item pm-3pbs was scoped to pm activity/history only, not mutations."
type: Chore
status: open
status: closed
priority: 2
tags[3]: agent-ux,dogfood,dx
created_at: "2026-05-25T18:30:34.477Z"
updated_at: "2026-05-25T20:25:05.565Z"
updated_at: "2026-05-25T23:47:52.695Z"
author: dogfood-agent
parent: pm-rnpb
comments[4]{created_at,author,text}:
comments[5]{created_at,author,text}:
"2026-05-25T18:30:41.628Z",dogfood-agent,"Dedup checks run against real tracker (no PM_PATH override) on 2026-05-25 before filing: (1) pm search 'create update changed_fields verbose suppress quiet flag' --limit 12 -> hits pm-rnpb(in_progress parent), pm-5k2w(init verbosity, closed), pm-swie/pm-ote/pm-j1v7/pm-phob/pm-kpz5/pm-wo8/pm-ysgr/pm-mfza/pm-06t (all unrelated create/update feature work, closed) -- none about output verbosity/changed_fields suppression. (2) pm search 'mutation output quiet compact' -> pm-3pbs (closed) reduces verbosity of pm ACTIVITY/HISTORY and adds compact mode to history, NOT create/update mutations -- scope differs. (3) pm list --status open --limit 30 -> pm-59gj,pm-gt82,pm-3cbk (none related). (4) pm list --status in-progress --limit 20 -> pm-rnpb,pm-7rlp,pm-cf1u (none related). Conclusion: no open or recently-closed item covers suppressing/trimming create/update mutation output. Filed as new under pm-rnpb."
"2026-05-25T19:47:52.743Z",codex-full-audit,"Full audit confirmation: mutation JSON output remains a real token-efficiency issue and is not solved in this slice. Manual/sub-agent dogfood still shows bulky changed_fields/linked object payloads as a high-value optimization for agents. This pass focused on command recovery, package validation, SDK/package boundary, and vector cache correctness; leave pm-ch59 open for explicit compact mutation-output projection work."
"2026-05-25T19:56:59.534Z",manual-dogfood-specialist,"Manual dogfood evidence 2026-05-25: create/update JSON outputs in the temp LaunchOps project still repeatedly emitted large changed_fields arrays. On first create, changed_fields listed id/title/description/type/status/priority/tags/created_at/updated_at/deadline/author/body; update with comment/note/learning emitted a full item plus changed_fields. This confirms the open token-cost concern remains relevant for agents; fields projections help reads but mutation outputs still need a compact/default projection or a way to suppress changed_fields without losing deterministic mutation evidence.\n"
"2026-05-25T20:25:05.565Z",manual-dogfood-subagent,"Manual dogfood reconfirmed mutation output verbosity: create/update/claim/close/comment-style commands returned full item/comment payloads by default. In small temp project this was usable, but comments on pm-rnpb returned a massive historical comment array, reinforcing the need for compact mutation acknowledgements / suppressible changed_fields and append outputs for token efficiency."
"2026-05-25T23:13:10.074Z",claude-code-agent,"IMPLEMENTED (branch agent-ux-compact-mutation-output-and-flag-recovery). Approach: shared pure helper src/core/output/mutation-projection.ts projectMutationResult() drops the verbose changed_fields array and replaces it with changed_field_count (deterministic mutation evidence, ~no token cost). Two surfaces: (1) CLI global flag --no-changed-fields (main.ts opt) -> GlobalOptions.noChangedFields (command-types.ts + registration-helpers.getGlobalOptions opts.changedFields===false) -> OutputOptions.noChangedFields -> printResult projects before formatOutput; applies uniformly to ALL mutation commands since they route through printResult. (2) MCP agent path: runAction create/update/close/append/update-many are compact-by-default (changed_field_count) unless options.full=true (withMutationCompaction strips the full flag before the runner); pm_create/pm_update/pm_close tool descriptions document it. Discoverability: added --no-changed-fields to SUBCOMMAND_GLOBAL_FLAG_CONTRACTS (pm contracts) + completion (bash global tail x26, zsh _arguments global block, fish global defs). Coverage: helper added to vitest include (100% via tests/unit/mutation-projection.spec.ts). Tests: mutation-projection unit (6 cases), output.spec printResult noChangedFields, mcp-dynamic compact-by-default+full opt-out. Manual smoke (temp dir): default keeps changed_fields; --no-changed-fields -> changed_field_count:10 (TOON+JSON); default --json still has changed_fields (backward compatible)."
close_reason: "Shipped on branch agent-ux-compact-mutation-output-and-flag-recovery. Added shared projectMutationResult helper (src/core/output/mutation-projection.ts, 100% covered), global --no-changed-fields CLI flag, and MCP compact-by-default for create/update/close/append/update-many (opt back in with options.full). Root-cause fix: extractCommandScopedOptions strips the new global so it never counts as an update field (fixes update-many rollback + update no-op). Full coverage gate green (1819 tests, 100%), 16/16 dogfood checks pass, check-secrets clean."

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 | 🟠 Major | ⚡ Quick win

Do not directly modify PM system-of-record files.

Line 2 through Line 18 are direct edits under .agents/pm/**, which violates repo policy and should be done via the PM command workflow that generates these records.

As per coding guidelines: .agents/pm/**: pm is the system of record. Do not edit .agents/pm files directly.

🤖 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 @.agents/pm/chores/pm-ch59.toon around lines 2 - 18, This change directly
edits PM system-of-record entries under .agents/pm, which is prohibited; revert
those direct edits and recreate the chore via the PM command workflow instead
(use the pm CLI to create or update the chore so the tool records the entry),
ensuring the new feature/flag work remains in your branch; if you need to
reference implementation symbols for locating the logic to keep (not edit in
.agents), keep changes confined to the code under the feature branch (e.g.,
projectMutationResult, GlobalOptions.noChangedFields, printResult,
registration-helpers.getGlobalOptions, SUBCOMMAND_GLOBAL_FLAG_CONTRACTS) and
only generate/update the .agents/pm record by running the pm create/update
commands so the system-of-record is authored by the pm tooling rather than by
direct file edits.

Comment thread src/core/output/mutation-projection.ts Resolved
…e-safe sort aliases

- CodeRabbit (Major): projectMutationResult only compacted top-level changed_fields,
  so update-many (which reports rows[*].changed_fields) stayed verbose. Made the
  projection recursive so --no-changed-fields / MCP compact mode trims changed_fields
  wherever it appears; returns the same reference when nothing is compacted.
- Gemini (High): the list --sort alias lookup used direct property access on a plain
  object with user input, enabling prototype-chain hits (__proto__/constructor/...).
  Switched to a Map so unknown/prototype keys resolve to undefined.
- Tests: nested update-many-style compaction + no-op same-reference case; sort alias
  rejects __proto__/constructor/toString.

Full coverage gate green (1821 tests, 100%).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@unbraind

Copy link
Copy Markdown
Owner Author

Addressed review feedback in 7e4f9ea:

  • CodeRabbit (Major) — projectMutationResult now compacts changed_fields recursively, so update-many (rows[*].changed_fields) is trimmed by --no-changed-fields / MCP compact mode too. Verified: --no-changed-fields update-many now emits changed_field_count per row.
  • Gemini (High) — the --sort alias lookup is now a Map (was a plain-object lookup on user input), so __proto__/constructor/etc. resolve to undefined. Added a regression test.
  • CodeRabbit (.agents/pm/** "do not edit directly") — those files are written by the pm CLI itself (item close/retitle, audited history-redact), not hand-edited, so this is expected and policy-compliant.

Full coverage gate green (1821 tests, 100%).

@codex review
/gemini review
@gemini-code-assist please re-review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Credits must be used to enable repository wide code reviews.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request introduces several agent-UX and token-efficiency improvements. It adds a shared projectMutationResult helper to compact mutation command outputs by dropping the verbose changed_fields array and replacing it with changed_field_count. This compaction is exposed via a new global CLI flag --no-changed-fields and is enabled by default on the MCP agent path (with an opt-out option). Additionally, it enhances unknown-option error recovery by suggesting nearest flags (including prefix/abbreviation matches), adding cross-command flag awareness hints, extending the semantic flag map, and introducing convenience aliases for --sort fields (updated and created). Feedback was provided to harden the isPlainObject check in the new mutation projection helper to prevent prototype loss on non-plain objects.

Comment on lines +29 to +31
function isPlainObject(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(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.

medium

The current implementation of isPlainObject returns true for any non-null object that is not an array. This includes built-in objects like Date, RegExp, Map, Set, or custom class instances. If any such object is traversed recursively by compactChangedFields, it will be treated as a plain object, and its prototype will be lost (e.g., a Date object would be incorrectly mapped to an empty plain object {}).

Using a stricter check that verifies the prototype is either null or Object.prototype prevents prototype loss and ensures robust handling of non-plain objects.

function isPlainObject(value: unknown): value is Record<string, unknown> {
  if (typeof value !== "object" || value === null || Array.isArray(value)) {
    return false;
  }
  const proto = Object.getPrototypeOf(value);
  return proto === null || proto === Object.prototype;
}

Gemini (medium): the recursive changed_fields projection used a loose isPlainObject
that returned true for Date/RegExp/Map/Set/class instances, so recursing into such
a value would rewrite it to {} and lose its prototype. Tighten the check to only
traverse genuine plain objects (prototype null or Object.prototype); built-ins and
class instances are now returned unchanged. Added regression tests (Date preserved,
null-prototype object still compacted).

Full coverage gate green (100%).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@unbraind

Copy link
Copy Markdown
Owner Author

Addressed Gemini's medium finding in d596dbe: isPlainObject in the recursive projection now only traverses genuine plain objects (prototype null or Object.prototype), so Date/Map/class instances are preserved instead of being rewritten to {}. Added regression tests. Full coverage gate green (100%).

@codex review
/gemini review
@gemini-code-assist please re-review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Credits must be used to enable repository wide code reviews.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a shared mutation-result projection helper (projectMutationResult) that compacts verbose changed_fields arrays into a simple count (changed_field_count) for improved token efficiency in agent loops. This compaction is exposed via a new global CLI flag --no-changed-fields and is enabled by default on MCP mutation tools. Additionally, it enhances CLI error recovery by introducing prefix/abbreviation-aware flag suggestions, cross-command flag hints, and sort field aliases for list commands. The review feedback suggests preserving the prototype of null-prototype objects during compaction and adding corresponding test assertions.

Comment thread src/core/output/mutation-projection.ts Outdated
return value;
}

let changed = false;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

When compacting a plain object that has a null prototype (e.g., created via Object.create(null)), initializing projected as {} will inadvertently assign Object.prototype as its prototype. To preserve the original prototype of the object, initialize projected dynamically based on the prototype of value.

Suggested change
let changed = false;
const projected: Record<string, unknown> = Object.getPrototypeOf(value) === null ? Object.create(null) : {};

Comment on lines +81 to +82
expect(projected.changed_fields).toBeUndefined();
expect(projected.changed_field_count).toBe(2);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Add an assertion to verify that the prototype of the null-prototype object is correctly preserved after compaction.

expect(projected.changed_fields).toBeUndefined();
expect(projected.changed_field_count).toBe(2);
expect(Object.getPrototypeOf(projected)).toBeNull();

…view, Gemini)

Gemini (medium): compacting a null-prototype object initialized the projected copy
as {} (Object.prototype), silently re-parenting it. Build the projected object from
Object.getPrototypeOf(value) so null-prototype (and Object.prototype) sources keep
their prototype. Test now asserts the compacted null-prototype object stays null-proto.

Full coverage gate green (100%).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@unbraind

Copy link
Copy Markdown
Owner Author

Addressed Gemini's remaining two findings in 70cefec: the projected object now inherits the source prototype via Object.getPrototypeOf(value), so a null-prototype object keeps its prototype during compaction; added the prototype assertion to the test. Full coverage gate green (100%).

@codex review
/gemini review
@gemini-code-assist please re-review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a compact mutation output mode that replaces the verbose changed_fields array with a changed_field_count to improve token efficiency in agent loops. This is supported by a new global CLI flag --no-changed-fields and is enabled by default for MCP mutation tools. Additionally, the PR enhances unknown-option recovery with prefix/abbreviation-aware suggestions, cross-command flag hints, and list --sort aliases. The reviewer feedback is highly constructive and identifies opportunities to ensure deterministic sorting of flag suggestions, optimize plain object instantiation in the projection helper, and defensively guard against empty arrays when rendering cross-command hints.

Comment on lines 232 to 239
return candidates
.sort((left, right) => {
if (left.rank !== right.rank) {
return left.rank - right.rank;
}
if (left.distance !== right.distance) {
return left.distance - right.distance;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

To ensure that the suggestions are completely deterministic and consistent across different environments, consider adding an alphabetical tie-breaker sort by the flag name when both rank and distance are equal.

Suggested change
return candidates
.sort((left, right) => {
if (left.rank !== right.rank) {
return left.rank - right.rank;
}
if (left.distance !== right.distance) {
return left.distance - right.distance;
}
return candidates
.sort((left, right) => {
if (left.rank !== right.rank) {
return left.rank - right.rank;
}
if (left.distance !== right.distance) {
return left.distance - right.distance;
}
return left.flag.localeCompare(right.flag);

Comment thread src/core/output/mutation-projection.ts Outdated
Comment on lines +56 to +59
let changed = false;
// Preserve the source prototype (null or Object.prototype) so a null-prototype
// object is not silently re-parented to Object.prototype during compaction.
const projected: Record<string, unknown> = Object.create(Object.getPrototypeOf(value) as object | null);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Using Object.create(Object.getPrototypeOf(value)) for standard plain objects can be slower and is less idiomatic than using {}. Since isPlainObject guarantees that the prototype is either null or Object.prototype, we can optimize this by checking if the prototype is null and using {} otherwise.

Suggested change
let changed = false;
// Preserve the source prototype (null or Object.prototype) so a null-prototype
// object is not silently re-parented to Object.prototype during compaction.
const projected: Record<string, unknown> = Object.create(Object.getPrototypeOf(value) as object | null);
let changed = false;
const proto = Object.getPrototypeOf(value);
const projected: Record<string, unknown> = proto === null ? Object.create(null) : {};

Comment thread src/cli/error-guidance.ts Outdated
Comment on lines +664 to +666
...(otherCommands
? [`${optionName} is a valid option on: ${otherCommands.join(", ")}. If you meant one of those, run that command instead.`]
: []),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

To prevent potential UX issues where an empty array [] is returned by normalizeContextList (which is truthy in JavaScript) and results in an empty list of commands being printed, check both otherCommands and otherCommands.length > 0 defensively. This also aligns with the pattern used for suggestions on the line above.

Suggested change
...(otherCommands
? [`${optionName} is a valid option on: ${otherCommands.join(", ")}. If you meant one of those, run that command instead.`]
: []),
...(otherCommands && otherCommands.length > 0
? [`${optionName} is a valid option on: ${otherCommands.join(", ")}. If you meant one of those, run that command instead.`]
: []),

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 👍

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

…commands guard

- mutation-projection: use `proto === null ? Object.create(null) : {}` (idiomatic/
  faster for the common plain-object case; still preserves null prototypes).
- error-guidance: guard the cross-command hint with `otherCommands.length > 0` for
  consistency with the nearest-options check (normalizeContextList already returns
  undefined for empty, this is belt-and-suspenders).

The suggested alphabetical tie-breaker in suggestNearestLongFlags was already present
(localeCompare tie-breaker). Full coverage gate green (100%).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@unbraind

Copy link
Copy Markdown
Owner Author

Applied Gemini's latest nits in e6ced17:

  • mutation-projection.ts: projected object now uses proto === null ? Object.create(null) : {} (idiomatic/fast for plain objects, still preserves null prototypes).
  • error-guidance.ts: cross-command hint guarded with otherCommands.length > 0 for consistency with the nearest-options branch.
  • commander-usage.ts tie-breaker: already implemented — suggestNearestLongFlags sorts by rank, distance, then flag.localeCompare, so suggestions are already deterministic.

Full coverage gate green (100%).

@codex review
/gemini review
@gemini-code-assist please re-review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request introduces several agent-UX and token-efficiency improvements. It adds a shared mutation-result projection helper to compact the verbose changed_fields array into a changed_field_count, controlled via a new global CLI flag --no-changed-fields and enabled by default on MCP mutation paths. It also enhances unknown-option error recovery with nearest, abbreviated, and cross-command flag suggestions, and introduces convenient sort aliases (updated and created) for list commands. These changes are supported by updated documentation, completion scripts, and comprehensive unit and integration tests. I have no feedback to provide as there are no review comments.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e6ced17e44

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/mcp/server.ts Outdated
Comment on lines +460 to +461
const full = runnerOptions.full === true;
delete runnerOptions.full;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve runtime field named full in MCP mutation options

Consuming and deleting options.full for compaction control breaks legitimate mutation payloads when a runtime schema defines a create/update field whose option key resolves to full: pm_create/pm_update/pm_append/pm_update-many will silently drop that field instead of persisting it. This regression is introduced here because runnerOptions.full is always removed before handing options to the command runner, so clients can no longer set that user data key through MCP.

Useful? React with 👍 / 👎.

Comment thread src/core/output/output.ts

export function printResult(result: unknown, options: OutputOptions): void {
const rendered = formatOutput(result, options);
const projected = options.noChangedFields ? projectMutationResult(result, { changedFields: "compact" }) : result;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Limit --no-changed-fields compaction to mutation envelopes

printResult now applies projectMutationResult to every command output whenever --no-changed-fields is set, so non-mutation responses that legitimately use changed_fields lose detailed data. A concrete regression is history --diff: each entry’s changed_fields list (the actual field names touched) is collapsed into changed_field_count, which removes information users/scripts rely on for auditing.

Useful? React with 👍 / 👎.

Comment thread src/core/output/mutation-projection.ts Outdated
Comment on lines +62 to +63
if (key === CHANGED_FIELDS_KEY && Array.isArray(entry)) {
projected[CHANGED_FIELD_COUNT_KEY] = entry.length;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Avoid compacting user-defined changed_fields metadata

The recursive projector rewrites any object key named changed_fields if the value is an array, even when it is normal item metadata rather than the mutation delta envelope. If a runtime schema defines a metadata key like changed_fields (for example a string-array field), --no-changed-fields and MCP default compact mode will silently replace that user data with changed_field_count, corrupting returned item content.

Useful? React with 👍 / 👎.

@unbraind

Copy link
Copy Markdown
Owner Author

Addressed latest reviewer feedback in c969db4:\n\n- limited changed_fields compaction to mutation envelopes and update-many apply/rollback rows, preserving non-mutation/user changed_fields metadata\n- moved MCP full changed_fields control to fullChangedFields so mutation options.full is forwarded unchanged\n- added focused regressions and reran build/static/focused tests\n\n@codex review\n/gemini review\n@gemini-code-assist please re-review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a global --no-changed-fields CLI flag and compacts MCP mutation outputs by default to drop the verbose changed_fields array in favor of a changed_field_count for token efficiency. It also improves CLI unknown-option recovery with nearest-flag suggestions (including abbreviations like --desc -> --description), cross-command flag hints, and sort field aliases (updated -> updated_at, created -> created_at). Reviewers suggested preserving null prototypes when copying projected results in mutation-projection.ts and improving type safety in the MCP server's withMutationCompaction helper by making the options parameter optional and nullable.

Comment on lines +106 to +109
if (rowsChanged) {
projected = { ...projected, [ROWS_KEY]: nextRows };
changed = true;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Using the object spread operator { ...projected } on an object with a null prototype will create a new object with Object.prototype as its prototype, thereby losing the null prototype. To preserve the null prototype of projected (which can happen if the original result had a null prototype), use Object.assign with a newly created null-prototype object instead.

    if (rowsChanged) {
      const nextProjected = Object.getPrototypeOf(projected) === null ? Object.create(null) : {};
      projected = Object.assign(nextProjected, projected, { [ROWS_KEY]: nextRows });
      changed = true;
    }

Comment thread src/mcp/server.ts Outdated
Comment on lines +479 to +484
function withMutationCompaction(args: Record<string, unknown>, options: Record<string, unknown>): {
changedFields: "full" | "compact";
runnerOptions: Record<string, unknown>;
} {
return { changedFields: args.fullChangedFields === true ? "full" : "compact", runnerOptions: { ...options } };
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

In the pm_close tool definition, options is not a required field and can be undefined at runtime. Typing the options parameter as non-nullable Record<string, unknown> in withMutationCompaction can lead to type-checking errors under strictNullChecks or potential runtime issues if not properly guarded. Making the parameter optional and nullable improves type safety and robustness.

Suggested change
function withMutationCompaction(args: Record<string, unknown>, options: Record<string, unknown>): {
changedFields: "full" | "compact";
runnerOptions: Record<string, unknown>;
} {
return { changedFields: args.fullChangedFields === true ? "full" : "compact", runnerOptions: { ...options } };
}
function withMutationCompaction(args: Record<string, unknown>, options?: Record<string, unknown> | null): {
changedFields: "full" | "compact";
runnerOptions: Record<string, unknown>;
} {
return { changedFields: args.fullChangedFields === true ? "full" : "compact", runnerOptions: { ...options } };
}

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Keep them coming!

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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)
tests/unit/mutation-projection.spec.ts (1)

104-113: ⚡ Quick win

Add a regression for null-prototype update-many envelopes.

Great null-proto coverage for top-level compaction. Please add the same assertion for the update-many rows rewrite path, since that path rebuilds the envelope.

🧪 Suggested test addition
+  it("preserves null-prototype for update-many envelopes when rows are compacted", () => {
+    const result = Object.assign(Object.create(null), {
+      mode: "apply",
+      rows: [Object.assign(Object.create(null), { id: "pm-1", changed_fields: ["status"] })],
+    }) as Record<string, unknown>;
+
+    const projected = projectMutationResult(result, { changedFields: "compact" }) as Record<string, unknown>;
+    expect(Object.getPrototypeOf(projected)).toBeNull();
+  });
🤖 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 `@tests/unit/mutation-projection.spec.ts` around lines 104 - 113, Add a
regression test that exercises the update-many rewrite path by passing a
null-prototype envelope through projectMutationResult and asserting the rebuilt
rows preserve the null prototype and compacted fields; specifically, reuse the
nullProto object (Object.create(null) with item and changed_fields) inside an
envelope shaped like { rows: [nullProto] } (or the same structure used by the
update-many path in the test file), call projectMutationResult(envelope, {
changedFields: "compact" }), then assert projected.rows[0].changed_fields is
undefined, projected.rows[0].changed_field_count === 2, and
Object.getPrototypeOf(projected.rows[0]) === null so the update-many rows
rewrite path preserves null prototypes.
🤖 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 `@src/core/output/mutation-projection.ts`:
- Around line 104-109: The current rewrite uses object spread ({ ...projected,
[ROWS_KEY]: nextRows }) which converts null-prototype envelopes to plain
Object.prototype; instead detect the prototype of projected (const proto =
Object.getPrototypeOf(projected)) and construct the replacement envelope with
the same prototype (e.g. const newProjected = Object.create(proto);
Object.assign(newProjected, projected); newProjected[ROWS_KEY] = nextRows), then
set projected = newProjected and changed = true; avoid using object spread so
null-prototype envelopes remain preserved (refer to ROWS_KEY, projected,
nextRows, rowsChanged).

In `@src/mcp/server.ts`:
- Around line 140-144: The new top-level MCP transport flag fullChangedFields is
being passed through into dynamic extension options because
extensionOptionsFromArgs doesn't filter/reserve it; update
extensionOptionsFromArgs to treat fullChangedFields (and other MCP control
flags) as reserved and remove them from the returned options so pm_run and other
dynamic extension actions never receive fullChangedFields as an unexpected
option; ensure the same reservation logic is applied in the other similar block
referenced (the second occurrence around the pm_run handling).

---

Nitpick comments:
In `@tests/unit/mutation-projection.spec.ts`:
- Around line 104-113: Add a regression test that exercises the update-many
rewrite path by passing a null-prototype envelope through projectMutationResult
and asserting the rebuilt rows preserve the null prototype and compacted fields;
specifically, reuse the nullProto object (Object.create(null) with item and
changed_fields) inside an envelope shaped like { rows: [nullProto] } (or the
same structure used by the update-many path in the test file), call
projectMutationResult(envelope, { changedFields: "compact" }), then assert
projected.rows[0].changed_fields is undefined,
projected.rows[0].changed_field_count === 2, and
Object.getPrototypeOf(projected.rows[0]) === null so the update-many rows
rewrite path preserves null prototypes.
🪄 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: Repository UI (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: 1c4a2d74-a8cf-493c-a89f-82a149335247

📥 Commits

Reviewing files that changed from the base of the PR and between e9598ec and c969db4.

📒 Files selected for processing (12)
  • .agents/pm/features/pm-rnpb.toon
  • .agents/pm/history/pm-70mi.jsonl
  • .agents/pm/history/pm-rnpb.jsonl
  • .agents/pm/plans/pm-70mi.toon
  • src/cli/commands/list.ts
  • src/cli/error-guidance.ts
  • src/core/output/mutation-projection.ts
  • src/mcp/server.ts
  • tests/integration/mcp-dynamic-package-actions.spec.ts
  • tests/unit/list-command.spec.ts
  • tests/unit/mutation-projection.spec.ts
  • tests/unit/output.spec.ts
✅ Files skipped from review due to trivial changes (2)
  • .agents/pm/history/pm-rnpb.jsonl
  • .agents/pm/history/pm-70mi.jsonl

Comment thread src/core/output/mutation-projection.ts Resolved
Comment thread src/mcp/server.ts Resolved
@unbraind
unbraind merged commit 2786779 into main May 26, 2026
13 checks passed
@unbraind
unbraind deleted the agent-ux-compact-mutation-output-and-flag-recovery branch May 26, 2026 15:38
unbraind added a commit that referenced this pull request May 26, 2026
…ide builder (#73)

Two real nightly failures from run 26439727168 (2026-05-26) plus an
in-place cleanup of the extension-policy override-normalization
duplicate identified under the pm-3cbk maintainability decision.

Nightly fixes:
- tests/integration/cli.integration.spec.ts: add shell:true to
  execFileSync(npm.cmd) on win32. After CVE-2024-27980 Node hardening,
  spawnSync of a .cmd file requires shell mode or it fails with
  EINVAL. The previous PR #68 switched the binary to npm.cmd on
  Windows but did not enable shell mode, regressing Windows Node 20.
- tests/unit/extension-command.spec.ts: realpath the mkdtemp temp root
  before comparing against resolveInstallSource output. macOS resolves
  /var/folders to /private/var/folders via fs.realpath, and the
  production code path canonicalizes, while the test was holding the
  raw os.tmpdir-prefixed path.

pm-3cbk dedup:
- Extract buildExtensionPolicyOverride + collectExtensionPolicyOverrides
  helpers inside src/core/extensions/extension-policy.ts to remove the
  25-line duplicate between normalizeExtensionPolicy and
  hydrateExtensionPolicy override loops. Single-file extraction keeps
  the existing uncovered allowlist clean.

Tracker hygiene:
- Backfill resolution/expected_result/actual_result on three closed
  items (pm-ch59, pm-8nyc, pm-70mi) from PR #72 that pre-dated the
  --expected-result/--actual-result close flow. pm validate
  --check-resolution now warning-free.
- Close pm-gf6f with full metadata; pnpm changelog:pm regenerated
  CHANGELOG.md to include the new Fixed entry.

Verification:
- Targeted tests pass (extension-command + cli.integration matchers).
- extension-loader/settings-store/settings-validator/contracts-command:
  90 tests pass.
- Full release gate ok: build, typecheck, docs, static-quality,
  coverage (100%/100%/100%/100%), version-policy, secret-scan,
  npx-smoke, package-first-dogfood, npm-pack, compat,
  sentry-telemetry. Sentry critical=0 high=5 within threshold;
  telemetry finish_error_rate_pct=1.95, missing-error-code rows=0.
- Manual temp-dir dogfood (init/create/list/search/context/health/
  validate) happy path verified.
- pm health ok; pm validate --check-resolution --check-history-drift
  ok; pnpm changelog:pm:check unchanged.

Refs pm-7rlp, pm-3cbk, pm-gf6f.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
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