Skip to content

fix(cli): honor --json for config commands - #95

Closed
dakjdakd wants to merge 1 commit into
aoci-spec:mainfrom
dakjdakd:fix/config-json-output
Closed

dakjdakd wants to merge 1 commit into
aoci-spec:mainfrom
dakjdakd:fix/config-json-output

Conversation

@dakjdakd

@dakjdakd dakjdakd commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

What changes and why

The config command family did not consistently honor the repository-wide --json output contract. config list and config get wrote directly with fmt.Println, so their output bypassed Cobra's configured writer and could not be consumed reliably by callers that embed the CLI. Scalar values such as locale were emitted as bare text instead of JSON strings, while list values lost their array shape. config set --json still emitted localized human-readable status text, which made a successful mutation indistinguishable from non-JSON command output.

This change makes all three configuration operations use the existing command output pipeline and JSON serializer:

  • config list --json returns the complete configuration object as JSON through the command writer.
  • config get --json returns typed JSON values, including strings, booleans, numbers, and arrays.
  • config set --json returns a stable result object containing ok, key, and the typed persisted value.
  • Human-readable mode continues to use the existing localized messages, while writing through cmd.OutOrStdout() so embedded callers receive the same output as terminal users.

The implementation centralizes key projection in configValue, which keeps human-readable formatting and machine-readable typing explicit for every supported configuration key. The added CLI tests parse the emitted JSON and verify that a JSON mutation is persisted. The repository's AOCI code entry and Baseline were updated through the formal maintain, update-entry, verify, check, and guide workflow so the cognition index remains aligned with the implementation.

Closes #94

Affected public contracts

  • CLI command, flag, exit code, or --json shape
  • MCP tool names, input schemas, or response structures
  • spec/public/ contract text or machine vocabulary (internal/machinecontract)
  • Index format, Baseline, receipt, or transaction identity derivation

Compatibility impact: this makes the documented machine-readable mode consistent. Existing human-readable invocations retain their localized text. Configuration files and stored values are unchanged, and existing consumers that expected valid JSON receive correctly typed values instead of bare or localized text. The Baseline update records the new source fingerprints and cognition entry; it does not alter the configuration format or transaction protocol.

Verification

  • make fast
  • make full
  • python3 scripts/blackbox/mcp_conformance.py
  • python3 scripts/blackbox/mcp_scenarios.py
  • python3 scripts/blackbox/mcp_lifecycle.py
  • go test ./internal/cli -run 'TestConfigCommandsHonorJSONOutput|TestConfigGetUsesCobraOutputWriter' -count=1
  • go vet ./...
  • CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o build/aoci-fast ./cmd/aoci
  • CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -o build/aoci-fast.exe ./cmd/aoci
  • build/aoci-fast.exe --repo . verify --json
  • build/aoci-fast.exe --repo . check --json
  • build/aoci-fast.exe --repo . index agent guide --agent codex --json

Operating system impact

The change is platform-neutral. It also ensures Windows callers receive the same command-writer and JSON behavior as Linux and macOS callers; no filesystem or OS-specific behavior was changed.

Migration and recovery

No migration is required. The configuration file format, stored keys, and persisted values remain unchanged. If a caller needs the previous human-readable presentation, it can omit --json as before. A failed configuration write follows the existing command error and persistence behavior.

Cognition

  • A managed object changed and the AOCI governance cycle (maintain -> update_entry -> verify/check) was completed, with index and code in the same commit
  • No managed object changed

@dakjdakd
dakjdakd force-pushed the fix/config-json-output branch from 4cb0064 to c0c6a12 Compare October 2, 2026 19:12
@alkor2000 alkor2000 closed this in d3716a8 Oct 5, 2026
alkor2000 added a commit that referenced this pull request Oct 5, 2026
…nyway; Qoder alias; git stderr on inventory failure

#97: Safe Inventory asked git to list every ignored file on every scan,
verify, and Maintain (`git ls-files --others --ignored`). A node_modules with
tens of thousands of files cost tens of seconds per call, and on Windows a
pnpm workspace whose package links form a cycle never finished (git walked the
junctions until MAX_PATH, the whole result was held in memory, and the
pagefile grew to ~15 GB). Every one of those files was then excluded by its
built-in directory category or by exclude_dirs before any rule could see it.

The ignored listing now uses `git status --porcelain -z --ignored=matching
--untracked-files=all`: a directory matching an ignore pattern is one entry,
everything else ignored is listed per file, including ignored files inside
untracked directories. A matched directory is collapsed into one exclusion
line only when HardExcludedDirectory says every path beneath it is a hard
exclusion (built-in generated, runtime, or VCS component, or an exclude_dirs
component); any other matched directory is expanded with the rc17 per-file
listing restricted to it through literal pathspecs. So no candidate, ignored
candidate, rule outcome, opt-in, role, or identity changes: a test pins the
collapsed inventory against the full listing over every ignored shape, and
the upgrade axis gains two shapes (a rule-pulled file under an ignored gen/,
an observed file under an ignored out/ beside an ignored node_modules) that
stay aligned across the upgrade. git before 2.16 falls back to the full
listing.

An earlier draft collapsed every ignored directory; a six-reviewer adversarial
pass with three skeptics per finding showed that it broke alignment for rc17
repositories whose full profile, starter rules, user rules, or opt-ins govern
files inside ignored directories, hid ignored files inside untracked
directories, and regressed scope explain. The narrowed design above is the
fix; the review report is in the maintainer's notes.

A failed inventory git query keeps its machine code
(safe_inventory_git_query_failed) and now carries git's stderr, captured in a
bounded 2 KiB tail cut at a rune boundary. init, scan, scope, and source
manifest print it verbatim on the next stderr line in human and --json modes
(scan and the scope/source errors keep the cause through causedError and
ExitError.Unwrap, with unchanged text and exit codes); docs/localization.md
records this as the one verbatim exception. verify, check, and Maintain report
the failure as a business-source finding, and the troubleshooting page says to
run scan. scope explain reports a path under an exclude_dirs directory as a
configured exclusion.

init --agent qoder writes the project .mcp.json Qoder CLI reads (verified with
@qoder-ai/qodercli 1.1.64: `qoder mcp list` shows the server Connected), the
AGENTS.md block, and no hook; doctor and the aoci ui page gain a Qoder row
backed by that file; Detect recognises a project .qoder or user ~/.qoder.

The Maintain authoring instructions now say batches are not user decisions:
after a successful write call Maintain again until remaining is 0 without
asking. A Qoder session stopped after the first of 53 batches to ask.

config --json follow-ups on top of #95: set's value is what the next get
returns (merged view, with the saved team value as the fallback when a local
file is unreadable), and a locale set carries restart_mcp_required and the
pending migration numbers human mode prints; human output is byte-for-byte
identical to rc17 across list, every get key, and the sets.

Tests: internal/fs (collapse, equivalence with the full listing, untracked
directories, tracked-beside-ignored, old-git fallback, literal bounded
pathspecs, bounded rune-safe tail, listing failure stderr, Windows junction
cycle), internal/cli (init and scan stderr detail in both locales, Qoder init,
doctor row, config JSON types and merged value, locale set facts),
internal/hooks (Qoder install message, Detect project and user level),
internal/ui (qoder_mcp key). Black-box: lifecycle bringup plants an ignored
node_modules with a real symlink cycle; upgrade axis 64 checks per version
over eight shapes. make update-goldens is unchanged.

Closes #97

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@alkor2000

Copy link
Copy Markdown
Contributor

In rc18 as d3716a8, under your authorship. Governance files were regenerated here, same as every landed PR.

alkor2000 added a commit that referenced this pull request Oct 6, 2026
rc18 shipped typed --json output for config list, get, and set (PR #95 and
its follow-ups), but the CLI runtime contract had no configuration section,
so the result shapes were a public surface automation could rely on without
a contract behind them. Add one: list emits the effective configuration
object, get a typed JSON value, and set an {ok, key, value} result whose
value is what the next get returns (the merged view, so a local override
shows there as it shows here), plus restart_mcp_required only for locale and
locale_migration only when a migration is pending; an unknown key or invalid
value exits 3 with the version-1 error envelope before any write; human
output is unchanged.

Gates: make fast, scripts/check-public-text.sh. Governance: the contract's
Entry re-authored through the rc18 MCP service; verify, check, and guide
aligned.

Co-Authored-By: Claude Fable 5.1 <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.

config commands should honor the global --json output contract

2 participants