@ulysses-ai/create-workspace 0.24.0-beta.0 → 0.24.1-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ulysses-ai/create-workspace",
3
- "version": "0.24.0-beta.0",
3
+ "version": "0.24.1-beta.0",
4
4
  "description": "A workspace convention for Claude Code: sessions, handoffs, and shared context as files in git",
5
5
  "keywords": [
6
6
  "claude",
@@ -93,6 +93,33 @@ export function configSummary(config) {
93
93
  return parts.join(' | ');
94
94
  }
95
95
 
96
+ /**
97
+ * The one line session-start prints when this machine's per-user setup is
98
+ * incomplete (gh:130), or null when nothing is missing. A clone of a
99
+ * workspace a teammate already initialized is the common case: repos/ is
100
+ * empty and there is no user identity yet. /workspace-init handles that as
101
+ * a join — it no longer refuses an initialized workspace — so the hint says
102
+ * "join" there and keeps the plain "initialize" wording for a workspace
103
+ * that was never initialized.
104
+ *
105
+ * @param {object} s
106
+ * @param {boolean} s.initialized workspace.json → workspace.initialized
107
+ * @param {string[]} s.missingRepos repos in workspace.json absent under repos/
108
+ * @param {boolean} s.hasUser settings.local.json carries workspace.user
109
+ * @param {boolean} s.hasClaudeLocal CLAUDE.local.md exists
110
+ */
111
+ export function setupHint({ initialized, missingRepos = [], hasUser, hasClaudeLocal }) {
112
+ const gaps = [];
113
+ if (missingRepos.length > 0) gaps.push(`missing repos: ${missingRepos.join(', ')}`);
114
+ if (!hasUser) gaps.push('no user set in .claude/settings.local.json');
115
+ else if (!hasClaudeLocal) gaps.push('no CLAUDE.local.md');
116
+ if (gaps.length === 0) return null;
117
+ const action = initialized
118
+ ? 'Run /workspace-init to join this workspace — it sets up only what this machine is missing'
119
+ : 'Run /workspace-init to initialize this workspace';
120
+ return `Setup incomplete (${gaps.join('; ')}). ${action}.`;
121
+ }
122
+
96
123
  /**
97
124
  * Defensive normalization for a session tracker's `repos` field. A fresh
98
125
  * tracker written by create-work-session always holds an array, but an
@@ -16,6 +16,7 @@ import {
16
16
  sessionFolderPath,
17
17
  timeAgo,
18
18
  configSummary,
19
+ setupHint,
19
20
  } from './_utils.mjs';
20
21
  import { reconcile, readSessionRegistry, resolveChatName } from '../scripts/chat-record.mjs';
21
22
 
@@ -125,7 +126,16 @@ for (const name of repoNames) {
125
126
  missing.push(name);
126
127
  }
127
128
  }
128
- if (missing.length > 0) lines.push(`Missing repos: ${missing.join(', ')}. Run /workspace-init to clone them.`);
129
+ // A teammate's fresh clone of an initialized workspace has no repos/ and no
130
+ // user identity; point it at /workspace-init's join flow (gh:130).
131
+ const localSettings = readJSON(join(root, '.claude', 'settings.local.json'));
132
+ const hint = setupHint({
133
+ initialized: config.workspace?.initialized === true,
134
+ missingRepos: missing,
135
+ hasUser: typeof localSettings?.workspace?.user === 'string' && localSettings.workspace.user !== '',
136
+ hasClaudeLocal: existsSync(join(root, 'CLAUDE.local.md')),
137
+ });
138
+ if (hint) lines.push(hint);
129
139
  if (existing.length > 0) lines.push(`Repos synced: ${existing.join(', ')}`);
130
140
 
131
141
  // Surface active work sessions
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: workspace-init
3
- description: First-time workspace initialization. Clones repos, installs template components, extracts team knowledge from documentation sources and Claude chat history, activates rules, configures user identity. Run once after scaffolding with --init.
3
+ description: First-time workspace initialization, or joining a workspace a teammate already initialized. Init clones repos, installs template components, extracts team knowledge from documentation sources and Claude chat history, activates rules, configures user identity — run once after scaffolding with --init. Join (an initialized workspace you just cloned) sets up only this machine's per-user pieces — repos, user identity, CLAUDE.local.md.
4
4
 
5
5
  # scope example (remove # to activate path-scoped loading):
6
6
  # scope: repos/my-api/
@@ -10,19 +10,61 @@ description: First-time workspace initialization. Clones repos, installs templat
10
10
 
11
11
  Guided initialization for a newly scaffolded workspace. Clones repos, installs template components from the `.workspace-update/` payload, extracts team knowledge from documentation sources and prior Claude chat history, populates shared context, formalizes existing worktrees as work sessions, and configures the workspace for use.
12
12
 
13
- This skill runs once. After completion, it sets `workspace.initialized: true` in workspace.json.
13
+ Initialization runs once per workspace and ends by setting `workspace.initialized: true` in workspace.json. Every later person who clones that workspace runs this skill too — it detects the initialized workspace and runs the **join flow** instead, which sets up only the per-user, machine-local pieces.
14
14
 
15
- ## Prerequisites
15
+ ## Choose the mode
16
16
 
17
- - `.workspace-update/` payload directory must exist (staged by `npx @ulysses-ai/create-workspace --init`)
18
- - If no `.workspace-update/` payload exists, report: "No update payload found. Run `npx @ulysses-ai/create-workspace --init` to stage the template."
19
- - `workspace.json` must exist
17
+ `workspace.json` must exist; if it does not, report "No workspace.json found — this is not a workspace. Scaffold one with `npx @ulysses-ai/create-workspace --init`." and stop.
20
18
 
21
- ## Gate
19
+ Then decide from `workspace.initialized` and the `.workspace-update/` payload. `/workspace-init join` forces the join flow on an initialized workspace and skips the question below.
22
20
 
23
- If `workspace.json` has `"initialized": true` AND `.workspace-update/` exists with `"action": "init"` in `.workspace-update/.manifest.json`, warn: "Workspace is already initialized. Did you mean to run `npx @ulysses-ai/create-workspace --upgrade` instead?" Do not proceed unless the user explicitly confirms they want to re-initialize.
21
+ | `initialized` | `.workspace-update/` payload | Mode |
22
+ |---|---|---|
23
+ | not `true` | `"action": "init"` in `.manifest.json` | **Init** — the full flow below |
24
+ | not `true` | absent | Report "No update payload found. Run `npx @ulysses-ai/create-workspace --init` to stage the template." and stop |
25
+ | `true` | `"action": "init"` | Warn "Workspace is already initialized. Did you mean to run `npx @ulysses-ai/create-workspace --upgrade` instead?" Proceed with init only if the user explicitly confirms a re-initialization |
26
+ | `true` | any other payload | Report "A template update is staged — run /workspace-update." and stop |
27
+ | `true` | absent | **Join check** — see below |
24
28
 
25
- If `workspace.json` has `"initialized": true` and no `.workspace-update/` payload exists, report: "Workspace already initialized. Use /workspace-update for template updates, or /maintenance to check integrity."
29
+ **Join check.** Inspect this machine's per-user pieces:
30
+
31
+ - `repos/{name}` for each repo in `workspace.json`;
32
+ - `.claude/settings.local.json` with a `workspace.user` value;
33
+ - `CLAUDE.local.md` at the root;
34
+ - `workspace-context/team-member/{user}/` for that user.
35
+
36
+ If all are present, report "Workspace already initialized and this machine is set up. Use /workspace-update for template updates, or /maintenance to check integrity." and stop. If any is missing, run the **Join flow**.
37
+
38
+ ## Join flow
39
+
40
+ A join sets up one person on one machine. It never touches tracked files, creates no branch and makes no commits — everything it writes is gitignored (`repos/`, `.claude/settings.local.json`, `CLAUDE.local.md`, the generated per-user index). It skips the template install, rule activation, knowledge extraction, tracker setup, root cleanup and remote setup: the teammate who initialized the workspace already did those, and their results arrived with the clone.
41
+
42
+ 1. **Report what is missing** — one line per piece from the join check, and say this run is a join, not an init.
43
+ 2. **User identity.** If `workspace.user` is unset, ask "What name should be used for your user-scoped context? [{system-username}]". List the existing `workspace-context/team-member/*/` folders so a returning person can pick theirs; a name with no folder is a new team member. The name must match `^[A-Za-z0-9_-]+$`. Write it to `.claude/settings.local.json`, merging into any existing content rather than replacing it:
44
+ ```json
45
+ { "workspace": { "user": "{name}" } }
46
+ ```
47
+ 3. **Clone missing repos.** For each repo in `workspace.json` without `repos/{name}`: ask "Clone {name} from {remote}? [Y/n]", then `git clone {remote} repos/{name}`. If the entry names a `branch`, check that the clone is on it. A clone that fails (no access, wrong URL) is reported and skipped, never retried in a loop.
48
+ 4. **Per-user folder.** If `workspace-context/team-member/{user}/` does not exist, create it. The generator in the next step builds an index only for folders that exist, so without it `CLAUDE.local.md` would import a missing file.
49
+ 5. **CLAUDE.local.md.** Run:
50
+ ```bash
51
+ node .claude/scripts/generate-claude-local.mjs --root .
52
+ ```
53
+ It writes `CLAUDE.local.md` from `workspace.user` and regenerates the workspace-context indexes, including the per-user one. If it refuses because a `CLAUDE.local.md` with different content exists, show the difference and ask before re-running with `--force`.
54
+ 6. **Report:**
55
+ ```
56
+ "Joined {workspace-name} as {user}.
57
+ - Repos cloned: {list} (already present: {list}; failed: {list})
58
+ - User identity: {set now | already set}
59
+ - CLAUDE.local.md: {written | unchanged}
60
+ - Work lifecycle: {workspace.sessionModel}
61
+
62
+ Restart Claude Code so CLAUDE.local.md and the hooks load, then run /start-work."
63
+ ```
64
+
65
+ A join changes nothing the team shares, so there is nothing to push. The new team member's first tracked file lands through ordinary work — `/start-work`, a `/braindump`, or `/handoff`.
66
+
67
+ The rest of this skill is the **Init** flow.
26
68
 
27
69
  ## Branching
28
70