{"id":481779,"date":"2026-05-31T13:36:30","date_gmt":"2026-05-31T13:36:30","guid":{"rendered":"https:\/\/savepearlharbor.com\/?p=481779"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=481779","title":{"rendered":"AI Workspace System: one local workspace for Codex, Claude Code, and GitHub"},"content":{"rendered":"<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>I ran into a very practical problem after doing a lot of local work with AI agents. I had Codex projects, Claude Code projects, regular repositories edited with agents, drafts, pipelines, instructions, skills, artifacts, and several machines. At some point it became hard to tell where the current version of a project lived, which files were safe to push, where agent instructions belonged, and where source code had already been mixed with logs and intermediate output.<\/p>\n<p>That is why I built <code>AI Workspace System<\/code>: a small set of shell scripts, conventions, and Markdown documentation that makes local AI-agent work predictable. It is not an IDE and not an agent orchestrator. It is a thin infrastructure layer around Git, GitHub, Codex, and Claude Code.<\/p>\n<p>The core idea is simple: all projects should be visible from one list, instructions should follow one structure, sync should be safe by default, and machine-specific details should not live in the repository.<\/p>\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/dc4\/b0f\/b16\/dc4b0fb1615b97be5ac492c82d7e6bf2.png\" width=\"1459\" height=\"877\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/dc4\/b0f\/b16\/dc4b0fb1615b97be5ac492c82d7e6bf2.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/dc4\/b0f\/b16\/dc4b0fb1615b97be5ac492c82d7e6bf2.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/figure>\n<h3>What the project does<\/h3>\n<p>First, it provides one launcher for Codex and Claude Code. <code>ai-launch codex<\/code> or <code>ai-launch claude<\/code> shows a project list. The first items are always <code>New project<\/code> and <code>No project<\/code>: create a project or start an agent without project context.<\/p>\n<p>Second, it defines one instruction model. <a href=\"http:\/\/AGENTS.md\" rel=\"noopener noreferrer nofollow\"><code>AGENTS.md<\/code><\/a> is the canonical shared instruction file. <a href=\"http:\/\/CLAUDE.md\" rel=\"noopener noreferrer nofollow\"><code>CLAUDE.md<\/code><\/a> imports it with <code>@<\/code><a href=\"http:\/\/AGENTS.md\" rel=\"noopener noreferrer nofollow\"><code>AGENTS.md<\/code><\/a> and contains only Claude Code-specific notes.<\/p>\n<p>Third, it includes a conservative <code>workspace-sync<\/code>. It fetches remotes, pulls only clean worktrees, pushes already committed changes, and skips dirty repositories. In scheduled mode it never commits automatically.<\/p>\n<p>Fourth, it separates owned repositories from upstream repositories. If <code>origin<\/code> is external, scheduled sync pushes only to a separate <code>backup<\/code> remote. Pushing to the real upstream must be an explicit action.<\/p>\n<p>Fifth, it separates target repos from delivery workbenches. A target repo contains reviewed code and public material. A workbench contains prompts, research, drafts, pipelines, and local state. The relationship is described in <code>workspace.yaml<\/code>.<\/p>\n<h3>How to use it<\/h3>\n<p>Minimal setup:<\/p>\n<pre><code class=\"bash\">mkdir -p ~\/projectsgit clone &lt;ai-workspace-system-repo-url&gt; ~\/projects\/ai-workspace-system~\/projects\/ai-workspace-system\/bin\/install-localworkspace-configure<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:87px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p><code>install-local<\/code> creates symlinks in <code>~\/.local\/bin<\/code>, and <code>workspace-configure<\/code> writes local configuration:<\/p>\n<pre><code>~\/.config\/ai-workspace\/config<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>The config contains everything that belongs to the current machine:<\/p>\n<pre><code>PROJECTS_HOME=\"$HOME\/projects\"PROJECT_ROOTS=\"$HOME\/projects\"SYNC_ROOTS=\"$HOME\/projects\"GITHUB_ORG=\"\"BACKUP_REMOTE=\"backup\"CODEX_BIN=\"$HOME\/.local\/bin\/codex\"CLAUDE_BIN=\"$HOME\/.local\/bin\/claude\"SECRET_SCAN_CMD=\"\"<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p><code>PROJECT_ROOTS<\/code> controls the picker. <code>SYNC_ROOTS<\/code> controls scheduled sync. A project can be visible to agents without being part of automatic sync.<\/p>\n<p>After setup:<\/p>\n<pre><code class=\"bash\">ai-launch codexai-launch claude<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>The agent does not start in a random directory. The launcher first shows <code>New project<\/code>, <code>No project<\/code>, then projects from <code>PROJECT_ROOTS<\/code>. <code>New project<\/code> calls <code>workspace-new-project<\/code>: it creates a directory under <code>PROJECTS_HOME<\/code>, writes baseline <a href=\"http:\/\/README.md\" rel=\"noopener noreferrer nofollow\"><code>README.md<\/code><\/a>, <a href=\"http:\/\/AGENTS.md\" rel=\"noopener noreferrer nofollow\"><code>AGENTS.md<\/code><\/a>, <a href=\"http:\/\/CLAUDE.md\" rel=\"noopener noreferrer nofollow\"><code>CLAUDE.md<\/code><\/a>, <code>workspace.yaml<\/code>, <code>.gitignore<\/code>, and <code>.env.example<\/code>, then runs <code>git init -b main<\/code> and creates an initial commit. <code>No project<\/code> is for one-off questions.<\/p>\n<p>Scheduled sync uses a user-level systemd timer at 13:00, 19:00, and 23:50:<\/p>\n<pre><code class=\"bash\">systemctl --user daemon-reloadsystemctl --user enable --now ai-workspace-sync.timer<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>Before enabling it:<\/p>\n<pre><code class=\"bash\">workspace-sync --dry-run<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>This shows which repositories will be discovered.<\/p>\n<h3>Prompt for collecting local projects<\/h3>\n<p>Another key part is <code>prompts\/<\/code><a href=\"http:\/\/discover-and-normalize-local-projects.md\" rel=\"noopener noreferrer nofollow\"><code>discover-and-normalize-local-projects.md<\/code><\/a>. Give it to Codex or Claude Code on a machine where projects are scattered across the home directory. It asks the agent to inventory directories, classify candidates, propose a move plan, move safe projects, add <a href=\"http:\/\/README.md\" rel=\"noopener noreferrer nofollow\"><code>README.md<\/code><\/a>, <a href=\"http:\/\/AGENTS.md\" rel=\"noopener noreferrer nofollow\"><code>AGENTS.md<\/code><\/a>, <a href=\"http:\/\/CLAUDE.md\" rel=\"noopener noreferrer nofollow\"><code>CLAUDE.md<\/code><\/a>, <code>.gitignore<\/code>, and <code>workspace.yaml<\/code>, and initialize Git only where the directory is a real project rather than an archive, cache, or runtime state.<\/p>\n<p>Short version:<\/p>\n<pre><code>Find likely local projects, classify them, move approved ones into ~\/projects,add baseline AI\/project docs, and initialize Git repos where appropriate.Do not move secrets, agent sessions, logs, caches, auth files, or shell history.Preserve existing remotes. Ask before committing dirty repos or changing upstream policy.<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<h3>Global agent instructions<\/h3>\n<p>Global agent files are never overwritten blindly. <code>~\/.codex\/<\/code><a href=\"http:\/\/AGENTS.md\" rel=\"noopener noreferrer nofollow\"><code>AGENTS.md<\/code><\/a>, <code>~\/.claude\/<\/code><a href=\"http:\/\/CLAUDE.md\" rel=\"noopener noreferrer nofollow\"><code>CLAUDE.md<\/code><\/a>, and <code>~\/.claude\/rules\/<\/code> are treated as machine-level preferences. If a file is missing, the agent may propose creating it. If it already exists, the agent should preserve its content and propose a small additive change. Runtime state, auth, sessions, logs, sqlite files, and shell history must not be committed.<\/p>\n<h3>Commands<\/h3>\n<p><code>workspace-configure<\/code> writes local config: GitHub org or user, agent binary paths, project roots, and secret scanner.<\/p>\n<p><code>ai-launch codex<\/code> starts Codex through the project picker.<\/p>\n<p><code>ai-launch claude<\/code> starts Claude Code through the project picker.<\/p>\n<p><code>workspace-new-project &lt;name&gt;<\/code> creates a project in <code>PROJECTS_HOME<\/code>, initializes Git, and creates an initial commit. If <code>GITHUB_ORG<\/code> is set, it can create a private GitHub repo.<\/p>\n<p><code>workspace-sync<\/code> performs safe sync: fetch, fast-forward pull for clean repositories, and push for committed changes.<\/p>\n<p><code>workspace-sync --scheduled<\/code> is used by the timer: no auto-commit, no force push, and no push to an external <code>origin<\/code>.<\/p>\n<p><code>workspace-sync --commit<\/code> enables interactive mode: for dirty repos it asks whether to commit. If <code>SECRET_SCAN_CMD<\/code> is set, it runs before the commit.<\/p>\n<p><code>workspace-ensure-backup --push<\/code> adds a backup remote for external upstream repositories.<\/p>\n<h3>Limitations<\/h3>\n<p>GitHub sync is not a full backup. It does not replace <code>restic<\/code>, <code>borg<\/code>, or another real backup tool. Uncommitted documents, large artifacts, databases, dumps, and agent sessions should live elsewhere.<\/p>\n<p>Sync works only with Git repositories. If a project is not initialized as a Git repo, the launcher may show it, but sync will not preserve its state.<\/p>\n<p>Scheduled sync does not commit dirty worktrees. This is inconvenient sometimes, but intentional: automatic commits can publish secrets, temporary files, or half-finished work.<\/p>\n<p>The system does not guess policy for external repositories. If <code>origin<\/code> does not belong to your <code>GITHUB_ORG<\/code>, the project is treated as external. It can be backed up to <code>backup<\/code>, but pushing to upstream requires an explicit command.<\/p>\n<p>It is also a shell-first system. It is transparent and easy to fix, but it does not provide a polished UI, operation history, or centralized project database.<\/p>\n<h3>How we tuned it<\/h3>\n<p>The first version was personal: a specific GitHub organization, local roots, and a local secret-audit script. That is wrong for an open-source project: a user should provide their values during setup instead of removing somebody else\u2019s.<\/p>\n<p>The first fix was <code>workspace-configure<\/code>. The repository now contains generic defaults, while real machine configuration lives in <code>~\/.config\/ai-workspace\/config<\/code>.<\/p>\n<p>The second fix was log placement. A sync log could end up inside the repository. Logs contain local paths, repo names, branches, and remotes. Now logs default to <code>~\/.local\/state\/ai-workspace\/logs<\/code>, and <code>logs\/<\/code> is ignored.<\/p>\n<p>The third fix made <code>workspace-new-project<\/code> independent of hardcoded <code>~\/projects<\/code>. It uses <code>PROJECTS_HOME<\/code>, or the first item from <code>PROJECT_ROOTS<\/code>. <code>ai-launch<\/code> now uses the actual path printed by <code>workspace-new-project<\/code>.<\/p>\n<p>The fourth fix was documentation cleanup. README, bootstrap prompts, templates, and playbooks use placeholders such as <code>&lt;ai-workspace-system-repo-url&gt;<\/code>, <code>&lt;github-org-or-user&gt;<\/code>, and <code>&lt;projects-home&gt;<\/code>.<\/p>\n<h3>What broke and how it was fixed<\/h3>\n<p>One failure was conceptual: the system wanted to be both a working personal tool and a portable template. Hardcoded GitHub organization and local paths made it convenient on one machine but broke reuse. The fix was explicit setup through <code>workspace-configure<\/code> and an empty <code>GITHUB_ORG<\/code> in <code>config.example<\/code>.<\/p>\n<p>Another failure was publication safety. A sync log got into the project working tree. It was not a secret by itself, but it exposed the structure of the home directory. The fix was to move logs to the XDG state directory and exclude <code>logs\/<\/code> from Git.<\/p>\n<p>A third failure was secret scanning. <code>workspace-sync --commit<\/code> called a private local script. That file does not exist on another machine. The fix was to make <code>SECRET_SCAN_CMD<\/code> an optional local setting.<\/p>\n<p>The fourth failure was a Git hook. A global <code>prepare-commit-msg<\/code> hook was supposed to add <code>Signed-off-by<\/code>, but it used <code>echo -e<\/code> under <code>\/bin\/sh<\/code>. On this system, <code>echo<\/code> printed <code>-e<\/code> as text, so commit messages got an extra line. The portable fix:<\/p>\n<pre><code>if [ -n \"$SOB\" ] &amp;&amp; ! grep -qs \"^$SOB\" \"$1\"; then  printf '\\n%s\\n' \"$SOB\" &gt;&gt; \"$1\"fi<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p><code>printf<\/code> is more predictable than <code>echo -e<\/code> in shell scripts.<\/p>\n<p>The fifth failure was UX: project creation through the launcher assumed a fixed root. After introducing <code>PROJECTS_HOME<\/code>, that was wrong. The fix was to make <code>workspace-new-project<\/code> print the actual project path and make the launcher use it.<\/p>\n<p>The result is less \u201cmy local setup\u201d and more a small system. It still solves the same everyday task: open Codex or Claude Code, choose a project, safely sync state, and keep instructions from being lost. But personal decisions now live in local config, and the repository can be published and moved between machines.<\/p>\n<p>Repo: <a href=\"https:\/\/github.com\/tym83-ai\/ai-workspace-system\" rel=\"noopener noreferrer nofollow\">https:\/\/github.com\/tym83-ai\/ai-workspace-system<\/a><\/p>\n<\/div>\n<p>\u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/1041798\/\">https:\/\/habr.com\/ru\/articles\/1041798\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>I ran into a very practical problem after doing a lot of local work with AI agents. I had Codex projects, Claude Code projects, regular repositories edited with agents, drafts, pipelines, instructions, skills, artifacts, and several machines. At some point it became hard to tell where the current version of a project lived, which files were safe to push, where agent instructions belonged, and where source code had already been mixed with logs and intermediate output.That is why I built AI Workspace System: a small set of shell scripts, conventions, and Markdown documentation that makes local AI-agent work predictable. It is not an IDE and not an agent orchestrator. It is a thin infrastructure layer around Git, GitHub, Codex, and Claude Code.The core idea is simple: all projects should be visible from one list, instructions should follow one structure, sync should be safe by default, and machine-specific details should not live in the repository.What the project doesFirst, it provides one launcher for Codex and Claude Code. ai-launch codex or ai-launch claude shows a project list. The first items are always New project and No project: create a project or start an agent without project context.Second, it defines one instruction model. AGENTS.md is the canonical shared instruction file. CLAUDE.md imports it with @AGENTS.md and contains only Claude Code-specific notes.Third, it includes a conservative workspace-sync. It fetches remotes, pulls only clean worktrees, pushes already committed changes, and skips dirty repositories. In scheduled mode it never commits automatically.Fourth, it separates owned repositories from upstream repositories. If origin is external, scheduled sync pushes only to a separate backup remote. Pushing to the real upstream must be an explicit action.Fifth, it separates target repos from delivery workbenches. A target repo contains reviewed code and public material. A workbench contains prompts, research, drafts, pipelines, and local state. The relationship is described in workspace.yaml.How to use itMinimal setup:mkdir -p ~\/projectsgit clone &lt;ai-workspace-system-repo-url&gt; ~\/projects\/ai-workspace-system~\/projects\/ai-workspace-system\/bin\/install-localworkspace-configureinstall-local creates symlinks in ~\/.local\/bin, and workspace-configure writes local configuration:~\/.config\/ai-workspace\/configThe config contains everything that belongs to the current machine:PROJECTS_HOME=&#187;$HOME\/projects&#187;PROJECT_ROOTS=&#187;$HOME\/projects&#187;SYNC_ROOTS=&#187;$HOME\/projects&#187;GITHUB_ORG=&#187;&#187;BACKUP_REMOTE=&#187;backup&#187;CODEX_BIN=&#187;$HOME\/.local\/bin\/codex&#187;CLAUDE_BIN=&#187;$HOME\/.local\/bin\/claude&#187;SECRET_SCAN_CMD=&#187;&#187;PROJECT_ROOTS controls the picker. SYNC_ROOTS controls scheduled sync. A project can be visible to agents without being part of automatic sync.After setup:ai-launch codexai-launch claudeThe agent does not start in a random directory. The launcher first shows New project, No project, then projects from PROJECT_ROOTS. New project calls workspace-new-project: it creates a directory under PROJECTS_HOME, writes baseline README.md, AGENTS.md, CLAUDE.md, workspace.yaml, .gitignore, and .env.example, then runs git init -b main and creates an initial commit. No project is for one-off questions.Scheduled sync uses a user-level systemd timer at 13:00, 19:00, and 23:50:systemctl &#8212;user daemon-reloadsystemctl &#8212;user enable &#8212;now ai-workspace-sync.timerBefore enabling it:workspace-sync &#8212;dry-runThis shows which repositories will be discovered.Prompt for collecting local projectsAnother key part is prompts\/discover-and-normalize-local-projects.md. Give it to Codex or Claude Code on a machine where projects are scattered across the home directory. It asks the agent to inventory directories, classify candidates, propose a move plan, move safe projects, add README.md, AGENTS.md, CLAUDE.md, .gitignore, and workspace.yaml, and initialize Git only where the directory is a real project rather than an archive, cache, or runtime state.Short version:Find likely local projects, classify them, move approved ones into ~\/projects,add baseline AI\/project docs, and initialize Git repos where appropriate.Do not move secrets, agent sessions, logs, caches, auth files, or shell history.Preserve existing remotes. Ask before committing dirty repos or changing upstream policy.Global agent instructionsGlobal agent files are never overwritten blindly. ~\/.codex\/AGENTS.md, ~\/.claude\/CLAUDE.md, and ~\/.claude\/rules\/ are treated as machine-level preferences. If a file is missing, the agent may propose creating it. If it already exists, the agent should preserve its content and propose a small additive change. Runtime state, auth, sessions, logs, sqlite files, and shell history must not be committed.Commandsworkspace-configure writes local config: GitHub org or user, agent binary paths, project roots, and secret scanner.ai-launch codex starts Codex through the project picker.ai-launch claude starts Claude Code through the project picker.workspace-new-project &lt;name&gt; creates a project in PROJECTS_HOME, initializes Git, and creates an initial commit. If GITHUB_ORG is set, it can create a private GitHub repo.workspace-sync performs safe sync: fetch, fast-forward pull for clean repositories, and push for committed changes.workspace-sync &#8212;scheduled is used by the timer: no auto-commit, no force push, and no push to an external origin.workspace-sync &#8212;commit enables interactive mode: for dirty repos it asks whether to commit. If SECRET_SCAN_CMD is set, it runs before the commit.workspace-ensure-backup &#8212;push adds a backup remote for external upstream repositories.LimitationsGitHub sync is not a full backup. It does not replace restic, borg, or another real backup tool. Uncommitted documents, large artifacts, databases, dumps, and agent sessions should live elsewhere.Sync works only with Git repositories. If a project is not initialized as a Git repo, the launcher may show it, but sync will not preserve its state.Scheduled sync does not commit dirty worktrees. This is inconvenient sometimes, but intentional: automatic commits can publish secrets, temporary files, or half-finished work.The system does not guess policy for external repositories. If origin does not belong to your GITHUB_ORG, the project is treated as external. It can be backed up to backup, but pushing to upstream requires an explicit command.It is also a shell-first system. It is transparent and easy to fix, but it does not provide a polished UI, operation history, or centralized project database.How we tuned itThe first version was personal: a specific GitHub organization, local roots, and a local secret-audit script. That is wrong for an open-source project: a user should provide their values during setup instead of removing somebody else\u2019s.The first fix was workspace-configure. The repository now contains generic defaults, while real machine configuration lives in ~\/.config\/ai-workspace\/config.The second fix was log placement. A sync log could end up inside the repository. Logs contain local paths, repo names, branches, and remotes. Now logs default to ~\/.local\/state\/ai-workspace\/logs, and logs\/ is ignored.The third fix made workspace-new-project independent of hardcoded ~\/projects. It uses PROJECTS_HOME, or the first item from PROJECT_ROOTS. ai-launch now uses the actual path printed by workspace-new-project.The fourth fix was documentation cleanup. README, bootstrap prompts, templates, and playbooks use placeholders such as &lt;ai-workspace-system-repo-url&gt;, &lt;github-org-or-user&gt;, and &lt;projects-home&gt;.What broke and how it was fixedOne failure was conceptual: the system wanted to be both a working personal tool and a portable template. Hardcoded GitHub organization and local paths made it convenient on one machine but broke reuse. The fix was explicit setup through workspace-configure and an empty GITHUB_ORG in config.example.Another failure was publication safety. A sync log got into the project working tree. It was not a secret by itself, but it exposed the structure of the home directory. The fix was to move logs to the XDG state directory and exclude logs\/ from Git.A third failure was secret scanning. workspace-sync &#8212;commit called a private local script. That file does not exist on another machine. The fix was to make SECRET_SCAN_CMD an optional local setting.The fourth failure was a Git hook. A global prepare-commit-msg hook was supposed to add Signed-off-by, but it used echo -e under \/bin\/sh. On this system, echo printed -e as text, so commit messages got an extra line. The portable fix:if [ -n &#171;$SOB&#187; ] &amp;&amp; ! grep -qs &#171;^$SOB&#187; &#171;$1&#187;; then  printf &#8216;\\n%s\\n&#8217; &#171;$SOB&#187; &gt;&gt; &#171;$1&#8243;fiprintf is more predictable than echo -e in shell scripts.The fifth failure was UX: project creation through the launcher assumed a fixed root. After introducing PROJECTS_HOME, that was wrong. The fix was to make workspace-new-project print the actual project path and make the launcher use it.The result is less \u201cmy local setup\u201d and more a small system. It still solves the same everyday task: open Codex or Claude Code, choose a project, safely sync state, and keep instructions from being lost. But personal decisions now live in local config, and the repository can be published and moved between machines.Repo: https:\/\/github.com\/tym83-ai\/ai-workspace-system\u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 https:\/\/habr.com\/ru\/articles\/1041798\/<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-481779","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/481779","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=481779"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/481779\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=481779"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=481779"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=481779"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}