mirror of
https://github.com/instructkr/claw-code.git
synced 2026-06-06 01:42:47 -04:00
Compare commits
12 Commits
60f44d314b
...
89e7f415a9
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
89e7f415a9 | ||
|
|
c3e7b6af60 | ||
|
|
3af2d9f986 | ||
|
|
09ff1caf42 | ||
|
|
0e6d48d9dc | ||
|
|
b7ea04661a | ||
|
|
73d8d6e638 | ||
|
|
9ac66cbeb3 | ||
|
|
773aa021be | ||
|
|
5c3e1c1444 | ||
|
|
3260258b56 | ||
|
|
a88d52fe88 |
@@ -55,8 +55,15 @@ REQUIRED_ITEM_FIELDS = [
|
||||
|
||||
|
||||
def load_board(path: Path) -> dict[str, Any]:
|
||||
with path.open() as f:
|
||||
board = json.load(f)
|
||||
try:
|
||||
with path.open() as f:
|
||||
board = json.load(f)
|
||||
except FileNotFoundError:
|
||||
raise ValueError(f"board not found at {path}") from None
|
||||
except IsADirectoryError:
|
||||
raise ValueError(f"board path is a directory: {path}") from None
|
||||
except json.JSONDecodeError as exc:
|
||||
raise ValueError(f"invalid board JSON at {path}: {exc}") from None
|
||||
if not isinstance(board, dict):
|
||||
raise ValueError("board JSON root must be an object")
|
||||
items = board.get("items")
|
||||
@@ -226,7 +233,11 @@ def main() -> int:
|
||||
parser.add_argument("--check", action="store_true", help="fail if board_md is not up to date")
|
||||
args = parser.parse_args()
|
||||
|
||||
board = load_board(args.board_json)
|
||||
try:
|
||||
board = load_board(args.board_json)
|
||||
except ValueError as exc:
|
||||
print(f"ERROR: {exc}", file=sys.stderr)
|
||||
return 1
|
||||
errors = validate_board(board)
|
||||
if errors:
|
||||
for error in errors:
|
||||
@@ -234,14 +245,22 @@ def main() -> int:
|
||||
return 1
|
||||
rendered = render(board)
|
||||
if args.check:
|
||||
existing = args.board_md.read_text() if args.board_md.exists() else ""
|
||||
try:
|
||||
existing = args.board_md.read_text() if args.board_md.exists() else ""
|
||||
except IsADirectoryError:
|
||||
print(f"ERROR: board markdown path is a directory: {args.board_md}", file=sys.stderr)
|
||||
return 1
|
||||
if existing != rendered:
|
||||
print(f"ERROR: {args.board_md} is not up to date", file=sys.stderr)
|
||||
return 1
|
||||
print(f"PASS: {args.board_md} is up to date and roadmap coverage is complete")
|
||||
return 0
|
||||
args.board_md.parent.mkdir(parents=True, exist_ok=True)
|
||||
args.board_md.write_text(rendered)
|
||||
try:
|
||||
args.board_md.write_text(rendered)
|
||||
except IsADirectoryError:
|
||||
print(f"ERROR: board markdown path is a directory: {args.board_md}", file=sys.stderr)
|
||||
return 1
|
||||
print(f"wrote {args.board_md}")
|
||||
return 0
|
||||
|
||||
|
||||
31
ROADMAP.md
31
ROADMAP.md
@@ -7783,3 +7783,34 @@ Original filing (2026-04-18): the session emitted `SessionStart hook (completed)
|
||||
809. **Top-level help/version/MCP/plugin JSON spellings hang with zero stdout in trailing `--output-format json` form instead of returning bounded JSON/help or typed unsupported envelopes** — dogfooded 2026-05-27 on rebuilt main `db81598` (`cargo build --manifest-path rust/Cargo.toml -p rusty-claude-cli`; `claw --version` Git SHA `db81598`). `help --output-format json`, `version --output-format json`, `mcp --output-format json`, `mcp help --output-format json`, `plugins --output-format json`, and `plugins help --output-format json` each timed out under an 8s outer timeout with stdout `0`; stderr only contained the local deprecated `enabledPlugins` settings warning. Leading global-style probes (`--help --output-format json`, `--version --output-format json`) fail immediately as `[error-kind: cli_parse] unknown option`, so the hang is again in the trailing subcommand-style routing/startup path. **Required fix shape:** treat help/version/MCP/plugin discovery surfaces as bounded non-interactive control-plane commands; either return JSON help/list/version payloads or standard typed JSON unsupported envelopes with `error_kind`, non-null `hint`, and `message`; add timeout/nonzero-stdout regression coverage for the six trailing repro commands and parser-envelope coverage for leading global-style spellings. **Why this matters:** claws need safe scriptable help/version/plugin/MCP discovery before provider/session startup; silent hangs hide whether a command is unsupported, misparsed, or initializing runtime state. Source: gaebal-gajae 19:00 dogfood probe. [SCOPE: claw-code]
|
||||
810. **TTY JSON success for `config`/`plugins --output-format json` contaminates stdout with deprecated-settings warnings before the JSON object** — dogfooded 2026-05-27 on rebuilt main `db81598` after #809. Under pseudo-TTY (`script -q -c "./rust/target/debug/claw config --output-format json"` and `plugins --output-format json`), the commands return rc `0` and bounded JSON, but stdout begins with `warning: /home/bellman/.claw/settings.json: field "enabledPlugins" is deprecated ...` before the JSON object (`first_json_index=121`). Parsing succeeds only after manually stripping the warning/prefix; raw stdout is not valid JSON. **Required fix shape:** in JSON mode, keep diagnostics/warnings on stderr or include structured warning fields inside the JSON envelope, but never prepend human warnings to stdout; add regression coverage that raw stdout from JSON commands parses from byte 0 under TTY and non-TTY modes. **Why this matters:** even when the TTY path avoids the hang from #807/#808/#809, claws and scripts still cannot safely `json.loads(stdout)` if configuration warnings are mixed into stdout. Source: gaebal-gajae 20:00 pseudo-TTY dogfood probe. [SCOPE: claw-code]
|
||||
811. **Previously typed JSON error/list surfaces hang in plain non-TTY trailing `--output-format json` form instead of emitting their JSON envelopes** — dogfooded 2026-05-27 on rebuilt main `b0e94c9` after #810. In plain non-TTY automation, `agents list --bogus --output-format json`, `skills show does-not-exist --output-format json`, `plugins show does-not-exist --output-format json`, `diff --output-format json`, `sessions show does-not-exist --output-format json`, and `resume bogus --output-format json` each timed out under an 8s outer timeout with stdout `0`; stderr only contained the local deprecated `enabledPlugins` settings warning. Several of these surfaces had prior roadmap fixes for typed JSON/text envelopes, so this is a regression-class scriptability gap: the command-specific envelope may exist, but plain non-TTY trailing JSON invocation routes into interactive startup before reaching it. **Required fix shape:** ensure trailing `--output-format json` is honored before any interactive/provider/session startup for error/list surfaces; add plain non-TTY timeout regression coverage that asserts raw stdout is a parseable typed JSON envelope for the six repro commands, including `error_kind`, non-null `hint`, and `message` where applicable. **Why this matters:** claws primarily invoke CLI checks from non-TTY automation; a fix that only works in manual/TTY mode still leaves JSON error handling unusable for agents. Source: gaebal-gajae 20:30 dogfood probe. [SCOPE: claw-code]
|
||||
|
||||
|
||||
812. **`claw --output-format json doctor --help` must stay a local help fast path and never fall through into runtime/provider startup** — dogfooded 2026-05-28 04:01 UTC after #701 worktree drift. The reported repro was `cargo run -q --bin claw -- --output-format json doctor --help`, which did not produce local help promptly and had to be killed, while the positive control `cargo run -q --bin claw -- --output-format json --help` emitted valid JSON help. Fresh bounded repro on branch `fix/doctor-help-json-local` did not reproduce the hang on current code (`timeout 5s cargo run -q --bin claw -- --output-format json doctor --help` exited 0 with a `kind:"help"`/`status:"ok"` doctor help envelope), which means the parser fast path is present but under-tested for this exact dogfood surface.
|
||||
|
||||
**Pinpoint.** The guarded path is `rust/crates/rusty-claude-cli/src/main.rs`: global `--output-format json` is parsed before `rest`, `parse_local_help_action()` maps `doctor --help` to `CliAction::HelpTopic { topic: Doctor }`, and `print_help_topic()` must return without calling `run_doctor()`, `LiveCli`, provider setup, session resume, or runtime startup. The previous risk class is help fallthrough: treating `doctor --help` as prompt text or as `doctor` diagnostics would either hit provider/session startup or run checks instead of local help.
|
||||
|
||||
**Fix.** Keep the local help interception and strengthen the doctor JSON help contract with structured, machine-readable metadata: `usage`, `formats`, `local_only:true`, `requires_credentials:false`, `requires_provider_request:false`, `requires_session_resume:false`, `mutates_workspace:false`, `output_fields`, `check_names`, and `status_values`. Preserve prose-only text help for `claw doctor --help`.
|
||||
|
||||
**Acceptance.** `timeout 5s cargo run -q --bin claw -- --output-format json doctor --help` exits 0 and parses as JSON with `.kind=="help"`, `.command=="doctor"`, `.local_only==true`, `.requires_provider_request==false`, and `output_fields` containing `checks`. `timeout 5s cargo run -q --bin claw -- doctor --help` exits 0 with plaintext beginning `Doctor` and no JSON parsing requirement. Neither command starts a provider request or session resume.
|
||||
|
||||
**Verification.** Regression tests: `doctor_help_json_is_local_structured_and_bounded_702` and `doctor_help_text_stays_plaintext_and_local_702` in `rust/crates/rusty-claude-cli/tests/output_format_contract.rs`; focused command repros recorded in `/tmp/claw_doctor_help_json.out` and `/tmp/claw_doctor_help_json.err` during the doctor-help fix branch.
|
||||
|
||||
813. **Dogfood probe shell-string loops can fabricate CLI argv failures for JSON help surfaces** — dogfooded 2026-05-28 after #3185 merged. A verification loop used a single shell string variable (`cmd="--output-format json doctor --help"`; then `cargo run -q --bin claw -- $cmd | python3 -c 'json.load(...)'`). The resulting channel transcript showed `unknown option: --output-format json doctor --help` and Python JSON parse stack noise, even though explicit argv invocations on fresh `main` all returned valid JSON: `cargo run -q --bin claw -- --output-format json doctor --help`, `cargo run -q --bin claw -- doctor --help --output-format json`, and `cargo run -q --bin claw -- help doctor --output-format json`. This is a dogfood-harness test-brittleness / event-log opacity gap, not a product parser regression. The log made a probe-construction mistake look like a claw-code failure.
|
||||
|
||||
**Required fix shape.** Add a tiny argv-safe dogfood helper (script or documented recipe) that runs CLI probes as explicit argv arrays rather than interpolated shell strings, captures stdout/stderr separately, and labels probe-construction failures distinctly from product failures. For ad-hoc shell loops, prefer arrays/functions (`run_probe --output-format json doctor --help`) over `$cmd` strings; never pipe unknown stdout directly into a JSON parser without first recording rc/stdout/stderr.
|
||||
|
||||
**Acceptance.** Future dogfood reports for argv-sensitive CLI surfaces include the exact argv vector and can distinguish `probe_error` from `product_error`; reproducing the three doctor-help forms through the helper yields three parseable JSON objects from byte 0 without Python parser stack noise. [SCOPE: claw-code dogfood harness]
|
||||
|
||||
814. **Plain non-TTY trailing `--output-format json` still times out for inventory/error surfaces after #3186** — dogfooded 2026-05-28 07:00 on fresh `main` `0e6d48d9d` after #3186 merged. Using explicit argv probes with separated stdout/stderr (per #813) reproduced the older #811 class on current main: `cargo run -q --bin claw -- agents list --bogus --output-format json`, `skills show does-not-exist --output-format json`, and `plugins show does-not-exist --output-format json` each hit the 5s timeout (`rc=124`) with `stdout` length 0; stderr contained only compile warnings plus the local deprecated `enabledPlugins` settings warning. This confirms the argv-safe probe harness can distinguish product failure from probe-construction failure, and the product gap remains for trailing JSON flag forms on inventory/error surfaces.
|
||||
|
||||
**Required fix shape.** Parse trailing `--output-format json` for local inventory/error commands before any REPL/provider startup in plain non-TTY mode, matching the already-working leading global form where applicable. Add timeout regression coverage for at least `agents list --bogus --output-format json`, `skills show does-not-exist --output-format json`, and `plugins show does-not-exist --output-format json` asserting nonzero stdout with a single parseable JSON envelope containing `status:"error"`, `error_kind`, and non-null `hint`. Keep deprecation/config warnings out of stdout in JSON mode.
|
||||
|
||||
**Acceptance.** All three repro commands exit within 5s under non-TTY automation, produce parseable JSON from stdout byte 0, and never require provider credentials/session startup. [SCOPE: claw-code]
|
||||
|
||||
**Follow-up verification (2026-05-28 07:30 on `main` `09ff1caf4`).** After #3187 merged, rerunning the same three commands with explicit argv showed the product path had already been fixed upstream: `agents list --bogus --output-format json` returned rc 1 with a JSON `unknown_option` envelope, `skills show does-not-exist --output-format json` returned rc 1 with `skill_not_found`, and `plugins show does-not-exist --output-format json` returned rc 1 with `plugin_not_found`. Stdout was nonzero and parseable in all three cases; warnings stayed on stderr. Remaining actionable lesson is process-level: ROADMAP record #814 is preserved as historical repro + verification, not an open product blocker.
|
||||
|
||||
815. **`claw --output-format json config` reports the same deprecated-settings warning twice: once structurally in `warnings[]` and once as prose on stderr** — dogfooded 2026-05-28 08:00 on current `main` after #3188. `timeout 5s cargo run -q --bin claw -- --output-format json config >out 2>err` exits 0 with parseable stdout JSON (`kind:"config", action:"list", status:"ok"`) and `warnings.length == 1`, but stderr still contains the same `enabledPlugins is deprecated` warning once. This is better than older stdout contamination, but still duplicates the same diagnostic across two channels in JSON mode. A machine consumer that reads the structured warning also sees an extra prose warning on stderr; a log scraper may count one config issue twice.
|
||||
|
||||
**Required fix shape.** In JSON mode for config/list surfaces that already include `warnings[]`, suppress eager prose emission of the same config warning on stderr or mark it as already collected. Text mode should keep the human stderr warning. Add regression coverage asserting `claw --output-format json config` returns exactly one structured warning and zero duplicate `enabledPlugins` prose lines on stderr.
|
||||
|
||||
**Acceptance.** With a deprecated `enabledPlugins` key present, `claw --output-format json config` exits 0, stdout parses from byte 0 and includes `warnings[]`, and stderr has no duplicate deprecation warning for the same file/key. [SCOPE: claw-code]
|
||||
|
||||
@@ -379,12 +379,6 @@ impl ConfigLoader {
|
||||
loaded_entries.push(entry);
|
||||
}
|
||||
|
||||
// Still emit to stderr for non-JSON callers that go through the normal load() path;
|
||||
// here we just *also* return them so callers can surface them structurally.
|
||||
for warning in &all_warnings {
|
||||
emit_config_warning_once(warning);
|
||||
}
|
||||
|
||||
let merged_value = JsonValue::Object(merged.clone());
|
||||
|
||||
let feature_config = RuntimeFeatureConfig {
|
||||
|
||||
@@ -1157,7 +1157,11 @@ fn parse_args(args: &[String]) -> Result<CliAction, String> {
|
||||
return action;
|
||||
}
|
||||
|
||||
let permission_mode = permission_mode_override.unwrap_or_else(default_permission_mode);
|
||||
// Keep config-backed defaults lazy so pure-local JSON surfaces (notably
|
||||
// `claw --output-format json config`) can report config warnings
|
||||
// structurally without an earlier default-resolution load writing prose
|
||||
// warnings to stderr.
|
||||
let permission_mode = || permission_mode_override.unwrap_or_else(default_permission_mode);
|
||||
|
||||
match rest[0].as_str() {
|
||||
"dump-manifests" => parse_dump_manifests_args(&rest[1..], output_format),
|
||||
@@ -1301,7 +1305,7 @@ fn parse_args(args: &[String]) -> Result<CliAction, String> {
|
||||
model,
|
||||
output_format,
|
||||
allowed_tools,
|
||||
permission_mode,
|
||||
permission_mode: permission_mode(),
|
||||
compact,
|
||||
base_commit,
|
||||
reasoning_effort: reasoning_effort.clone(),
|
||||
@@ -1338,7 +1342,7 @@ fn parse_args(args: &[String]) -> Result<CliAction, String> {
|
||||
model,
|
||||
output_format,
|
||||
allowed_tools,
|
||||
permission_mode,
|
||||
permission_mode: permission_mode(),
|
||||
compact,
|
||||
base_commit: base_commit.clone(),
|
||||
reasoning_effort: reasoning_effort.clone(),
|
||||
@@ -1350,7 +1354,7 @@ fn parse_args(args: &[String]) -> Result<CliAction, String> {
|
||||
model,
|
||||
output_format,
|
||||
allowed_tools,
|
||||
permission_mode,
|
||||
permission_mode(),
|
||||
compact,
|
||||
base_commit,
|
||||
reasoning_effort,
|
||||
@@ -1389,7 +1393,7 @@ fn parse_args(args: &[String]) -> Result<CliAction, String> {
|
||||
model,
|
||||
output_format,
|
||||
allowed_tools,
|
||||
permission_mode,
|
||||
permission_mode: permission_mode(),
|
||||
compact,
|
||||
base_commit,
|
||||
reasoning_effort: reasoning_effort.clone(),
|
||||
@@ -7885,10 +7889,51 @@ fn render_export_help_json() -> serde_json::Value {
|
||||
})
|
||||
}
|
||||
|
||||
fn render_doctor_help_json() -> serde_json::Value {
|
||||
json!({
|
||||
"kind": "help",
|
||||
"action": "help",
|
||||
"status": "ok",
|
||||
"topic": "doctor",
|
||||
"command": "doctor",
|
||||
"schema_version": "1.0",
|
||||
"usage": "claw doctor [--output-format <format>]",
|
||||
"purpose": "diagnose local auth, config, workspace, sandbox, boot preflight, and build metadata",
|
||||
"formats": ["text", "json"],
|
||||
"local_only": true,
|
||||
"requires_credentials": false,
|
||||
"requires_provider_request": false,
|
||||
"requires_session_resume": false,
|
||||
"mutates_workspace": false,
|
||||
"output_fields": ["kind", "action", "status", "message", "report", "has_failures", "summary", "checks"],
|
||||
"check_names": ["auth", "config", "install source", "workspace", "boot preflight", "sandbox", "system"],
|
||||
"status_values": ["ok", "warn", "fail"],
|
||||
"options": [
|
||||
{
|
||||
"name": "--output-format",
|
||||
"value": "<format>",
|
||||
"values": ["text", "json"],
|
||||
"default": "text",
|
||||
"description": "format for the doctor report or help envelope"
|
||||
},
|
||||
{
|
||||
"name": "--help",
|
||||
"aliases": ["-h"],
|
||||
"description": "show help for the doctor command without running diagnostics"
|
||||
}
|
||||
],
|
||||
"related": ["/doctor", "claw --resume latest /doctor"],
|
||||
"message": render_help_topic(LocalHelpTopic::Doctor),
|
||||
})
|
||||
}
|
||||
|
||||
fn render_help_topic_json(topic: LocalHelpTopic) -> serde_json::Value {
|
||||
if topic == LocalHelpTopic::Export {
|
||||
return render_export_help_json();
|
||||
}
|
||||
if topic == LocalHelpTopic::Doctor {
|
||||
return render_doctor_help_json();
|
||||
}
|
||||
|
||||
json!({
|
||||
"kind": "help",
|
||||
|
||||
@@ -66,6 +66,64 @@ fn export_help_preserves_plaintext_in_text_mode_384() {
|
||||
serde_json::from_str::<Value>(&stdout).expect_err("text help should remain plaintext");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn doctor_help_json_is_local_structured_and_bounded_702() {
|
||||
let root = unique_temp_dir("doctor-help-json-702");
|
||||
fs::create_dir_all(&root).expect("temp dir should exist");
|
||||
|
||||
let parsed = assert_json_command(&root, &["--output-format", "json", "doctor", "--help"]);
|
||||
assert_doctor_help_json_contract(&parsed);
|
||||
|
||||
let suffix_parsed =
|
||||
assert_json_command(&root, &["doctor", "--help", "--output-format", "json"]);
|
||||
assert_doctor_help_json_contract(&suffix_parsed);
|
||||
|
||||
let help_topic_parsed =
|
||||
assert_json_command(&root, &["help", "doctor", "--output-format", "json"]);
|
||||
assert_doctor_help_json_contract(&help_topic_parsed);
|
||||
}
|
||||
|
||||
fn assert_doctor_help_json_contract(parsed: &Value) {
|
||||
assert_eq!(parsed["kind"], "help");
|
||||
assert_eq!(parsed["action"], "help");
|
||||
assert_eq!(parsed["status"], "ok");
|
||||
assert_eq!(parsed["topic"], "doctor");
|
||||
assert_eq!(parsed["command"], "doctor");
|
||||
assert_eq!(parsed["usage"], "claw doctor [--output-format <format>]");
|
||||
assert_eq!(parsed["local_only"], true);
|
||||
assert_eq!(parsed["requires_credentials"], false);
|
||||
assert_eq!(parsed["requires_provider_request"], false);
|
||||
assert_eq!(parsed["requires_session_resume"], false);
|
||||
assert_eq!(parsed["mutates_workspace"], false);
|
||||
|
||||
let fields = parsed["output_fields"].as_array().expect("output_fields");
|
||||
assert!(fields.iter().any(|field| field == "checks"));
|
||||
let statuses = parsed["status_values"].as_array().expect("status_values");
|
||||
assert!(statuses.iter().any(|status| status == "warn"));
|
||||
let checks = parsed["check_names"].as_array().expect("check_names");
|
||||
assert!(checks.iter().any(|check| check == "auth"));
|
||||
assert!(checks.iter().any(|check| check == "boot preflight"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn doctor_help_text_stays_plaintext_and_local_702() {
|
||||
let root = unique_temp_dir("doctor-help-text-702");
|
||||
fs::create_dir_all(&root).expect("temp dir should exist");
|
||||
|
||||
let output = run_claw(&root, &["doctor", "--help"], &[]);
|
||||
assert!(
|
||||
output.status.success(),
|
||||
"stdout:\n{}\n\nstderr:\n{}",
|
||||
String::from_utf8_lossy(&output.stdout),
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
let stdout = String::from_utf8(output.stdout).expect("stdout utf8");
|
||||
assert!(stdout.starts_with("Doctor\n"));
|
||||
assert!(stdout.contains("Usage claw doctor"));
|
||||
assert!(stdout.contains("no provider request or session resume required"));
|
||||
serde_json::from_str::<Value>(&stdout).expect_err("text help should remain plaintext");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn version_emits_json_when_requested() {
|
||||
let root = unique_temp_dir("version-json");
|
||||
@@ -1201,6 +1259,66 @@ fn inventory_commands_deduplicate_config_deprecation_warnings_per_process() {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn config_json_reports_deprecations_structurally_without_stderr_duplicate_815() {
|
||||
let root = unique_temp_dir("config-json-warning-815");
|
||||
let config_home = root.join("config-home");
|
||||
let home = root.join("home");
|
||||
fs::create_dir_all(&config_home).expect("config home should exist");
|
||||
fs::create_dir_all(&home).expect("home should exist");
|
||||
fs::write(
|
||||
config_home.join("settings.json"),
|
||||
r#"{"enabledPlugins": {}}"#,
|
||||
)
|
||||
.expect("deprecated config fixture should write");
|
||||
|
||||
let envs = [
|
||||
(
|
||||
"CLAW_CONFIG_HOME",
|
||||
config_home.to_str().expect("utf8 config home"),
|
||||
),
|
||||
("HOME", home.to_str().expect("utf8 home")),
|
||||
];
|
||||
let output = run_claw(&root, &["--output-format", "json", "config"], &envs);
|
||||
assert!(
|
||||
output.status.success(),
|
||||
"stdout:\n{}\n\nstderr:\n{}",
|
||||
String::from_utf8_lossy(&output.stdout),
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
|
||||
let parsed: Value =
|
||||
serde_json::from_slice(&output.stdout).expect("stdout should be valid json");
|
||||
let warnings = parsed["warnings"]
|
||||
.as_array()
|
||||
.expect("config JSON should include warnings[]");
|
||||
assert!(
|
||||
warnings.iter().any(|warning| warning
|
||||
.as_str()
|
||||
.is_some_and(|text| text.contains("field \"enabledPlugins\" is deprecated"))),
|
||||
"config JSON warnings[] should include enabledPlugins deprecation: {parsed}"
|
||||
);
|
||||
|
||||
let stderr = String::from_utf8(output.stderr).expect("stderr utf8");
|
||||
assert!(
|
||||
!stderr.contains("field \"enabledPlugins\" is deprecated"),
|
||||
"JSON config should not duplicate collected config deprecations on stderr:\n{stderr}"
|
||||
);
|
||||
|
||||
let text_output = run_claw(&root, &["config"], &envs);
|
||||
assert!(
|
||||
text_output.status.success(),
|
||||
"stdout:\n{}\n\nstderr:\n{}",
|
||||
String::from_utf8_lossy(&text_output.stdout),
|
||||
String::from_utf8_lossy(&text_output.stderr)
|
||||
);
|
||||
let text_stderr = String::from_utf8(text_output.stderr).expect("stderr utf8");
|
||||
assert!(
|
||||
text_stderr.contains("field \"enabledPlugins\" is deprecated"),
|
||||
"text config should keep human-readable config warnings on stderr"
|
||||
);
|
||||
}
|
||||
|
||||
fn assert_json_command(current_dir: &Path, args: &[&str]) -> Value {
|
||||
assert_json_command_with_env(current_dir, args, &[])
|
||||
}
|
||||
|
||||
@@ -13,6 +13,24 @@
|
||||
#
|
||||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
sed -n '2,12p' "$0" | sed 's/^# //; s/^#//'
|
||||
}
|
||||
|
||||
if [[ $# -gt 0 ]]; then
|
||||
case "$1" in
|
||||
--help|-h)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "error: unknown argument: $1" >&2
|
||||
usage >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
RUST_DIR="$REPO_ROOT/rust"
|
||||
BINARY="$RUST_DIR/target/debug/claw"
|
||||
@@ -60,8 +78,8 @@ echo " export CLAW=$BINARY" >&2
|
||||
echo "" >&2
|
||||
echo " Dogfood with isolated config (no real user config on stderr):" >&2
|
||||
echo " CLAW_ISOLATED=\$(mktemp -d)" >&2
|
||||
echo " trap 'rm -rf \"\$CLAW_ISOLATED\"' EXIT" >&2
|
||||
echo " CLAW_CONFIG_HOME=\$CLAW_ISOLATED \$CLAW plugins list --output-format json" >&2
|
||||
echo " rm -rf \$CLAW_ISOLATED" >&2
|
||||
echo "" >&2
|
||||
echo " cargo run overhead: ~1s/invocation vs 7ms for pre-built binary." >&2
|
||||
echo " Prefer pre-built binary (\$CLAW) for dogfood loops." >&2
|
||||
|
||||
@@ -49,6 +49,9 @@ def main() -> int:
|
||||
except FileNotFoundError:
|
||||
print(f"error: board not found at {board_path}")
|
||||
return 1
|
||||
except IsADirectoryError:
|
||||
print(f"error: board path is a directory: {board_path}")
|
||||
return 1
|
||||
except json.JSONDecodeError as exc:
|
||||
print(f"error: invalid board JSON at {board_path}: {exc}")
|
||||
return 1
|
||||
|
||||
Reference in New Issue
Block a user