Install
$ agentstack add skill-mdproctor-cc-praxis-workspace-init ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ● Filesystem access Used
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
Workspace Init
Creates a companion workspace at ~/claude/private// or ~/claude/public//. If the project belongs to a family of related projects, the workspace can be nested under a shared parent folder (e.g. ~/claude/private/casehub/claudony/). Run once per project, per machine.
After running, open Claude in the workspace — CLAUDE.md instructs Claude to add-dir the project automatically at session start.
Workflow
Step 1 — Detect context (no questions yet)
Run all detection silently first. Do not ask the user anything in this step.
PROJECT_PATH=$(pwd)
PROJECT_NAME=$(basename "$PROJECT_PATH")
PROJECT_PARENT_DIR=$(dirname "$PROJECT_PATH")
INFERRED_PARENT=$(basename "$PROJECT_PARENT_DIR")
case "$PROJECT_NAME" in
parent|bom|build|root|aggregator) MAVEN_STRUCTURAL_NAME=true ;;
esac
case "$INFERRED_PARENT" in
src|projects|code|repos|dev|home|Users|claude|private|public|workspace|workspaces|".")
INFERRED_PARENT="" ;;
esac
GITHUB_OWNER=$(git -C "$PROJECT_PATH" remote get-url origin 2>/dev/null | sed 's|.*github.com[:/]\([^/]*\)/.*|\1|')
Step 1 — Input collection (single AskUserQuestion batch)
Use a SINGLE AskUserQuestion tool call with all four questions below. This produces the step-wizard header UI (e.g. ☒ Name ☒ Privacy ☒ Tag ☐ Position). Do NOT ask questions one at a time — that loses the wizard UI.
Also run the family detection bash (Step 1a) before this call so the family grouping question can be included if a family is detected.
The four questions to batch:
Q1 — Workspace name:
- If
MAVEN_STRUCTURAL_NAME=true: "This folder is named `— a Maven convention. Inferred workspace name:`. Confirm or type a different name." - Otherwise: "Workspace name: `` — confirm or type a different name."
- Options: `` (Recommended), Other
Q2 — Privacy:
- "Where should the workspace live?"
- Options: private, public
Q3 — Workspace repo tag:
- "Tag to distinguish workspace repos from project repos on GitHub (e.g. wsp-casehub vs casehub)."
- Options: wsp (Recommended), ws, wrk, Custom
Q4 — Tag position:
- "Prefix or postfix?"
- Options: prefix → wsp-casehub / wsp-casehub-work (Recommended), postfix → casehub-wsp / casehub-work-wsp
After collecting all four answers, set:
WORKSPACE_NAMEfrom Q1PRIVACY_FLAG=--privateorPRIVACY_FLAG=--publicfrom Q2 — use in ALLgh repo createcallsTAGfrom Q3, position from Q4REPO_NAMEpattern: prefix →-/--; postfix →-/--
Then run Step 1a (family detection) if not already done, then ask the second batch below.
Step 1 — Second AskUserQuestion batch (family scope + GitHub URL)
Use a second single AskUserQuestion call with these two questions.
Q5 — Family grouping (only if family detected in Step 1a):
- Show the sibling list and ask scope.
- Options: All repos, Select repos, Just this one, Flat (no family grouping)
Q6 — GitHub remote URL:
- "GitHub remote URL for the workspace repo (optional). Suggested:
github.com//" - Options: Yes use suggested URL, Skip — add later, Custom URL
After Q6: if URL confirmed or provided, check whether the GitHub repo already exists:
gh repo view / --json name,description 2>/dev/null && echo "exists" || echo "not found"
- Doesn't exist → create at Step 8
- Exists + workspace markers → offer to clone and reuse
- Exists + NOT workspace → offer delete+recreate or exit
Step 1 — Third AskUserQuestion batch (CLAUDE.md handling per repo)
Use a third single AskUserQuestion call for this question.
Q7 — CLAUDE.md handling:
For each repo in scope, detect its CLAUDE.md status: Check each repo individually with separate commands — no loop:
git -C /concrete/path/to/repo1 ls-files --error-unmatch CLAUDE.md 2>/dev/null && echo "committed" || echo "not committed"
git -C /concrete/path/to/repo2 ls-files --error-unmatch CLAUDE.md 2>/dev/null && echo "committed" || echo "not committed"
Present one question showing all repos and their status, asking for the handling decision. The user can set one default for all, or override per repo:
> "How should existing CLAUDE.md files be handled? > > | Repo | Status | Default decision | > |----------|-----------|-----------------| > | engine | committed | A — migrate to workspace + symlink back | > | work | committed | A — migrate to workspace + symlink back | > | ledger | untracked | C — symlink only | > | qhorus | missing | init — create with /init first | > > A = migrate content to workspace CLAUDE.md, git rm from project, symlink back > B = keep in project, workspace CLAUDE.md @includes it > C = untracked — create workspace CLAUDE.md, symlink project→workspace > init = missing — run /init to create, then treat as A or B > > Accept defaults, or specify overrides (e.g. 'engine=B, work=B, rest=A'):"
Record the decision per repo as CLAUDE_MD_DECISION[repo]. These decisions are used during execution — no further confirmation needed per repo.
Step 1a — Detect project family
Run immediately after Step 1 context detection. The detection already has PROJECT_PARENT_DIR and INFERRED_PARENT from Step 1.
Infer the potential parent name from the project path:
# Already computed in Step 1:
# PROJECT_PARENT_DIR, INFERRED_PARENT
If INFERRED_PARENT is non-empty, run two checks in order:
Check A — Existing family workspace folder:
Use Python to list subdirectories — avoids find -exec and shell variable expansion:
python3 /tmp/ws_family_check.py
Write /tmp/ws_family_check.py with the Write tool before running:
import os, sys
from pathlib import Path
family_path = Path.home() / "claude" / "" / ""
project_parent = Path("")
# List existing child workspaces
if family_path.is_dir():
members = sorted(d.name for d in family_path.iterdir() if d.is_dir())
print("FAMILY_MEMBERS:", ", ".join(members) if members else "")
else:
print("FAMILY_MEMBERS:")
# Find git repos in project parent without a workspace
missing = []
for d in sorted(project_parent.iterdir()):
if d.is_dir() and (d / ".git").is_dir() and not (family_path / d.name).is_dir():
missing.append(d.name)
print("MISSING:", ", ".join(missing) if missing else "")
Substitute concrete values for `, , and ` before writing.
Present:
> "Found an existing family workspace at ~/claude/// containing: `. > > Repos without a workspace yet: (or "all accounted for" if none) > > 1. **All missing** — create workspaces for all repos not yet set up > 2. **Just this one** — add /` to the family > 3. Flat — no family grouping"
If 1 → set BATCH_REPOS=, set BASE=~/claude///, run batch. If 2 → set BASE=~/claude/// and proceed to Step 1b. If 3 → use flat path BASE=~/claude// and proceed to Step 2.
Check B — Sibling git repos in same parent directory (no existing family workspace):
Only run Check B if Check A did not trigger (no existing family folder found).
List what's already cloned locally AND check GitHub for repos in the family that haven't been cloned yet. The goal is always "clone missing peers" — whether that's all of them or just a few.
Use separate commands — no shell variables or loops:
ls -d /concrete/project/parent/dir/*/
and:
gh repo list --json name --jq '.[].name' 2>/dev/null
Compute LOCALSIBLINGS, GITHUBREPOS, UNCLONED, and their counts manually from the output — do not use shell variables or pipes between these commands.
If $LOCAL_COUNT is greater than 1 OR $UNCLONED_COUNT is greater than 0, present:
> "Found family ` in : > > **Already cloned locally:** > **On GitHub, not yet cloned:** (or "none" if all present) > > How would you like to set up workspaces? > > 1. **All** — clone any missing peers + create workspaces for the whole family > 2. **Select** — choose which repos to include (cloning as needed) > 3. **Just this one** — create only /, nest it under the family folder > 4. **Flat** — no family grouping, use ~/claude///`"
If 1 (All): Clone each uncloned repo with a separate git clone command per repo — no loop:
git clone git@github.com:/.git /concrete/project/parent/
git clone git@github.com:/.git /concrete/project/parent/
Then set BATCH_REPOS=. Set BASE=~/claude///. Run Step 1b to create the family root, then run the full workspace-init workflow (Steps 2–10) for each repo in BATCH_REPOS one repo at a time, in strict sequence. Complete all steps (including Step 5 CLAUDE.md gate and Step 6 CLAUDE.md decision) for one repo before starting the next. Never parallelise across repos — each repo's CLAUDE.md decision must be individually confirmed. Skip any repo that already has a workspace.
If 2 (Select): Show numbered list of siblings, user picks. Set BATCH_REPOS=. Proceed as per option 1 for the selected set.
If 3 (Just this one): Set BASE=~/claude/// and proceed to Step 1b then Step 2.
If 4 (Flat): Use flat path BASE=~/claude// and proceed to Step 2.
If neither check triggers: proceed directly to Step 1.5 with BASE=~/claude//.
Step 1.5 — Show plan and confirm before executing anything
This step always runs before any files are created, moved, or committed.
Scan each repo in BATCH_REPOS (or just the current project if no batch) for methodology artifacts using the Step 9 detection logic — but do NOT execute anything yet. Build the full plan and present it to the user.
Present the plan in this format:
WORKSPACE INIT PLAN
══════════════════════════════════════════════════════════════════
FAMILY ROOT ~/claude/// (new git repo)
CLAUDE.md family workspace hub
.gitignore excludes child workspace dirs
adr/ blog/ snapshots/ specs/ plans/
CHILD WORKSPACES (one git repo each)
~/claude///engine/
~/claude///work/
~/claude///ledger/
...
CLAUDE.md HANDLING (decisions collected in Q7 — shown here for confirmation)
┌──────────┬───────────────┬──────────────────────────────────────────────┐
│ Repo │ Status │ Decision (from Q7) │
├──────────┼───────────────┼──────────────────────────────────────────────┤
│ engine │ committed │ A — migrate to workspace + symlink back │
│ work │ committed │ A — migrate to workspace + symlink back │
│ ledger │ untracked │ C — symlink project→workspace │
│ qhorus │ missing │ init — run /init first, then A │
│ ... │ ... │ ... │
└──────────┴───────────────┴──────────────────────────────────────────────┘
These decisions were set in Q7. "adjust" can change any row before YES.
FILE MOVES (copied to workspace, then git rm'd and committed in project)
┌──────────┬──────────────────────────────────┬──────────────────────────────┬────────┐
│ Repo │ Source (project) │ Destination (workspace) │ Files │
├──────────┼──────────────────────────────────┼──────────────────────────────┼────────┤
│ work │ HANDOFF.md │ work/HANDOFF.md │ 1 │
│ work │ blog/ │ work/blog/ │ 17 │
│ work │ docs/superpowers/plans/ │ work/plans/ │ 13 │
│ ledger │ HANDOFF.md │ ledger/HANDOFF.md │ 1 │
│ ledger │ IDEAS.md │ ledger/IDEAS.md │ 1 │
│ ledger │ blog/ │ ledger/blog/ │ 20 │
│ ... │ ... │ ... │ ... │
└──────────┴──────────────────────────────────┴──────────────────────────────┴────────┘
Each move: git rm in project → commit "chore: migrate methodology artifacts to workspace"
SESSION HISTORY MIGRATION
~/.claude/projects// → ~/.claude/projects//
(one migration per repo)
══════════════════════════════════════════════════════════════════
Total: workspaces · file moves · git commits across repos
Proceed with this plan? (YES / adjust / no)
- YES → execute per the decisions collected in Q7 and confirmed here.
No additional per-repo confirmations during execution — all decisions were made in Q7.
- adjust → user edits any draft or changes any action. Re-show updated plan, ask again.
- no → abort, nothing written.
Do not create any directories, move any files, run any git commands, or create any GitHub repos (gh repo create) until the user confirms with YES. Nothing touches the filesystem or GitHub until the plan is approved.
During execution: implement CLAUDE_MD_DECISION[repo] for each repo exactly as recorded in Q7. Do not re-ask or re-confirm — decisions were made in Q7 and confirmed in the plan.
Step 1b — Create or update family workspace root
Only runs when Step 1a resolved to a nested path (user said YES to family grouping).
The family root ~/claude/// is itself a workspace — opening Claude there gives cross-repo context. It has its own CLAUDE.md distinct from the per-repo child workspaces.
FAMILY_ROOT=~/claude//
FAMILY_CLAUDE="$FAMILY_ROOT/CLAUDE.md"
If FAMILY_CLAUDE does not exist — create it:
List sibling git repos using ls (concrete path, no variables):
ls -d /concrete/project/parent/dir/*/
Filter to those containing .git/ manually from the output.
Draft the family CLAUDE.md and show to user for acceptance before writing:
# Family Workspace
**Workspace type:**
## Member Repos
| Repo | Local path | Child workspace |
|------|-----------|-----------------|
| | | `//` |
Note: repos without a child workspace can still be loaded with add-dir on demand.
## Session Start
Open Claude here for cross-repo work. Load the repos relevant to your session:
add-dir
add-dir
## Shared Artifacts
Family-level artifacts (cross-repo ADRs, plans, design docs) accumulate here.
Per-repo artifacts live in each child workspace.
| Skill | Writes to |
|-------|-----------|
| adr | `adr/` |
| write-content | `blog/` |
| handover | `HANDOFF.md` |
| idea-log | `IDEAS.md` |
Create the standard artifact directories at the family root too:
Run: python3 ~/.claude/skills/workspace-init/workspace_create.py create-dirs
Write the family root .gitignore — ignores all child workspace directories so the family repo tracks only family-level artifacts, not the nested repos:
Write .gitignore using the Write tool with the concrete sibling names derived from the ls output above — no shell variables or heredoc expansion:
# Child workspace repos — each has its own git history
/
/
/
# OS / editor noise
.DS_Store
*.swp
This prevents git from seeing child workspace directories as untracked content or accidentally registering them as submodules. Each child repo commits to its own history independently.
If FAMILY_CLAUDE already exists — update the member repos table:
Add the new repo to the table if not already listed. Show the proposed change and ask for acceptance before writing. Never overwrite the whole file — only update the member repos table row.
Step 1b — Clone sibling repos from GitHub org
If the project directory exists and is a git repo with a GitHub remote, detect the GitHub organisation and offer to clone all sibling repos into the same parent directory.
# Detect org from remote
REMOTE_URL=$(git -C "" remote get-url origin 2>/dev/null)
if [ -n "$REMOTE_URL" ]; then
ORG=$(echo "$REMOTE_URL" | sed -E 's|.*github\.com[:/]([^/]+)/.*|\1|')
PARENT_DIR=$(dirname "")
fi
If an org is detected, list all repos and identify which are missing locally:
ALL_REPOS=$(gh repo list "$ORG" --json name --limit 200 -q '.[].name' | sort)
MISSING=()
for repo in $ALL_REPOS; do
if [ ! -d "$PARENT_DIR/$repo" ]; then
MISSING+=("$repo")
fi
done
If there are missing repos, present the list: > "Found **
…
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: mdproctor
- Source: mdproctor/cc-praxis
- License: Apache-2.0
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.