@ulysses-ai/create-workspace 0.19.0-beta.0 → 0.21.0-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/README.md +1 -1
- package/lib/upgrade.mjs +20 -0
- package/lib/upgrade.test.mjs +119 -0
- package/package.json +3 -3
- package/template/CLAUDE.md.tmpl +5 -2
- package/template/_claude/hooks/workspace-update-check.mjs +4 -4
- package/template/_claude/lib/freshness.mjs +20 -7
- package/template/_claude/lib/registry-check.mjs +80 -14
- package/template/_claude/scripts/build-workspace-context.mjs +2 -0
- package/template/_claude/scripts/chat-record.mjs +27 -19
- package/template/_claude/scripts/classify-update.mjs +212 -0
- package/template/_claude/scripts/cleanup-work-session.mjs +4 -2
- package/template/_claude/scripts/maintenance-audit.mjs +0 -0
- package/template/_claude/scripts/merge-mode.mjs +61 -0
- package/template/_claude/scripts/migrate-canonical-priority.mjs +7 -1
- package/template/_claude/scripts/migrate-claude-md-freshness-include.mjs +35 -10
- package/template/_claude/scripts/migrate-session-layout.mjs +19 -8
- package/template/_claude/scripts/migrate-sessions.mjs +444 -121
- package/template/_claude/scripts/task-pr.mjs +213 -105
- package/template/_claude/scripts/task-worktree.mjs +26 -16
- package/template/_claude/skills/complete-work/SKILL.md +16 -12
- package/template/_claude/skills/maintenance/SKILL.md +48 -108
- package/template/_claude/skills/migrate-sessions/SKILL.md +17 -5
- package/template/_claude/skills/start-work/SKILL.md +4 -4
- package/template/_claude/skills/workspace-init/SKILL.md +20 -14
- package/template/_claude/skills/workspace-update/SKILL.md +93 -40
- package/template/_gitignore +3 -0
- package/template/workspace.json.tmpl +0 -1
|
@@ -1,42 +1,64 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: workspace-update
|
|
3
|
-
description: Apply a staged template update to an initialized workspace. The CLI stages a payload in .workspace-update/; this skill processes it
|
|
3
|
+
description: Apply a staged template update to an initialized workspace. The CLI stages a payload in .workspace-update/; this skill processes it and verifies the result with a scripted audit.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Workspace Update
|
|
7
7
|
|
|
8
|
-
Apply a staged template update to an initialized workspace. The CLI (`npx @ulysses-ai/create-workspace --upgrade`) stages the payload in `.workspace-update/`. This skill reads and applies it
|
|
8
|
+
Apply a staged template update to an initialized workspace. The CLI (`npx @ulysses-ai/create-workspace --upgrade`) stages the payload in `.workspace-update/`. This skill reads and applies it, then verifies the result with the scripted maintenance audit — one audit, at the end, when there is something to verify (gh:180).
|
|
9
9
|
|
|
10
10
|
## Prerequisites
|
|
11
11
|
|
|
12
12
|
- `workspace.json` must have `initialized: true`
|
|
13
|
-
- If not initialized,
|
|
13
|
+
- If not initialized, check whether initialization was committed but never merged — the workspace-init flow ends with its commits merged to the default branch, so an unmerged init branch explains a missing flag:
|
|
14
|
+
```bash
|
|
15
|
+
git log --all --format=%H -S'"initialized": true' -- workspace.json
|
|
16
|
+
```
|
|
17
|
+
If there are hits, name the branch holding the newest commit (`git branch --all --contains {sha}`) and report: "This workspace was initialized on branch `{branch}`, but that branch was never merged. Merge it first (`git merge {branch}`), then re-run /workspace-update." Only if there are no hits, report: "Workspace not initialized. Run /workspace-init first."
|
|
14
18
|
- `.workspace-update/` payload directory must exist (staged by `npx @ulysses-ai/create-workspace --upgrade`)
|
|
15
19
|
- If no `.workspace-update/` payload exists, report: "No update payload found. Run `npx @ulysses-ai/create-workspace --upgrade` to stage the template."
|
|
16
|
-
- Read `.workspace-update/.manifest.json` for `fromVersion`, `
|
|
20
|
+
- Read `.workspace-update/.manifest.json` for `fromVersion`, `templateVersion` (the target version), and `action`
|
|
17
21
|
- If `action` is `"init"`, report: "This payload is for initial setup. Run /workspace-init instead."
|
|
18
22
|
|
|
19
23
|
## Flow
|
|
20
24
|
|
|
21
|
-
### Step 1:
|
|
25
|
+
### Step 1: Decide where the update lands
|
|
22
26
|
|
|
23
|
-
|
|
27
|
+
Check the workspace repo for a remote (`git remote`). This decides where every later step works:
|
|
24
28
|
|
|
25
|
-
|
|
29
|
+
- **No remote** — apply in place. The Step 7 commit lands on the launcher's default branch: the one sanctioned launcher commit, because a repo with no remote has nowhere else for a template update to go. The payload path is `.workspace-update/`.
|
|
30
|
+
- **A remote exists** — the launcher never commits to its default branch. Create a task worktree up front and treat it as the workspace root for Steps 2–6:
|
|
31
|
+
```bash
|
|
32
|
+
node .claude/scripts/task-worktree.mjs --root . --create --repo . --branch chore/template-update-{version}
|
|
33
|
+
```
|
|
34
|
+
The payload is untracked, so it does not appear inside the worktree — keep referencing it at the launcher's absolute path (`{launcher}/.workspace-update`). Step 7 commits, pushes, and PRs from the worktree.
|
|
26
35
|
|
|
27
|
-
|
|
36
|
+
In the commands below, `{payload}` is `.workspace-update` in the no-remote flow and `{launcher}/.workspace-update` in the worktree flow.
|
|
28
37
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
-
|
|
32
|
-
|
|
38
|
+
### Step 2: Classify the payload
|
|
39
|
+
|
|
40
|
+
Run the classifier — it compares every verbatim-installed payload file (`.claude/**`, `.mcp.json`, `.claudeignore`) against the workspace by content, and detects files the template no longer ships:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
node {payload}/.claude/scripts/classify-update.mjs --root . --payload {payload}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
It runs from the payload precisely so workspaces that don't have it installed yet can use it; once installed, `node .claude/scripts/classify-update.mjs --root .` is equivalent. Output is JSON with five lists:
|
|
47
|
+
|
|
48
|
+
- `new` — no installed counterpart; safe to batch-apply (Step 3) behind one confirmation
|
|
49
|
+
- `identical` — installed file already equals the payload; skip silently
|
|
50
|
+
- `differs` — installed file was locally modified; needs a per-file decision
|
|
51
|
+
- `activated` — the payload ships `rules/{name}.md.skip` while the workspace keeps `{name}.md` active: the rule was deliberately activated. Nothing to install — the active rule stays.
|
|
52
|
+
- `removed` — installed file with no counterpart in the payload. Tests (`*.test.mjs`), gitignored paths, `.claude/worktrees/`, and files listed in `workspace.json` → `workspace.localFiles` (an array of `.claude/`-relative paths or globs the workspace owns) are excluded automatically, so only real template removals are listed.
|
|
53
|
+
|
|
54
|
+
Templates (`*.tmpl`, which install with `{{project-name}}` substitution), `_gitignore` (merged line-by-line), and `.manifest.json` (payload metadata) are not classified — each is handled by its own sub-step in Step 3.
|
|
33
55
|
|
|
34
56
|
Report with version info from the manifest:
|
|
35
57
|
```
|
|
36
|
-
"Template update: v{fromVersion} → v{
|
|
58
|
+
"Template update: v{fromVersion} → v{templateVersion}. {N} new files, {M} locally modified, {A} activated rules, {R} removed files, {K} unchanged."
|
|
37
59
|
```
|
|
38
60
|
|
|
39
|
-
If
|
|
61
|
+
If `new`, `differs`, `activated`, and `removed` are all empty, report: "Workspace is up to date (template v{templateVersion}). No changes needed."
|
|
40
62
|
|
|
41
63
|
### Step 2b: Historical .gitignore safety check
|
|
42
64
|
|
|
@@ -57,30 +79,31 @@ Commit the fix **before** applying other template updates. This runs ahead of St
|
|
|
57
79
|
|
|
58
80
|
### Step 3: Selective update
|
|
59
81
|
|
|
60
|
-
|
|
82
|
+
Batch the safe case, ask on the rest:
|
|
61
83
|
|
|
62
|
-
- **New
|
|
63
|
-
- **
|
|
64
|
-
- **
|
|
65
|
-
- **
|
|
84
|
+
- **New files (`new`):** present the list once — "Apply these {N} new files? [Y/n]" — and install them all on confirmation. No per-file prompting.
|
|
85
|
+
- **Locally modified (`differs`):** ask per file — "Template updated {file} but you have local changes. Show diff? [y/N]" — then apply, keep, or merge per the user's decision.
|
|
86
|
+
- **Removed in template (`removed`):** "Template removed {file}. Delete locally? [y/N]" — conservative default.
|
|
87
|
+
- **Activated rules (`activated`):** keep the active file, install nothing — report: "Rule {name} is active here and optional in the template; your activation is preserved."
|
|
66
88
|
- **Hook migration (.sh to .mjs):** Detect old `.sh` hooks in `.claude/hooks/` that have `.mjs` replacements in the payload. Offer: "Hook {name}.sh has a .mjs replacement in the update. Replace and update settings.json commands? [Y/n]" — this is a one-time migration for workspaces upgrading from pre-0.2.0
|
|
67
89
|
|
|
68
90
|
Also handle these non-component files from the payload:
|
|
69
91
|
|
|
70
92
|
- **settings.json:** Merge payload values into existing `.claude/settings.json` — do not overwrite user customizations. Add new keys, update hook commands if hooks were migrated, preserve user-added entries.
|
|
71
|
-
- **
|
|
72
|
-
-
|
|
93
|
+
- **workspace.json keys:** Compare the `workspace` object of the payload's `workspace.json.tmpl` with the installed `workspace.json`, key by key. For each key the template ships that the workspace lacks, show its template default and ask before adding. Never remove an existing key just because the template no longer ships it. `canonicalBudgetBytes` is opt-in since v0.19 — leave an existing value alone and mention it can be removed to turn the budget off.
|
|
94
|
+
- **CLAUDE.md:** If `{payload}/CLAUDE.md.tmpl` exists, regenerate `CLAUDE.md` from the template. Preserve any user-added sections not present in the template.
|
|
95
|
+
- **.gitignore:** Merge new entries from the payload's `_gitignore` into the existing `.gitignore` — do not remove user-added lines.
|
|
73
96
|
|
|
74
97
|
### Step 4: Update version
|
|
75
98
|
|
|
76
|
-
Read `
|
|
99
|
+
Read `templateVersion` from `.workspace-update/.manifest.json` and update `templateVersion` in `workspace.json` to match.
|
|
77
100
|
|
|
78
101
|
### Step 4a: Run idempotent migrators
|
|
79
102
|
|
|
80
|
-
|
|
103
|
+
Two migrators run on **every** update — both idempotent, safe on already-migrated workspaces. Run each and surface its action in the upgrade summary.
|
|
81
104
|
|
|
82
105
|
```bash
|
|
83
|
-
node
|
|
106
|
+
node {payload}/.claude/scripts/migrate-claude-md-freshness-include.mjs --root .
|
|
84
107
|
```
|
|
85
108
|
|
|
86
109
|
Output is JSON: `{"action":"appended"|"unchanged"|"skipped"}`.
|
|
@@ -90,34 +113,63 @@ Output is JSON: `{"action":"appended"|"unchanged"|"skipped"}`.
|
|
|
90
113
|
- `skipped` — no `CLAUDE.md` exists at the workspace root (rare; surface to the user).
|
|
91
114
|
|
|
92
115
|
```bash
|
|
93
|
-
node
|
|
116
|
+
node {payload}/.claude/scripts/migrate-canonical-priority.mjs --root .
|
|
94
117
|
```
|
|
95
118
|
|
|
96
|
-
Output is JSON: `{"status":"applied"|"noop","files":[...]}`. Back-fills `priority: critical` on every `workspace-context/shared/locked/*.md` that lacks the field, preserving today's full-load behavior until the user explicitly demotes a file.
|
|
119
|
+
Output is JSON: `{"status":"applied"|"noop","files":[...]}`. Back-fills `priority: critical` on every `workspace-context/shared/locked/*.md` that lacks the field, preserving today's full-load behavior until the user explicitly demotes a file. Skips `local-only-*` files — they are machine-local, never canonical.
|
|
120
|
+
|
|
121
|
+
The other `migrate-*.mjs` in the payload are **one-shot, version-gated migrations**: each applies to a specific step of the template's history and runs only when the manifest's `fromVersion` is older than the version that introduced it. Never run them unconditionally.
|
|
122
|
+
|
|
123
|
+
- `migrate-session-layout.mjs` — pre-v0.10.0 → v0.10.0: moves session content from launcher-side paths into each session's workspace worktree.
|
|
124
|
+
- `migrate-to-workspace-context.mjs` — pre-v0.15.0 → v0.15.0: renames `shared-context/` to `workspace-context/` and rebuilds its structure.
|
|
125
|
+
- `migrate-sessions.mjs` — pre-v0.18.0: drains session-model work sessions onto the task lifecycle. Not run inline — it has interactive inventory/backup/archive modes; `/migrate-sessions` drives it (Step 8 nudges when it applies).
|
|
126
|
+
- `migrate-open-work.mjs` — manual, any version: converts the deprecated `open-work.md` into tracker issues. Takes the file path as an argument and needs a configured tracker; run it only if the workspace still carries an `open-work.md` and the user asks.
|
|
97
127
|
|
|
98
|
-
|
|
128
|
+
Always run migrators with `--root .`. They resolve the workspace root from `--root` (default: the cwd) and never from their own location — a migrator invoked from the payload without `--root` would look for the workspace inside `.workspace-update/`.
|
|
99
129
|
|
|
100
130
|
### Step 5: Post-update verification
|
|
101
131
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
-
|
|
132
|
+
First regenerate the context catalogs — an update that adds or renames files under `.claude/` or `workspace-context/` leaves `index.md`/`canonical.md` stale until they are rebuilt:
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
node .claude/scripts/build-workspace-context.mjs --write --root .
|
|
136
|
+
node .claude/scripts/build-workspace-context.mjs --check --root .
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
`--check` must exit clean. If it reports drift, re-run `--write` and check again before continuing.
|
|
140
|
+
|
|
141
|
+
Then run the scripted audit — once, covering everything (gh:180). Build the changed list — every file this update added or changed: the applied `new` and `differs` files plus the merged ones (`CLAUDE.md`, `workspace.json`, `.gitignore`, `.claude/settings.json`, `.mcp.json` as touched) — one path per line into a temp file such as `workspace-scratchpad/update-changed.txt`, then:
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
node {payload}/.claude/scripts/maintenance-audit.mjs --root . --changed workspace-scratchpad/update-changed.txt
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Run it from the payload for the same reason as the classifier in Step 2: the workspace's own copy may predate this update. It reuses the sections of `/maintenance` audit that a script can decide (cross-references, frontmatter, structure, git state, catalog integrity, budgets, freshness) and marks findings on changed files `(from this update)`.
|
|
148
|
+
|
|
149
|
+
Present its report as the verification result. Git-state warnings are expected here — the update's commit hasn't landed yet (Step 7). Delete the temp changed list afterwards.
|
|
150
|
+
|
|
151
|
+
- Findings labeled `(from this update)` — caused by this update; fix before committing (usually a new skill missing from CLAUDE.md's list, or a stale catalog).
|
|
152
|
+
- Other findings — pre-existing; mention briefly.
|
|
153
|
+
- If the audit found issue-severity findings, offer to walk through them with the full `/maintenance` skill. Warnings and infos alone need no follow-up.
|
|
106
154
|
|
|
107
155
|
Report: "Post-update verification: {N} issues found" or "Post-update verification clean."
|
|
108
156
|
|
|
109
157
|
### Step 6: Cleanup
|
|
110
158
|
|
|
111
|
-
Delete the `.workspace-update/` directory
|
|
159
|
+
Delete the `.workspace-update/` directory at the launcher — in the worktree flow (Step 1) that happens after the merge and pull in Step 7. The payload has been fully processed and is no longer needed.
|
|
112
160
|
|
|
113
161
|
### Step 7: Commit
|
|
114
162
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
```
|
|
163
|
+
Where the commit lands was decided in Step 1.
|
|
164
|
+
|
|
165
|
+
- **No remote:** commit in place on the launcher's default branch — the one sanctioned launcher commit:
|
|
166
|
+
```bash
|
|
167
|
+
git add -A
|
|
168
|
+
git commit -m "chore: update workspace from template v{fromVersion} to v{templateVersion}"
|
|
169
|
+
```
|
|
170
|
+
- **Remote exists:** from the task worktree created in Step 1, commit the applied update, push the branch, and open a PR through the forge adapter — `node .claude/scripts/task-pr.mjs` when the workspace has it, otherwise the adapter under `.claude/scripts/forges/`. After the PR merges, pull the launcher, then delete the payload (Step 6).
|
|
119
171
|
|
|
120
|
-
Report: "Workspace updated to v{
|
|
172
|
+
Report: "Workspace updated to v{templateVersion}. Restart Claude Code if rules or hooks changed."
|
|
121
173
|
|
|
122
174
|
### Step 8: Session-model migration nudge
|
|
123
175
|
|
|
@@ -126,9 +178,10 @@ After the update is applied, if the sessions directory (`workspace.workSessionsD
|
|
|
126
178
|
## Notes
|
|
127
179
|
|
|
128
180
|
- The CLI (`npx @ulysses-ai/create-workspace --upgrade`) stages the payload. This skill processes it.
|
|
129
|
-
- Never overwrites without asking
|
|
130
|
-
- Preserves local modifications and
|
|
181
|
+
- Never overwrites without asking — `new` files are batched behind one confirmation; `differs` files are asked per file
|
|
182
|
+
- Preserves local modifications, custom content, existing `workspace.json` keys, and deliberately activated rules
|
|
183
|
+
- The launcher's default branch takes a template-update commit only when the workspace has no remote; with a remote, the update lands through a task worktree and a PR
|
|
131
184
|
- Can be run multiple times safely (idempotent) — if `.workspace-update/` doesn't exist, it reports no payload and exits
|
|
132
185
|
- Initial setup is handled by `npx @ulysses-ai/create-workspace --init` + `/workspace-init` — this skill is for subsequent updates only
|
|
133
186
|
- The `.sh` to `.mjs` hook migration is a one-time transition for workspaces created before hooks moved to JavaScript
|
|
134
|
-
- The
|
|
187
|
+
- The post-update audit is read-only and non-blocking — it surfaces issues, labels the ones this update caused, and never prevents the update itself
|
package/template/_gitignore
CHANGED
|
@@ -12,6 +12,9 @@ work-sessions/
|
|
|
12
12
|
# Disposable workspace-scoped scratchpad (session log, hook debug output)
|
|
13
13
|
workspace-scratchpad/
|
|
14
14
|
|
|
15
|
+
# Staged template payload — transient input for /workspace-update, never tracked
|
|
16
|
+
.workspace-update/
|
|
17
|
+
|
|
15
18
|
# Personal overrides
|
|
16
19
|
.claude/settings.local.json
|
|
17
20
|
.claude/.active-session.json
|