mirror of
https://github.com/instructkr/claw-code.git
synced 2026-06-04 21:47:10 +08:00
fix: scaffold safe init settings
Generated with https://github.com/Yeachan-Heo/gajae-code Co-authored-by: Gajae Code <dev@gajae-code.com>
This commit is contained in:
@@ -6383,7 +6383,7 @@ Original filing (2026-04-18): the session emitted `SessionStart hook (completed)
|
||||
435. **DONE — failed resume is non-zero and side-effect free; `--compact` stays a prompt modifier** — fixed 2026-06-04 in `fix: keep failed resume side-effect free`. Fresh-workspace `claw --resume latest` exits 1 in text and JSON modes; text writes the restore failure to stderr, JSON writes a typed `no_managed_sessions` restore envelope to stdout, and failed lookup no longer creates `.claw/sessions/<fingerprint>/`. `SessionStore::from_cwd`/`from_data_dir` now only derive the fingerprinted path; session save remains responsible for creating it. Global `--compact` no longer starts the REPL when it has no prompt or stdin: it returns typed `missing_argument` with `argument:"prompt or subcommand"`. `claw --compact "hello"` remains shorthand prompt mode and reaches provider/auth validation rather than command-not-found. Regression coverage: `session_store_from_cwd_is_side_effect_free_until_save`, `resume_latest_missing_session_fails_without_creating_session_dirs_435`, `compact_flag_missing_argument_and_shorthand_prompt_contract_435`, and `parses_compact_flag_for_prompt_mode`; broader checks reran `runtime session_control`, `resume_slash_commands`, `output_format_contract`, `claw` bin tests, `cargo fmt --all -- --check`, `scripts/roadmap-check-ids.sh`, `git diff --check`, and `cargo build --workspace --locked`.
|
||||
|
||||
|
||||
436. **`claw init` shipped `.claw.json` template explicitly sets `permissions.defaultMode:"dontAsk"` — every user who runs `claw init` gets a config file that disables permission prompts by default; sibling: `init` creates an empty `.claw/` directory with no settings.json template inside, and when `.claw/` already exists it skips the whole artifact (no settings template materialized)** — dogfooded 2026-05-11 by Jobdori on `b8f989b6` in response to Clawhip pinpoint nudge at `1503313241751949335`. Reproduction: `mkdir /tmp/probe && cd /tmp/probe && claw init --output-format json` returns `artifacts:[{name:".claw/",status:"created"},{name:".claw.json",status:"created"},...]`. Inspecting the created `.claw.json`: `{"permissions":{"defaultMode":"dontAsk"}}`. This is the polar opposite of safe-by-default: every user who follows the documented onboarding flow (`claw init` after `curl install.sh`) ships their workspace with permission prompts disabled. Compounds with **#428** (default runtime permission_mode is `danger-full-access`) — between the runtime default and the init template, a fresh claw setup has zero user-facing safety friction. **Sibling: `.claw/` artifact is an empty directory.** After `claw init`, `find .claw -type f` returns nothing. No `settings.json`, no template, no scaffolding — just `mkdir .claw`. The `--help` description implies init produces a usable workspace, but `.claw/settings.json` (the project-scope counterpart of `~/.claw/settings.json`) is never templated. **Sibling: `.claw/` skip-on-exists drops the entire artifact.** If `.claw/` already exists (e.g., from a partial setup, a `--resume` failure side effect per #435, or manual creation), `claw init` returns `.claw/: skipped` and does not materialize any expected sub-content. The other artifacts (`.claw.json`, `.gitignore`, `CLAUDE.md`) are still created, but a future `claw skills install` or `claw plugins enable` may expect `.claw/` to contain template files that are now missing. **Required fix shape:** (a) the shipped `.claw.json` template must default to `permissions.defaultMode:"acceptEdits"` or `"plan"` (safe-by-default modes per #428 spec) — `"dontAsk"` requires explicit opt-in; (b) `claw init` must materialize `.claw/settings.json` with documented schema defaults inside `.claw/` so the directory is useful on its own; (c) when `.claw/` already exists, `init` must report `partial` status (not `skipped`) and still try to create missing sub-files like `.claw/settings.json` without overwriting existing files; (d) emit per-sub-file artifact entries for `.claw/settings.json` and `.claw/sessions/` (skipped status if absent, deferred-to-first-save acceptable) so automation knows what's present; (e) regression test: `claw init` produces a `.claw.json` whose `permissions.defaultMode` is NOT `dontAsk`; `.claw/` contains at least one templated file. **Why this matters:** init is the primary onboarding surface. Every first-time user piping `curl install.sh | sh && claw init` gets a workspace pre-configured to skip permission prompts — and that workspace gets committed to the user's repo via the `init`-added entry. The `.claw/` empty-directory bug means feature discovery (skills, plugins) lacks the scaffolding it implies. Cross-references #428 (runtime default permission_mode), #50/#87/#91/#94/#97/#101/#106/#115/#123 (permission-rule audit), #435 (filesystem side effects on failed resume). Source: Jobdori live dogfood, `b8f989b6`, 2026-05-11.
|
||||
436. **DONE — `claw init` scaffolds safe project settings and reports partial/deferred artifacts** — fixed 2026-06-04 in `fix: scaffold safe init settings`. The starter `.claw.json` and new `.claw/settings.json` template now both use `permissions.defaultMode:"acceptEdits"` instead of unsafe `dontAsk`. Fresh init materializes `.claw/settings.json`, keeps `.claw/sessions/` deferred until the first successful session save, and emits per-artifact entries for `.claw/`, `.claw/settings.json`, `.claw/sessions/`, `.claw.json`, `.gitignore`, and `CLAUDE.md`. When `.claw/` already exists but its settings template is missing, init creates `.claw/settings.json` without overwriting existing files and reports `.claw/` as `partial` rather than `skipped`; idempotent reruns keep existing artifacts skipped and session storage deferred. JSON init output now includes `partial[]` and `deferred[]` alongside `created[]`, `updated[]`, and `skipped[]`, and init help/USAGE document the artifact statuses. Regression coverage: `initialize_repo_creates_expected_files_and_gitignore_entries`, `initialize_repo_is_idempotent_and_preserves_existing_files`, `artifacts_with_status_partitions_fresh_and_idempotent_runs`, and `init_json_envelope_has_hint_and_already_initialized_783`.
|
||||
|
||||
|
||||
437. **`version --output-format json` omits build provenance fields — no `is_dirty`, `branch`, `commit_date`, `commit_timestamp`, `rustc_version`; `git_sha` is truncated to 7 chars instead of full 40-char hash; sibling: `executable_path` leaks the build host's path (`/tmp/claw-dog-0530/...`) into runtime output** — dogfooded 2026-05-11 by Jobdori on `8cf628a5` in response to Clawhip pinpoint nudge at `1503320791582900344`. Reproduction: `claw version --output-format json` returns `{"build_date":"2026-05-11","executable_path":"/tmp/claw-dog-0530/rust/target/release/claw","git_sha":"b98b9a7","kind":"version","message":"Claw Code\n Version 0.1.0\n Git SHA b98b9a7\n Target aarch64-apple-darwin\n Build date 2026-05-11","target":"aarch64-apple-darwin","version":"0.1.0"}`. Critical provenance fields missing: (a) **`is_dirty`** — was the working tree clean at build time? Automation that pins on build provenance cannot tell if the binary was built from a clean commit or includes uncommitted changes; (b) **`branch`** — was this built from `main`, `dev/rust`, a release tag, or a feature branch? The `git_sha` alone doesn't reveal the integration point; (c) **`commit_date` / `commit_timestamp`** — only `build_date` (when the binary was compiled) is exposed; the commit itself might be days/weeks older if the build happened later. Reproducibility audits need both; (d) **`rustc_version`** — what Rust compiler version produced this binary? Critical for security advisories (e.g., known regressions in specific rustc versions); (e) **`git_sha` truncated to 7 chars** ("b98b9a7" instead of full "b98b9a71..."): 7-char shas have known collision rates in large repos and prevent unambiguous git rev-parse round-trip. **Sibling: `executable_path` leaks build-host path.** The `executable_path` field returns `/tmp/claw-dog-0530/rust/target/release/claw` — the directory where the binary was compiled, embedded into the binary metadata. For a binary copied/installed/symlinked to a different location, this field still reports the build path, not the actual invocation path. Either the field should reflect the runtime path via `std::env::current_exe()` at runtime (not compile-time), or it should be dropped to avoid leaking compile-host filesystem layout. **Sibling: prose `message` field duplicates structured data.** The `message` field still contains the entire text-mode prose version block (`"Claw Code\n Version 0.1.0\n Git SHA b98b9a7\n..."`) — every field present as structured JSON (`version`, `git_sha`, `target`, `build_date`) is also embedded in the prose. Same issue as #391 (`version json includes prose message field`) which was closed as "fixed" — the prose remains. **Required fix shape:** (a) add `is_dirty:bool`, `branch:string|null`, `commit_date:string` (ISO-8601), `commit_timestamp:int` (Unix epoch), `rustc_version:string` to the JSON envelope; (b) preserve full 40-char `git_sha` and add `git_sha_short:string` as a derived field if 7-char form is needed for UX; (c) `executable_path` should be `std::env::current_exe()` at runtime, not the compile-time path; (d) drop the prose `message` field from JSON or rename it `human_readable:string` and make it explicitly secondary to the structured fields; (e) re-verify #391 closure — the prose `message` is still present, the fix didn't fully land. **Why this matters:** version surface is the canonical provenance probe for security audits, build reproducibility, and bug-report metadata. Missing `is_dirty` means automated triage cannot distinguish "issue against a clean main commit" from "issue against a developer's uncommitted hack". Truncated `git_sha` blocks unambiguous git lookup. Leaked `executable_path` exposes build-host layout. Cross-references #391 (version prose duplication — apparently not fully fixed), #334 (version json omits build_date — fixed, but partial scope), #100 (commit identity audit). Source: Jobdori live dogfood, `8cf628a5`, 2026-05-11.
|
||||
|
||||
8
USAGE.md
8
USAGE.md
@@ -54,23 +54,23 @@ cd rust
|
||||
|
||||
### Initialize a repository
|
||||
|
||||
Set up a new repository with `.claw` config, `.claw.json`, `.gitignore` entries, and a `CLAUDE.md` guidance file:
|
||||
Set up a new repository with `.claw/settings.json`, `.claw.json`, `.gitignore` entries, and a `CLAUDE.md` guidance file:
|
||||
|
||||
```bash
|
||||
cd /path/to/your/repo
|
||||
./target/debug/claw init
|
||||
```
|
||||
|
||||
Text mode (human-readable) shows artifact creation summary with project path and next steps. Idempotent — running multiple times in the same repo marks already-created files as "skipped".
|
||||
Text mode (human-readable) shows artifact creation summary with project path and next steps. Idempotent — running multiple times in the same repo marks already-created files as "skipped", reports `.claw/` as "partial" when missing sub-files are materialized, and keeps `.claw/sessions/` deferred until the first successful session save.
|
||||
|
||||
JSON mode for scripting:
|
||||
```bash
|
||||
./target/debug/claw init --output-format json
|
||||
```
|
||||
|
||||
Returns structured output with `project_path`, `created[]`, `updated[]`, `skipped[]` arrays (one per artifact), and `artifacts[]` carrying each file's `name` and machine-stable `status` tag. The legacy `message` field preserves backward compatibility.
|
||||
Returns structured output with `project_path`, `created[]`, `updated[]`, `partial[]`, `deferred[]`, and `skipped[]` arrays (one per artifact status), and `artifacts[]` carrying each file's `name` and machine-stable `status` tag. The legacy `message` field preserves backward compatibility.
|
||||
|
||||
**Why structured fields matter:** Claws can detect per-artifact state (`created` vs `updated` vs `skipped`) without substring-matching human prose. Use the `created[]`, `updated[]`, and `skipped[]` arrays for conditional follow-up logic (e.g., only commit if files were actually created, not just updated).
|
||||
**Why structured fields matter:** Claws can detect per-artifact state (`created`, `updated`, `partial`, `deferred`, or `skipped`) without substring-matching human prose. Use the status arrays for conditional follow-up logic (e.g., only commit if files were actually created, not just updated).
|
||||
|
||||
### Interactive REPL
|
||||
|
||||
|
||||
@@ -4,7 +4,14 @@ use std::path::{Path, PathBuf};
|
||||
const STARTER_CLAW_JSON: &str = concat!(
|
||||
"{\n",
|
||||
" \"permissions\": {\n",
|
||||
" \"defaultMode\": \"dontAsk\"\n",
|
||||
" \"defaultMode\": \"acceptEdits\"\n",
|
||||
" }\n",
|
||||
"}\n",
|
||||
);
|
||||
const STARTER_SETTINGS_JSON: &str = concat!(
|
||||
"{\n",
|
||||
" \"permissions\": {\n",
|
||||
" \"defaultMode\": \"acceptEdits\"\n",
|
||||
" }\n",
|
||||
"}\n",
|
||||
);
|
||||
@@ -15,6 +22,8 @@ const GITIGNORE_ENTRIES: [&str; 3] = [".claw/settings.local.json", ".claw/sessio
|
||||
pub(crate) enum InitStatus {
|
||||
Created,
|
||||
Updated,
|
||||
Partial,
|
||||
Deferred,
|
||||
Skipped,
|
||||
}
|
||||
|
||||
@@ -24,6 +33,8 @@ impl InitStatus {
|
||||
match self {
|
||||
Self::Created => "created",
|
||||
Self::Updated => "updated",
|
||||
Self::Partial => "partial (created missing sub-files)",
|
||||
Self::Deferred => "deferred (created on first session save)",
|
||||
Self::Skipped => "skipped (already exists)",
|
||||
}
|
||||
}
|
||||
@@ -36,6 +47,8 @@ impl InitStatus {
|
||||
match self {
|
||||
Self::Created => "created",
|
||||
Self::Updated => "updated",
|
||||
Self::Partial => "partial",
|
||||
Self::Deferred => "deferred",
|
||||
Self::Skipped => "skipped",
|
||||
}
|
||||
}
|
||||
@@ -123,9 +136,30 @@ pub(crate) fn initialize_repo(cwd: &Path) -> Result<InitReport, Box<dyn std::err
|
||||
let mut artifacts = Vec::new();
|
||||
|
||||
let claw_dir = cwd.join(".claw");
|
||||
let claw_dir_status = ensure_dir(&claw_dir)?;
|
||||
let settings_json = claw_dir.join("settings.json");
|
||||
let settings_status = write_file_if_missing(&settings_json, STARTER_SETTINGS_JSON)?;
|
||||
let claw_dir_status =
|
||||
if claw_dir_status == InitStatus::Skipped && settings_status == InitStatus::Created {
|
||||
InitStatus::Partial
|
||||
} else {
|
||||
claw_dir_status
|
||||
};
|
||||
artifacts.push(InitArtifact {
|
||||
name: ".claw/",
|
||||
status: ensure_dir(&claw_dir)?,
|
||||
status: claw_dir_status,
|
||||
});
|
||||
artifacts.push(InitArtifact {
|
||||
name: ".claw/settings.json",
|
||||
status: settings_status,
|
||||
});
|
||||
artifacts.push(InitArtifact {
|
||||
name: ".claw/sessions/",
|
||||
status: if claw_dir.join("sessions").is_dir() {
|
||||
InitStatus::Skipped
|
||||
} else {
|
||||
InitStatus::Deferred
|
||||
},
|
||||
});
|
||||
|
||||
let claw_json = cwd.join(".claw.json");
|
||||
@@ -414,11 +448,26 @@ mod tests {
|
||||
concat!(
|
||||
"{\n",
|
||||
" \"permissions\": {\n",
|
||||
" \"defaultMode\": \"dontAsk\"\n",
|
||||
" \"defaultMode\": \"acceptEdits\"\n",
|
||||
" }\n",
|
||||
"}\n",
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
fs::read_to_string(root.join(".claw").join("settings.json"))
|
||||
.expect("read project settings"),
|
||||
concat!(
|
||||
"{\n",
|
||||
" \"permissions\": {\n",
|
||||
" \"defaultMode\": \"acceptEdits\"\n",
|
||||
" }\n",
|
||||
"}\n",
|
||||
)
|
||||
);
|
||||
assert!(
|
||||
!root.join(".claw").join("sessions").exists(),
|
||||
"sessions directory should be deferred until first session save"
|
||||
);
|
||||
let gitignore = fs::read_to_string(root.join(".gitignore")).expect("read gitignore");
|
||||
assert!(gitignore.contains(".claw/settings.local.json"));
|
||||
assert!(gitignore.contains(".claw/sessions/"));
|
||||
@@ -436,14 +485,24 @@ mod tests {
|
||||
fs::create_dir_all(&root).expect("create root");
|
||||
fs::write(root.join("CLAUDE.md"), "custom guidance\n").expect("write existing claude md");
|
||||
fs::write(root.join(".gitignore"), ".claw/settings.local.json\n").expect("write gitignore");
|
||||
fs::create_dir_all(root.join(".claw")).expect("create existing .claw dir");
|
||||
|
||||
let first = initialize_repo(&root).expect("first init should succeed");
|
||||
assert!(first
|
||||
.render()
|
||||
.contains("CLAUDE.md skipped (already exists)"));
|
||||
assert_eq!(
|
||||
first.artifacts_with_status(InitStatus::Partial),
|
||||
vec![".claw/".to_string()],
|
||||
"existing .claw/ should report partial when init creates missing settings.json"
|
||||
);
|
||||
assert!(root.join(".claw").join("settings.json").is_file());
|
||||
|
||||
let second = initialize_repo(&root).expect("second init should succeed");
|
||||
let second_rendered = second.render();
|
||||
assert!(second_rendered.contains(".claw/"));
|
||||
assert!(second_rendered.contains(".claw/settings.json"));
|
||||
assert!(second_rendered.contains(".claw/sessions/"));
|
||||
assert!(second_rendered.contains(".claw.json"));
|
||||
assert!(second_rendered.contains("skipped (already exists)"));
|
||||
assert!(second_rendered.contains(".gitignore skipped (already exists)"));
|
||||
@@ -474,16 +533,22 @@ mod tests {
|
||||
created_names,
|
||||
vec![
|
||||
".claw/".to_string(),
|
||||
".claw/settings.json".to_string(),
|
||||
".claw.json".to_string(),
|
||||
".gitignore".to_string(),
|
||||
"CLAUDE.md".to_string(),
|
||||
],
|
||||
"fresh init should place all four artifacts in created[]"
|
||||
"fresh init should place created artifacts in created[]"
|
||||
);
|
||||
assert!(
|
||||
fresh.artifacts_with_status(InitStatus::Skipped).is_empty(),
|
||||
"fresh init should have no skipped artifacts"
|
||||
);
|
||||
assert_eq!(
|
||||
fresh.artifacts_with_status(InitStatus::Deferred),
|
||||
vec![".claw/sessions/".to_string()],
|
||||
"fresh init should report session storage as deferred"
|
||||
);
|
||||
|
||||
let second = initialize_repo(&root).expect("second init should succeed");
|
||||
let skipped_names = second.artifacts_with_status(InitStatus::Skipped);
|
||||
@@ -491,27 +556,38 @@ mod tests {
|
||||
skipped_names,
|
||||
vec![
|
||||
".claw/".to_string(),
|
||||
".claw/settings.json".to_string(),
|
||||
".claw.json".to_string(),
|
||||
".gitignore".to_string(),
|
||||
"CLAUDE.md".to_string(),
|
||||
],
|
||||
"idempotent init should place all four artifacts in skipped[]"
|
||||
"idempotent init should place existing artifacts in skipped[]"
|
||||
);
|
||||
assert!(
|
||||
second.artifacts_with_status(InitStatus::Created).is_empty(),
|
||||
"idempotent init should have no created artifacts"
|
||||
);
|
||||
assert_eq!(
|
||||
second.artifacts_with_status(InitStatus::Deferred),
|
||||
vec![".claw/sessions/".to_string()],
|
||||
"idempotent init should keep session storage deferred until first save"
|
||||
);
|
||||
|
||||
// artifact_json_entries() uses the machine-stable `json_tag()` which
|
||||
// never changes wording (unlike `label()` which says "skipped (already exists)").
|
||||
let entries = second.artifact_json_entries();
|
||||
assert_eq!(entries.len(), 4);
|
||||
assert_eq!(entries.len(), 6);
|
||||
for entry in &entries {
|
||||
let name = entry.get("name").and_then(|v| v.as_str()).unwrap();
|
||||
let status = entry.get("status").and_then(|v| v.as_str()).unwrap();
|
||||
assert_eq!(
|
||||
status, "skipped",
|
||||
"machine status tag should be the bare word 'skipped', not label()'s 'skipped (already exists)'"
|
||||
);
|
||||
if name == ".claw/sessions/" {
|
||||
assert_eq!(status, "deferred");
|
||||
} else {
|
||||
assert_eq!(
|
||||
status, "skipped",
|
||||
"machine status tag should be the bare word 'skipped', not label()'s 'skipped (already exists)'"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fs::remove_dir_all(root).expect("cleanup temp dir");
|
||||
|
||||
@@ -9011,8 +9011,8 @@ fn render_help_topic(topic: LocalHelpTopic) -> String {
|
||||
.to_string(),
|
||||
LocalHelpTopic::Init => "Init
|
||||
Usage claw init [--output-format <format>]
|
||||
Purpose create .claw/, .claw.json, .gitignore, and CLAUDE.md in the current project
|
||||
Output list of created vs. skipped files (idempotent: safe to re-run)
|
||||
Purpose create .claw/settings.json, .claw.json, .gitignore, and CLAUDE.md in the current project
|
||||
Output per-artifact created/updated/partial/deferred/skipped status (idempotent: safe to re-run)
|
||||
Formats text (default), json
|
||||
Related claw status · claw doctor"
|
||||
.to_string(),
|
||||
@@ -9774,10 +9774,12 @@ fn init_json_value(report: &crate::init::InitReport, message: &str) -> serde_jso
|
||||
// Derive top-level status: "ok" when all artifacts succeeded (created or
|
||||
// skipped = idempotent); no failure path exists today so always "ok".
|
||||
let status = "ok";
|
||||
// #783: already_initialized lets orchestrators detect the idempotent case
|
||||
// without checking created.len() == 0; hint gives a stable next-action pointer.
|
||||
// #783/#436: already_initialized lets orchestrators detect the idempotent
|
||||
// case without checking every status bucket; deferred session storage does
|
||||
// not make the workspace uninitialized because it is created on first save.
|
||||
let already_initialized = report.artifacts_with_status(InitStatus::Created).is_empty()
|
||||
&& report.artifacts_with_status(InitStatus::Updated).is_empty();
|
||||
&& report.artifacts_with_status(InitStatus::Updated).is_empty()
|
||||
&& report.artifacts_with_status(InitStatus::Partial).is_empty();
|
||||
let hint = if already_initialized {
|
||||
"Workspace already initialised. Run `claw doctor` to verify health, or edit CLAUDE.md to customise guidance."
|
||||
} else {
|
||||
@@ -9792,6 +9794,8 @@ fn init_json_value(report: &crate::init::InitReport, message: &str) -> serde_jso
|
||||
"created": report.artifacts_with_status(InitStatus::Created),
|
||||
"updated": report.artifacts_with_status(InitStatus::Updated),
|
||||
"skipped": report.artifacts_with_status(InitStatus::Skipped),
|
||||
"partial": report.artifacts_with_status(InitStatus::Partial),
|
||||
"deferred": report.artifacts_with_status(InitStatus::Deferred),
|
||||
"artifacts": report.artifact_json_entries(),
|
||||
"hint": hint,
|
||||
"next_step": crate::init::InitReport::NEXT_STEP,
|
||||
|
||||
@@ -5,7 +5,7 @@ use std::sync::atomic::{AtomicU64, Ordering};
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
use runtime::Session;
|
||||
use serde_json::Value;
|
||||
use serde_json::{json, Value};
|
||||
|
||||
static TEMP_COUNTER: AtomicU64 = AtomicU64::new(0);
|
||||
|
||||
@@ -3928,6 +3928,41 @@ fn init_json_envelope_has_hint_and_already_initialized_783() {
|
||||
hint.contains("CLAUDE.md") || hint.contains("doctor"),
|
||||
"fresh-init hint should mention CLAUDE.md or doctor, got: {hint:?}"
|
||||
);
|
||||
assert_eq!(
|
||||
parsed["created"],
|
||||
json!([
|
||||
".claw/",
|
||||
".claw/settings.json",
|
||||
".claw.json",
|
||||
".gitignore",
|
||||
"CLAUDE.md"
|
||||
]),
|
||||
"fresh init should materialize .claw/settings.json and safe .claw.json"
|
||||
);
|
||||
assert_eq!(
|
||||
parsed["deferred"],
|
||||
json!([".claw/sessions/"]),
|
||||
"session storage should be reported as deferred until first save"
|
||||
);
|
||||
assert_eq!(parsed["partial"], json!([]));
|
||||
let claw_json = fs::read_to_string(root.join(".claw.json")).expect("read .claw.json");
|
||||
assert!(
|
||||
claw_json.contains("\"defaultMode\": \"acceptEdits\""),
|
||||
"init must not scaffold dontAsk in .claw.json: {claw_json}"
|
||||
);
|
||||
assert!(
|
||||
!claw_json.contains("dontAsk"),
|
||||
"init must not scaffold unsafe dontAsk permission mode: {claw_json}"
|
||||
);
|
||||
let settings_json = root.join(".claw").join("settings.json");
|
||||
assert!(
|
||||
settings_json.is_file(),
|
||||
"init should template .claw/settings.json"
|
||||
);
|
||||
assert!(
|
||||
!root.join(".claw").join("sessions").exists(),
|
||||
"sessions directory should remain deferred until first save"
|
||||
);
|
||||
|
||||
// Idempotent re-init — already_initialized should be true
|
||||
let output2 = run_claw(&root, &["--output-format", "json", "init"], &[]);
|
||||
@@ -3954,6 +3989,43 @@ fn init_json_envelope_has_hint_and_already_initialized_783() {
|
||||
hint2.contains("already") || hint2.contains("doctor"),
|
||||
"re-init hint should acknowledge workspace exists, got: {hint2:?}"
|
||||
);
|
||||
|
||||
let existing_claw_root = unique_temp_dir("init-existing-claw-436");
|
||||
fs::create_dir_all(existing_claw_root.join(".claw")).expect("existing .claw dir");
|
||||
let partial_output = run_claw(
|
||||
&existing_claw_root,
|
||||
&["--output-format", "json", "init"],
|
||||
&[],
|
||||
);
|
||||
assert!(
|
||||
partial_output.status.success(),
|
||||
"init with existing .claw should succeed"
|
||||
);
|
||||
let partial_stdout = String::from_utf8_lossy(&partial_output.stdout);
|
||||
let partial: serde_json::Value =
|
||||
serde_json::from_str(partial_stdout.trim()).expect("partial init should emit valid JSON");
|
||||
assert_eq!(
|
||||
partial["partial"],
|
||||
json!([".claw/"]),
|
||||
"existing .claw with newly-created settings should report partial .claw/"
|
||||
);
|
||||
assert_eq!(
|
||||
partial["created"],
|
||||
json!([
|
||||
".claw/settings.json",
|
||||
".claw.json",
|
||||
".gitignore",
|
||||
"CLAUDE.md"
|
||||
]),
|
||||
"init should still create missing sub-files when .claw already exists"
|
||||
);
|
||||
assert!(
|
||||
existing_claw_root
|
||||
.join(".claw")
|
||||
.join("settings.json")
|
||||
.is_file(),
|
||||
"existing .claw must receive missing settings template"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user