Skip to content

fix(workspace): migrate build_log.md from repo root to $SUTANDO_WORKSPACE - #859

Merged
sonichi merged 2 commits into
mainfrom
fix/build-log-workspace-path
May 18, 2026
Merged

sonichi merged 2 commits into
mainfrom
fix/build-log-workspace-path

Conversation

@sonichi

@sonichi sonichi commented May 18, 2026

Copy link
Copy Markdown
Owner

Summary

  • build_log.md is per-user mutable runtime state — belongs in $SUTANDO_WORKSPACE per the contract in CLAUDE.md, not the repo root.
  • Pre-fix state was silent split-brain: dashboard.py + health-check.py already read from WORKSPACE_DIR/build_log.md (missing on Chi's machine), while voice-context.ts + sync-memory.sh + init.sh still wrote to repo root.
  • skills/self-diagnose/scripts/gather*.sh used build_log.md as a marker file to LOCATE the repo, so a simple mv would have broken self-diagnose. Marker swapped to CLAUDE.md (stable, identity-bearing, unambiguous).

Changes

File Change
src/voice-context.ts:43 Read build_log from resolveWorkspace()/build_log.md (was REPO_DIR)
src/init.sh:66 Stop seeding build_log.md at repo root (workspace_default.py owns seeding now)
skills/self-diagnose/scripts/gather.sh Repo-locator marker build_log.md → CLAUDE.md
skills/self-diagnose/scripts/gather-remote.sh Same marker swap
src/workspace_default.py Add _migrate_inrepo_build_log() — symmetric to _migrate_inrepo_notes; sentinel-gated; runs from resolve_workspace() on both env-set + env-unset paths
tests/init-script.test.ts Flip build_log assertions: should NOT seed at repo root
tests/workspace-default.test.py +6 tests for new migrator (migrate / skip-equal / skip-missing / no-clobber / sentinel-idempotent / e2e)

Out of scope (intentional)

  • ~/.sutando/memory-sync/scripts/sync-memory.sh MACHINE_FILES path — private repo, separate commit.
  • Wider init.sh cleanup (logs/, state/, tasks/, results/ are also seeded at REPO and should arguably move to workspace) — separate work to keep this PR small + reviewable.
  • Doc refs in *.md files — comments only, no behavior; will pick up on next pass.

Test plan

  • python3 tests/workspace-default.test.py — 25/25 pass
  • npx tsx --test tests/init-script.test.ts — 18/18 pass
  • npx tsc --noEmit clean
  • On merge, resolve_workspace() auto-migrates Chi's local repo's build_log.md to ~/.sutando/workspace/build_log.md on next Python service start
  • Confirm Sutando.app dashboard + health-check pick up the build_log at new path

🤖 Generated with Claude Code

…PACE

build_log.md is per-user mutable runtime state and belongs in the workspace
per the contract in CLAUDE.md. Pre-fix state was split-brain:
- dashboard.py + health-check.py already read from WORKSPACE_DIR/build_log.md
- voice-context.ts + sync-memory.sh wrote to REPO/build_log.md
- self-diagnose's gather.sh used build_log.md as a marker to LOCATE the repo

Changes:
- src/voice-context.ts:43 → reads from resolveWorkspace()/build_log.md
- src/init.sh:66 → stop seeding at repo (workspace_default.py owns seeding)
- skills/self-diagnose/scripts/gather*.sh → swap repo-marker from
  build_log.md to CLAUDE.md (stable, identity-bearing)
- src/workspace_default.py → add _migrate_inrepo_build_log() symmetric to
  _migrate_inrepo_notes(); fires from resolve_workspace() in both env-set
  and env-unset paths; sentinel-gated for O(1) re-entry

Separate follow-up (not in this PR):
- ~/.sutando/memory-sync/scripts/sync-memory.sh MACHINE_FILES path (private
  repo, separate commit)

Tests: 18 init-script tests + 25 workspace-default tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

@sonichi sonichi left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

LGTM — comprehensive, symmetric, well-scoped. This is the systemic version of the manual mv+symlink I did on Mini earlier today.

Verified each change:

  1. skills/self-diagnose/scripts/gather*.sh — marker swap build_log.md → CLAUDE.md is the right secondary fix. CLAUDE.md is identity-bearing + stable + always-present in a sutando checkout; build_log.md was a fragile choice that broke the moment it moved. Comment cite makes the rationale clear.

  2. src/init.sh — dropping the build_log.md seeding at repo root is correct. Comment explicitly cites the workspace contract + names the consumers (workspace_default.py + dashboard + health-check) so the next reader sees why it's not there.

  3. src/voice-context.ts — voice-context build-log read migrated from REPO_DIR to WORKSPACE_DIR. This was one of the two unmigrated TS readers (Mini and dashboard already on WORKSPACE_DIR via different paths). Closes the divergence completely.

  4. src/workspace_default.py:_migrate_inrepo_build_log — clean implementation:

    • Sentinel-gated (.build_log-migrated) for O(1) re-entry on subsequent calls
    • Non-destructive on collision (skips if workspace target already exists)
    • Fires in both env-set and env-unset paths — right call, the migration needs to happen regardless of where workspace is resolved
    • Same shape as _migrate_inrepo_notes — easy mental model for the next contributor
  5. Tests updated to the new contract — init-script test now asserts NOT-created at repo root rather than testing the old seeding behavior. Test owns the contract change explicitly.

One non-blocking nit: the new _migrate_inrepo_build_log should ideally bump _migrate_from_legacy's _LEGACY_DIRS recognition. If a legacy install also has build_log.md at the repo root AND env is unset AND new default doesn't yet exist, the _migrate_from_legacy runs first → creates the new default with tasks/results/state/notes → then _migrate_inrepo_build_log runs against the NOW-existing target → skips (collision). End result is still right (build_log stays where it is for a moment), but the file gets stranded. Worth a follow-up test case OR an ordering tweak in resolve_workspace. Not blocking — current behavior is correct for the common path.

CI green (SUCCESS on the visible check). MERGEABLE. Ship — Chi's offline and asked Mini to drive autonomously.

@sonichi
sonichi merged commit 69cc964 into main May 18, 2026
2 checks passed
@sonichi
sonichi deleted the fix/build-log-workspace-path branch May 18, 2026 21:44
sonichi added a commit that referenced this pull request May 18, 2026
… build_log) (#861)

Adds a test verifying the migration ordering Mini flagged as non-blocking in
#859 review:
  - Legacy install (repo has tasks/results/state with content)
  - $SUTANDO_WORKSPACE unset
  - New default doesn't exist
  - build_log.md at repo root

Expected: _migrate_from_legacy runs first (moves dirs), then
_migrate_inrepo_build_log runs and migrates the file. Mini's concern was
that step 2 might skip due to "collision" with the newly-created target dir.
This test pins the actual behavior — collision check is against the FILE
workspace/build_log.md not the workspace DIR, so migration proceeds cleanly.

Verified manually before adding the test (Mini's nit is non-issue), but
worth permanent coverage so the door stays sealed.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
sonichi added a commit that referenced this pull request May 20, 2026
Closes the workspace-vs-repo path mismatch that surfaced as false-positive
health-check warnings (voice-agent.log "not found" at /workspace/logs while
the actual file was written to /repo/logs by startup.sh).

Behavior change:
- Add $WORKSPACE resolution at top of service-start block, mirroring
  src/workspace_default.py / .ts: env override `$SUTANDO_WORKSPACE`,
  fallback to `~/.sutando/workspace/`.
- All 8 service log redirections (voice-agent, web-client, dashboard,
  agent-api, screen-capture, telegram-bridge, discord-bridge,
  slack-bridge) now write to `"$LOGS_DIR/$name.log"` instead of the
  CWD-relative `logs/$name.log`.
- mkdir -p $WORKSPACE/{logs,tasks,results,data} so the redirections
  don't fail on a fresh install.
- Diagnostic-failure echo also points at $LOGS_DIR.
- Legacy repo-root `tasks/ results/ data/` mkdir kept for now —
  back-compat with any script that still resolves CWD-relative paths.

Why this matters: health-check.py + dashboard already look at
$WORKSPACE/logs (per PR #821/#859 workspace-design contract). Without
this change they ran on a stale subset of services every cycle and
emitted false "log not found" warnings that masked real issues.

No code changes inside the services — they emit to stdout/stderr and
startup.sh redirects, so this is a one-file shell-only diff. The
services never opened log files directly, which is why this didn't
need to ride along with #821's TS migration.

Tested: smoke-checked resolution snippet — `$LOGS_DIR` resolves to
`/Users/wangchi/.sutando/workspace/logs` (existing dir with current
logs from prior bridges). `bash -n` clean.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
sonichi added a commit that referenced this pull request May 20, 2026
…n first run

Closes the gap Susan flagged in PR #913 review: after #911 (workspace-aware
service logs), every install pre-dating that PR keeps two copies of
runtime state — stale repo-root `logs/`, `state/`, `pending-questions.md`,
`core-status.json` alongside the new workspace versions. git status
pollution + future debugging confusion.

This adds a one-time migration function modeled on
`workspace_default.py::_migrate_from_legacy` (PR #762):

- Fires only when `\$WORKSPACE/.legacy-migrated-911` sentinel is absent
- Scans `\$REPO/` for evidence (any of logs/state/tasks/results/notes/data
  non-empty); skips entirely on fresh installs
- Moves each runtime dir + file `\$REPO/X` → `\$WORKSPACE/X` ONLY when
  the workspace destination doesn't already exist (non-destructive on
  collision)
- Writes sentinel after the sweep; second run sees it and bails (zero
  log noise on every subsequent startup)
- Stderr line per moved item so users can audit what happened

Files moved: dirs `logs/`, `state/`, `tasks/`, `results/`, `notes/`,
`data/`; files `pending-questions.md`, `core-status.json`,
`contextual-chips.json`, `voice-state.json`, `build_log.md`. Matches
the set #911 + earlier #821/#859 contract migrated.

Smoke-tested locally with a synthetic test repo + workspace:
- First run: migrated 4 fake stale files (logs/, state/, notes/,
  core-status.json) from /tmp/sutando-migration-test → /tmp/test-workspace
  with one stderr line each. Sentinel written.
- Second run: silent (sentinel present, bails immediately).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
sonichi added a commit that referenced this pull request May 20, 2026
…oot (#913)

* feat(init.sh): create runtime dirs/files under \$WORKSPACE, not repo root

Closes the breadcrumb loop Mini flagged in #911 review: startup.sh now
redirects service logs to \$WORKSPACE/logs/, but init.sh was still
mkdir-ing repo-root logs/state/tasks/results/notes/data/ + writing
pending-questions.md, contextual-chips.json, core-status.json,
voice-state.json into the repo. So even with #911 merged, every
startup.sh run re-created stale repo-root breadcrumbs.

This PR makes init.sh workspace-aware:

- Resolve \$WORKSPACE at top (same shape as startup.sh, workspace_default.py)
- create_file_if_missing / create_dir_if_missing now write under \$WORKSPACE
- copy_if_missing kept repo-rooted — it's used today only for
  skills/schedule-crons/crons.json which lives in the repo by design

Smoke-tested: `bash src/init.sh --auto` creates the previously-missing
\$WORKSPACE/data/ + \$WORKSPACE/pending-questions.md cleanly. Existing
workspace files left alone (idempotent).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(init.sh): migrate stale repo-root runtime state to \$WORKSPACE on first run

Closes the gap Susan flagged in PR #913 review: after #911 (workspace-aware
service logs), every install pre-dating that PR keeps two copies of
runtime state — stale repo-root `logs/`, `state/`, `pending-questions.md`,
`core-status.json` alongside the new workspace versions. git status
pollution + future debugging confusion.

This adds a one-time migration function modeled on
`workspace_default.py::_migrate_from_legacy` (PR #762):

- Fires only when `\$WORKSPACE/.legacy-migrated-911` sentinel is absent
- Scans `\$REPO/` for evidence (any of logs/state/tasks/results/notes/data
  non-empty); skips entirely on fresh installs
- Moves each runtime dir + file `\$REPO/X` → `\$WORKSPACE/X` ONLY when
  the workspace destination doesn't already exist (non-destructive on
  collision)
- Writes sentinel after the sweep; second run sees it and bails (zero
  log noise on every subsequent startup)
- Stderr line per moved item so users can audit what happened

Files moved: dirs `logs/`, `state/`, `tasks/`, `results/`, `notes/`,
`data/`; files `pending-questions.md`, `core-status.json`,
`contextual-chips.json`, `voice-state.json`, `build_log.md`. Matches
the set #911 + earlier #821/#859 contract migrated.

Smoke-tested locally with a synthetic test repo + workspace:
- First run: migrated 4 fake stale files (logs/, state/, notes/,
  core-status.json) from /tmp/sutando-migration-test → /tmp/test-workspace
  with one stderr line each. Sentinel written.
- Second run: silent (sentinel present, bails immediately).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
liususan091219 pushed a commit to liususan091219/sutando-fork that referenced this pull request May 28, 2026
…PACE (sonichi#859)

build_log.md is per-user mutable runtime state and belongs in the workspace
per the contract in CLAUDE.md. Pre-fix state was split-brain:
- dashboard.py + health-check.py already read from WORKSPACE_DIR/build_log.md
- voice-context.ts + sync-memory.sh wrote to REPO/build_log.md
- self-diagnose's gather.sh used build_log.md as a marker to LOCATE the repo

Changes:
- src/voice-context.ts:43 → reads from resolveWorkspace()/build_log.md
- src/init.sh:66 → stop seeding at repo (workspace_default.py owns seeding)
- skills/self-diagnose/scripts/gather*.sh → swap repo-marker from
  build_log.md to CLAUDE.md (stable, identity-bearing)
- src/workspace_default.py → add _migrate_inrepo_build_log() symmetric to
  _migrate_inrepo_notes(); fires from resolve_workspace() in both env-set
  and env-unset paths; sentinel-gated for O(1) re-entry

Separate follow-up (not in this PR):
- ~/.sutando/memory-sync/scripts/sync-memory.sh MACHINE_FILES path (private
  repo, separate commit)

Tests: 18 init-script tests + 25 workspace-default tests pass.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
liususan091219 pushed a commit to liususan091219/sutando-fork that referenced this pull request May 28, 2026
…unset + build_log) (sonichi#861)

Adds a test verifying the migration ordering Mini flagged as non-blocking in
sonichi#859 review:
  - Legacy install (repo has tasks/results/state with content)
  - $SUTANDO_WORKSPACE unset
  - New default doesn't exist
  - build_log.md at repo root

Expected: _migrate_from_legacy runs first (moves dirs), then
_migrate_inrepo_build_log runs and migrates the file. Mini's concern was
that step 2 might skip due to "collision" with the newly-created target dir.
This test pins the actual behavior — collision check is against the FILE
workspace/build_log.md not the workspace DIR, so migration proceeds cleanly.

Verified manually before adding the test (Mini's nit is non-issue), but
worth permanent coverage so the door stays sealed.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
liususan091219 pushed a commit to liususan091219/sutando-fork that referenced this pull request May 28, 2026
Closes the workspace-vs-repo path mismatch that surfaced as false-positive
health-check warnings (voice-agent.log "not found" at /workspace/logs while
the actual file was written to /repo/logs by startup.sh).

Behavior change:
- Add $WORKSPACE resolution at top of service-start block, mirroring
  src/workspace_default.py / .ts: env override `$SUTANDO_WORKSPACE`,
  fallback to `~/.sutando/workspace/`.
- All 8 service log redirections (voice-agent, web-client, dashboard,
  agent-api, screen-capture, telegram-bridge, discord-bridge,
  slack-bridge) now write to `"$LOGS_DIR/$name.log"` instead of the
  CWD-relative `logs/$name.log`.
- mkdir -p $WORKSPACE/{logs,tasks,results,data} so the redirections
  don't fail on a fresh install.
- Diagnostic-failure echo also points at $LOGS_DIR.
- Legacy repo-root `tasks/ results/ data/` mkdir kept for now —
  back-compat with any script that still resolves CWD-relative paths.

Why this matters: health-check.py + dashboard already look at
$WORKSPACE/logs (per PR sonichi#821/sonichi#859 workspace-design contract). Without
this change they ran on a stale subset of services every cycle and
emitted false "log not found" warnings that masked real issues.

No code changes inside the services — they emit to stdout/stderr and
startup.sh redirects, so this is a one-file shell-only diff. The
services never opened log files directly, which is why this didn't
need to ride along with sonichi#821's TS migration.

Tested: smoke-checked resolution snippet — `$LOGS_DIR` resolves to
`/Users/wangchi/.sutando/workspace/logs` (existing dir with current
logs from prior bridges). `bash -n` clean.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
liususan091219 pushed a commit to liususan091219/sutando-fork that referenced this pull request May 28, 2026
…oot (sonichi#913)

* feat(init.sh): create runtime dirs/files under \$WORKSPACE, not repo root

Closes the breadcrumb loop Mini flagged in sonichi#911 review: startup.sh now
redirects service logs to \$WORKSPACE/logs/, but init.sh was still
mkdir-ing repo-root logs/state/tasks/results/notes/data/ + writing
pending-questions.md, contextual-chips.json, core-status.json,
voice-state.json into the repo. So even with sonichi#911 merged, every
startup.sh run re-created stale repo-root breadcrumbs.

This PR makes init.sh workspace-aware:

- Resolve \$WORKSPACE at top (same shape as startup.sh, workspace_default.py)
- create_file_if_missing / create_dir_if_missing now write under \$WORKSPACE
- copy_if_missing kept repo-rooted — it's used today only for
  skills/schedule-crons/crons.json which lives in the repo by design

Smoke-tested: `bash src/init.sh --auto` creates the previously-missing
\$WORKSPACE/data/ + \$WORKSPACE/pending-questions.md cleanly. Existing
workspace files left alone (idempotent).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(init.sh): migrate stale repo-root runtime state to \$WORKSPACE on first run

Closes the gap Susan flagged in PR sonichi#913 review: after sonichi#911 (workspace-aware
service logs), every install pre-dating that PR keeps two copies of
runtime state — stale repo-root `logs/`, `state/`, `pending-questions.md`,
`core-status.json` alongside the new workspace versions. git status
pollution + future debugging confusion.

This adds a one-time migration function modeled on
`workspace_default.py::_migrate_from_legacy` (PR sonichi#762):

- Fires only when `\$WORKSPACE/.legacy-migrated-911` sentinel is absent
- Scans `\$REPO/` for evidence (any of logs/state/tasks/results/notes/data
  non-empty); skips entirely on fresh installs
- Moves each runtime dir + file `\$REPO/X` → `\$WORKSPACE/X` ONLY when
  the workspace destination doesn't already exist (non-destructive on
  collision)
- Writes sentinel after the sweep; second run sees it and bails (zero
  log noise on every subsequent startup)
- Stderr line per moved item so users can audit what happened

Files moved: dirs `logs/`, `state/`, `tasks/`, `results/`, `notes/`,
`data/`; files `pending-questions.md`, `core-status.json`,
`contextual-chips.json`, `voice-state.json`, `build_log.md`. Matches
the set sonichi#911 + earlier sonichi#821/sonichi#859 contract migrated.

Smoke-tested locally with a synthetic test repo + workspace:
- First run: migrated 4 fake stale files (logs/, state/, notes/,
  core-status.json) from /tmp/sutando-migration-test → /tmp/test-workspace
  with one stderr line each. Sentinel written.
- Second run: silent (sentinel present, bails immediately).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
sonichi added a commit that referenced this pull request May 29, 2026
…solvers (closes #863)

Guards against re-introducing the workspace-vs-repo split-brain bug class
fixed in PR family #815-#859. Any future `process.cwd()` in TS code or
`Path.cwd()`/`os.getcwd()` in Python outside the resolver modules now fails CI.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
liususan091219 added a commit that referenced this pull request May 30, 2026
…solvers (closes #863) (#1322)

Guards against re-introducing the workspace-vs-repo split-brain bug class
fixed in PR family #815-#859. Any future `process.cwd()` in TS code or
`Path.cwd()`/`os.getcwd()` in Python outside the resolver modules now fails CI.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Susan Xueqing Liu <xliu127@stevens.edu>
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