Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 16 additions & 8 deletions .aoci/baseline.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"version": 1,
"created_at": "2026-08-07T17:44:36Z",
"updated_at": "2026-09-29T16:15:50Z",
"updated_at": "2026-10-02T19:10:57Z",
"files": {
".gitattributes": {
"role": "index",
Expand Down Expand Up @@ -137,9 +137,9 @@
},
"aoci.code.txt": {
"role": "index",
"sha256": "8567b2fde0aa491340d77d6dd2022c01cb0d12e566ba262678e36f7222f98206",
"size": 195231,
"normalized_sha256": "8567b2fde0aa491340d77d6dd2022c01cb0d12e566ba262678e36f7222f98206"
"sha256": "979a39b5d832a94c186d55de87c77594cb627085d4d00c71e3c5dfeabe9408c8",
"size": 195187,
"normalized_sha256": "979a39b5d832a94c186d55de87c77594cb627085d4d00c71e3c5dfeabe9408c8"
},
"aoci.meta.txt": {
"role": "index",
Expand Down Expand Up @@ -800,10 +800,10 @@
},
"internal/cli/config.go": {
"role": "index",
"sha256": "a8cd1f352a109f2b38ec1204f0abcde03bd316e248482a468037765c5183f6f1",
"size": 8820,
"normalized_sha256": "a8cd1f352a109f2b38ec1204f0abcde03bd316e248482a468037765c5183f6f1",
"format_sha256": "a8cd1f352a109f2b38ec1204f0abcde03bd316e248482a468037765c5183f6f1",
"sha256": "8efdc62b33ee232093b13e63f3dece3cbe0a345b763adb565d33f939de1ed9ad",
"size": 9821,
"normalized_sha256": "8efdc62b33ee232093b13e63f3dece3cbe0a345b763adb565d33f939de1ed9ad",
"format_sha256": "8efdc62b33ee232093b13e63f3dece3cbe0a345b763adb565d33f939de1ed9ad",
"format_kind": "gofmt"
},
"internal/cli/config_automation_test.go": {
Expand All @@ -822,6 +822,14 @@
"format_sha256": "60f1f2051cb2cf9c0bcd586e385ff46d6a1fbf3920da9aea7d0c25264458ac88",
"format_kind": "gofmt"
},
"internal/cli/config_json_test.go": {
"role": "observe",
"sha256": "0d2f693496f2ea29f672eddad18dbec1b242240bee02ad64f4d4833a15457679",
"size": 2990,
"normalized_sha256": "0d2f693496f2ea29f672eddad18dbec1b242240bee02ad64f4d4833a15457679",
"format_sha256": "0d2f693496f2ea29f672eddad18dbec1b242240bee02ad64f4d4833a15457679",
"format_kind": "gofmt"
},
"internal/cli/config_overview_delivery_test.go": {
"role": "observe",
"sha256": "6ed50cb28c05901eae849240d8494c0f3c7a89bb58c876eb947c1264f6b64655",
Expand Down
2 changes: 1 addition & 1 deletion aoci.code.txt
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ cognition_migration.go[CG7L]: F:Implements governed Legacy-to-Volumes migration
cognition_onboard.go[CG8M]: F:Exposes persistent model-authored onboarding from session start and evidence batching through preview, preparation, Apply, resume, status, and abort | R:code:internal/onboarding/workflow.go,code:internal/bootstrapapply/prepare.go,code:internal/migrationapply/prepare.go | A:aoci cognition onboard | S:Review preparation emits a human TTY digest-confirmation boundary; policy-bound auto preparation does not make the model an approver
cognition_plan.go[CG7S]: F:Routes deterministic cognition bootstrap, migration, and candidate-validation planning commands and writes planner artifacts to operator-chosen paths | R:code:internal/fs/atomic.go | A:writePlannerArtifact,validatePlannerOutput | S:An artifact path is validated before any human is asked, because a spent confirmation cannot be reused; the file is created only when absent and is owner-readable while it can stand in for that human
command_localization.go[CG7M]: F:Applies localized command names, help text, and errors without changing machine tokens | R:- | A:- | S:A flag absent from localizedFlagNames silently renders its English usage under every locale, and two message values that are byte-identical make --help fail closed instead
config.go[CG7M]: F:Implements team configuration list, merged-value get, and governed base-layer set commands | R:code:internal/config/config.go,code:textassets/catalog.go | A:aoci config list,aoci config get,aoci config set | S:Set loads only team base configuration so local endpoint overrides cannot leak; locale changes create migration state and need an MCP restart; batch cap and transport budget are range-checked here
config.go[CG7M]: F:Implements team configuration list, typed JSON get and set output, and governed base-layer updates | R:code:internal/config/config.go,code:textassets/catalog.go | A:aoci config list,aoci config get,aoci config set,--json | S:JSON mode uses Cobra output and typed values while human mode stays localized; team config remains isolated from local endpoint overrides
database.go[CG7M]: F:Registers database evidence commands and manages credential-free source declarations, readiness checks, and read-only catalog inspection | R:code:internal/dbevidence/config.go,code:internal/dbevidence/access.go,code:internal/dbevidence/collector.go | A:aoci database,aoci database source | S:Config stores only the credential environment-variable name, never a secret; source add/list are offline; missing-variable guidance requires Host-environment provisioning outside Agent inputs
database_cognition.go[CG7S]: F:Implements evidence-bound Database Cognition status and bootstrap workflows with a stable outer error and redacted failure diagnostics | R:code:internal/databasebootstrap/prepare.go,code:internal/dbcognition/state.go,code:internal/machinecontract/status.go | A:aoci database cognition status,aoci database cognition bootstrap,database_bootstrap_stopped,cause_code,safe_next_action | S:Stopped-bootstrap Details never include wrapped errors, source IDs, paths, endpoints, or credentials; unknown failures collapse to the generic stopped cause
database_evidence.go[CG7S]: F:Implements Database Evidence bundle access, independent baseline acceptance, and Code-context projection for table cognition | R:code:internal/dbevidence/store.go,code:internal/databasebootstrap/transaction.go,code:internal/cognition/impact.go | A:aoci database evidence,aoci database baseline accept | S:Evidence bundles expose catalog structure only and report business_data_read=false; baseline acceptance may auto-bootstrap missing Database Cognition only through its governed transaction
Expand Down
110 changes: 75 additions & 35 deletions internal/cli/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,15 @@ func init() {
if err != nil {
return &ExitError{Code: ExitConfig, Msg: err.Error()}
}
out, _ := json.MarshalIndent(cfg, "", " ")
fmt.Println(string(out))
return nil
if flagJSON {
return writePlannerJSON(cmd, cfg)
}
out, err := json.MarshalIndent(cfg, "", " ")
if err != nil {
return err
}
_, err = fmt.Fprintln(cmd.OutOrStdout(), string(out))
return err
},
}

Expand All @@ -57,34 +63,8 @@ func init() {
return &ExitError{Code: ExitConfig, Msg: err.Error()}
}

switch args[0] {
case "exclude_dirs":
fmt.Println(strings.Join(cfg.ExcludeDirs, ","))
case "exclude_files":
fmt.Println(strings.Join(cfg.ExcludeFiles, ","))
case "curation_exclude":
fmt.Println(strings.Join(cfg.CurationExclude, ","))
case "index_path":
fmt.Println(cfg.IndexPath)
case "locale":
fmt.Println(cfg.Locale)
case "hook_strict":
fmt.Println(cfg.HookStrict)
case "ledger_enabled":
fmt.Println(cfg.LedgerEnabled)
case "installed_agents":
fmt.Println(strings.Join(cfg.InstalledAgents, ","))
case "automation_mode":
fmt.Println(cfg.EffectiveAutomationMode())
case "cognition_refresh_threshold":
fmt.Println(cfg.CognitionRefreshThreshold)
case "overview_delivery.chunk_tokens":
fmt.Println(cfg.OverviewDelivery.ChunkTokens)
case "code_cognition_batch_entries":
fmt.Println(cfg.CodeCognitionBatchLimit())
case "maintain_transport_budget_bytes":
fmt.Println(cfg.MaintainTransportBudget())
default:
value, ok := configValue(cfg, args[0], flagJSON)
if !ok {
return &ExitError{
Code: ExitConfig,
Msg: cliMessage(
Expand All @@ -94,7 +74,11 @@ func init() {
),
}
}
return nil
if flagJSON {
return writePlannerJSON(cmd, value)
}
_, err = fmt.Fprintln(cmd.OutOrStdout(), value)
return err
},
}

Expand Down Expand Up @@ -252,18 +236,29 @@ func init() {
return &ExitError{Code: ExitConfig, Msg: err.Error()}
}
}
if flagJSON {
value, ok := configValue(cfg, key, true)
if !ok {
return &ExitError{Code: ExitConfig, Msg: cliMessage("config.unknown_key", key, "")}
}
return writePlannerJSON(cmd, struct {
OK bool `json:"ok"`
Key string `json:"key"`
Value any `json:"value"`
}{OK: true, Key: key, Value: value})
}
if !flagQuiet {
fmt.Println(cliMessage("config.saved", key))
_, _ = fmt.Fprintln(cmd.OutOrStdout(), cliMessage("config.saved", key))
if key == "locale" {
if cfg.LocaleMigration != nil {
fmt.Println(cliMessage(
_, _ = fmt.Fprintln(cmd.OutOrStdout(), cliMessage(
"config.locale_migration_pending",
cfg.LocaleMigration.HeaderPending,
len(cfg.LocaleMigration.EntryPaths),
len(cfg.LocaleMigration.CurationPaths),
))
}
fmt.Println(cliMessage("config.restart_mcp"))
_, _ = fmt.Fprintln(cmd.OutOrStdout(), cliMessage("config.restart_mcp"))
}
}
return nil
Expand All @@ -274,6 +269,51 @@ func init() {
registerCommand(cmd)
}

func configValue(cfg *config.Config, key string, jsonMode bool) (any, bool) {
switch key {
case "exclude_dirs":
if jsonMode {
return cfg.ExcludeDirs, true
}
return strings.Join(cfg.ExcludeDirs, ","), true
case "exclude_files":
if jsonMode {
return cfg.ExcludeFiles, true
}
return strings.Join(cfg.ExcludeFiles, ","), true
case "curation_exclude":
if jsonMode {
return cfg.CurationExclude, true
}
return strings.Join(cfg.CurationExclude, ","), true
case "index_path":
return cfg.IndexPath, true
case "locale":
return cfg.Locale, true
case "hook_strict":
return cfg.HookStrict, true
case "ledger_enabled":
return cfg.LedgerEnabled, true
case "installed_agents":
if jsonMode {
return cfg.InstalledAgents, true
}
return strings.Join(cfg.InstalledAgents, ","), true
case "automation_mode":
return cfg.EffectiveAutomationMode(), true
case "cognition_refresh_threshold":
return cfg.CognitionRefreshThreshold, true
case "overview_delivery.chunk_tokens":
return cfg.OverviewDelivery.ChunkTokens, true
case "code_cognition_batch_entries":
return cfg.CodeCognitionBatchLimit(), true
case "maintain_transport_budget_bytes":
return cfg.MaintainTransportBudget(), true
default:
return nil, false
}
}

func splitCSV(raw string) []string {
out := []string{}
for _, part := range strings.Split(raw, ",") {
Expand Down
97 changes: 97 additions & 0 deletions internal/cli/config_json_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package cli

import (
"bytes"
"encoding/json"
"testing"

"github.com/aoci-spec/aoci-code/internal/config"
)

func TestConfigCommandsHonorJSONOutput(t *testing.T) {
root := t.TempDir()
if err := config.Save(root, legacyTestConfig()); err != nil {
t.Fatal(err)
}

t.Run("list", func(t *testing.T) {
stdout, stderr := runConfigJSON(t, root, "list")
var value map[string]any
if err := json.Unmarshal(stdout, &value); err != nil {
t.Fatalf("config list did not return JSON: %v\n%s", err, stdout)
}
if _, ok := value["locale"]; !ok {
t.Fatalf("config list JSON omitted locale: %s", stdout)
}
if len(stderr) != 0 {
t.Fatalf("config list wrote diagnostics on success: %s", stderr)
}
})

t.Run("get", func(t *testing.T) {
stdout, stderr := runConfigJSON(t, root, "get", "hook_strict")
var value bool
if err := json.Unmarshal(stdout, &value); err != nil {
t.Fatalf("config get did not return a JSON scalar: %v\n%s", err, stdout)
}
if value != legacyTestConfig().HookStrict {
t.Fatalf("config get returned %v, want %v", value, legacyTestConfig().HookStrict)
}
if len(stderr) != 0 {
t.Fatalf("config get wrote diagnostics on success: %s", stderr)
}
})

t.Run("set", func(t *testing.T) {
stdout, stderr := runConfigJSON(t, root, "set", "ledger_enabled", "false")
var result struct {
OK bool `json:"ok"`
Key string `json:"key"`
Value bool `json:"value"`
}
if err := json.Unmarshal(stdout, &result); err != nil {
t.Fatalf("config set did not return JSON: %v\n%s", err, stdout)
}
if !result.OK || result.Key != "ledger_enabled" || result.Value {
t.Fatalf("unexpected config set result: %+v", result)
}
if len(stderr) != 0 {
t.Fatalf("config set wrote diagnostics on success: %s", stderr)
}
updated, err := config.LoadBase(root)
if err != nil {
t.Fatal(err)
}
if updated.LedgerEnabled {
t.Fatal("config set did not persist the requested value")
}
})
}

func TestConfigGetUsesCobraOutputWriter(t *testing.T) {
root := t.TempDir()
if err := config.Save(root, legacyTestConfig()); err != nil {
t.Fatal(err)
}

var stdout, stderr bytes.Buffer
if code := executeCLI([]string{"--repo", root, "config", "get", "locale"}, &stdout, &stderr); code != ExitOK {
t.Fatalf("config get failed: code=%d stdout=%s stderr=%s", code, stdout.String(), stderr.String())
}
if stdout.Len() == 0 {
t.Fatal("config get did not write to the command output writer")
}
if stderr.Len() != 0 {
t.Fatalf("config get wrote diagnostics on success: %s", stderr.String())
}
}

func runConfigJSON(t *testing.T, root string, args ...string) ([]byte, []byte) {
t.Helper()
var stdout, stderr bytes.Buffer
arguments := append([]string{"--repo", root, "--json", "config"}, args...)
if code := executeCLI(arguments, &stdout, &stderr); code != ExitOK {
t.Fatalf("config command failed: code=%d stdout=%s stderr=%s", code, stdout.String(), stderr.String())
}
return stdout.Bytes(), stderr.Bytes()
}