@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
|
@@ -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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
##
|
|
15
|
+
## Choose the mode
|
|
16
16
|
|
|
17
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|