@ulysses-ai/create-workspace 0.20.0-beta.0 → 0.22.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/init.mjs +9 -0
- package/lib/init.test.mjs +75 -0
- package/lib/scaffold.mjs +8 -0
- package/lib/scaffold.test.mjs +20 -0
- package/package.json +1 -1
- package/template/CLAUDE.md.tmpl +2 -2
- package/template/_claude/hooks/workspace-update-check.mjs +4 -4
- package/template/_claude/scripts/build-workspace-context.mjs +2 -0
- package/template/_claude/scripts/classify-update.mjs +368 -17
- package/template/_claude/scripts/maintenance-audit.mjs +697 -0
- package/template/_claude/scripts/template-baseline.mjs +215 -0
- package/template/_claude/skills/maintenance/SKILL.md +48 -108
- package/template/_claude/skills/workspace-init/SKILL.md +6 -0
- package/template/_claude/skills/workspace-update/SKILL.md +71 -37
- package/template/workspace.json.tmpl +0 -1
|
@@ -1,11 +1,11 @@
|
|
|
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
|
|
|
@@ -22,11 +22,7 @@ Apply a staged template update to an initialized workspace. The CLI (`npx @ulyss
|
|
|
22
22
|
|
|
23
23
|
## Flow
|
|
24
24
|
|
|
25
|
-
### Step 1:
|
|
26
|
-
|
|
27
|
-
Run `/maintenance audit` (read-only) to surface existing issues. Report findings briefly but **always continue to Step 2 immediately** — do not stop to ask about audit results. The audit is informational, not a gate. Any issues found will be included in the post-update report (Step 5) alongside the update results.
|
|
28
|
-
|
|
29
|
-
### Step 1b: Decide where the update lands
|
|
25
|
+
### Step 1: Decide where the update lands
|
|
30
26
|
|
|
31
27
|
Check the workspace repo for a remote (`git remote`). This decides where every later step works:
|
|
32
28
|
|
|
@@ -41,28 +37,36 @@ In the commands below, `{payload}` is `.workspace-update` in the no-remote flow
|
|
|
41
37
|
|
|
42
38
|
### Step 2: Classify the payload
|
|
43
39
|
|
|
44
|
-
Run the classifier — it compares every verbatim-installed payload file (`.claude/**`, `.mcp.json`, `.claudeignore`) against the workspace
|
|
40
|
+
Run the classifier — it compares every verbatim-installed payload file (`.claude/**`, `.mcp.json`, `.claudeignore`) against the workspace and the template baseline (`.claude/.template-baseline.json`, the hashes of what the template last shipped here), and detects files the template no longer ships:
|
|
45
41
|
|
|
46
42
|
```bash
|
|
47
43
|
node {payload}/.claude/scripts/classify-update.mjs --root . --payload {payload}
|
|
48
44
|
```
|
|
49
45
|
|
|
50
|
-
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
|
|
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:
|
|
51
47
|
|
|
52
|
-
- `new` — no installed counterpart; safe to batch-apply (Step 3) behind one confirmation
|
|
48
|
+
- `new` — no installed counterpart and no baseline entry; safe to batch-apply (Step 3) behind one confirmation
|
|
53
49
|
- `identical` — installed file already equals the payload; skip silently
|
|
54
|
-
- `
|
|
50
|
+
- `updated` — installed file still holds the baseline content while the payload ships something new: a pure template change the user never touched. Batched with `new` behind one confirmation
|
|
51
|
+
- `differs` — installed file matches neither the payload nor the baseline, and the template changed it since the baseline too: a local edit that meets a template change. Needs a per-file decision
|
|
52
|
+
- `localOnly` — installed file differs from the payload, but the payload equals the baseline: these are local edits to files the template didn't touch. Informational only — never asked about, never applied
|
|
53
|
+
- `deletedLocally` — the baseline records the file and the payload still ships it, but it is missing from the workspace (deleted locally, or declined at install time). Step 3 asks once whether to restore the list
|
|
54
|
+
- `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.
|
|
55
|
+
- `removed` — installed file with no counterpart in the payload. 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.
|
|
56
|
+
- `staleTests` — `*.test.mjs` files under `.claude/` the payload doesn't carry. The package never ships tests, so these came from a dev checkout and no update refreshes them (Step 3 offers removal).
|
|
55
57
|
|
|
56
|
-
|
|
58
|
+
Content is compared with line endings normalized (CRLF ≡ LF; binary files byte-exact), so a Windows autocrlf checkout does not read as locally modified.
|
|
57
59
|
|
|
58
|
-
|
|
60
|
+
If `hasBaseline` is false (the workspace predates v0.21), tell the user: "No template baseline — this first update asks about every changed file individually; once it finishes and writes the baseline (Step 4), later updates won't." Template changes then land in `differs`.
|
|
61
|
+
|
|
62
|
+
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.
|
|
59
63
|
|
|
60
64
|
Report with version info from the manifest:
|
|
61
65
|
```
|
|
62
|
-
"Template update: v{fromVersion} → v{templateVersion}. {N} new files, {M} locally modified, {R} removed files, {K} unchanged."
|
|
66
|
+
"Template update: v{fromVersion} → v{templateVersion}. {N} new files, {U} template-updated, {M} locally modified, {L} local-only edits, {D} deleted locally, {A} activated rules, {R} removed files, {K} unchanged."
|
|
63
67
|
```
|
|
64
68
|
|
|
65
|
-
If `new`, `differs`, and
|
|
69
|
+
If `new`, `updated`, `differs`, `deletedLocally`, `activated`, and `removed` are all empty, report: "Workspace is up to date (template v{templateVersion}). No changes needed." (`localOnly` files are informational and `staleTests` may still be worth offering.)
|
|
66
70
|
|
|
67
71
|
### Step 2b: Historical .gitignore safety check
|
|
68
72
|
|
|
@@ -83,28 +87,43 @@ Commit the fix **before** applying other template updates. This runs ahead of St
|
|
|
83
87
|
|
|
84
88
|
### Step 3: Selective update
|
|
85
89
|
|
|
86
|
-
Batch the safe
|
|
90
|
+
Batch the safe cases, ask on the rest:
|
|
87
91
|
|
|
88
|
-
- **New files (`new`):** present
|
|
89
|
-
- **Locally modified (`differs`):** ask per file — "
|
|
90
|
-
- **
|
|
92
|
+
- **New and template-updated files (`new` + `updated`):** present both lists once — "Apply these {N} new and {U} template-updated files? [Y/n]" — and install them all on confirmation. No per-file prompting: `updated` means the file still holds exactly what the template last shipped here, so applying the new version loses nothing.
|
|
93
|
+
- **Locally modified (`differs`):** ask per file — "Your version of {file} differs from the template's. Show diff? [y/N]" — then apply, keep, or merge per the user's decision.
|
|
94
|
+
- **Local-only edits (`localOnly`):** nothing to decide — these are your local edits to files the template hasn't changed since the last update. List them in the summary (so the edits are visible) and move on; do not ask about them.
|
|
95
|
+
- **Deleted locally (`deletedLocally`):** "These {N} files exist in the template and its baseline but not in your workspace — deleted locally (or never installed). Restore from the template? [Y/n]" — one confirmation for the whole list. Restoring installs the payload's version of each.
|
|
96
|
+
- **Removed in template (`removed`):** "Template removed {file}. Delete locally? [y/N]" — conservative default.
|
|
97
|
+
- **Activated rules (`activated`):** keep the active file, install nothing — report: "Rule {name} is active here and optional in the template; your activation is preserved."
|
|
98
|
+
- **Stale tests (`staleTests`):** "These {N} test files under .claude/ came from a dev checkout — the package never ships them, so updates can't refresh them (tests live in the template repo). Remove them? [Y/n]" — one confirmation for the whole list.
|
|
91
99
|
- **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
|
|
92
100
|
|
|
93
101
|
Also handle these non-component files from the payload:
|
|
94
102
|
|
|
95
103
|
- **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.
|
|
96
104
|
- **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.
|
|
97
|
-
- **
|
|
98
|
-
|
|
105
|
+
- **CLAUDE.md:** If `{payload}/CLAUDE.md.tmpl` exists, merge — never regenerate from scratch:
|
|
106
|
+
```bash
|
|
107
|
+
node {payload}/.claude/scripts/classify-update.mjs --root . --payload {payload} --merge-claude-md
|
|
108
|
+
```
|
|
109
|
+
The command prints the merged CLAUDE.md: template-owned lines take the template's new versions (skill-list entries match by their `/name`), while lines the template doesn't have — the workspace's own skill entries, custom bullets, whole sections — are kept in place. Two consequences to watch in the diff: an edit made directly to a template-owned skill line is replaced by the template's new wording, and a template prose line that was reworded locally survives alongside the new template line (it may appear twice). The merge keeps the current file's line endings. Show the user the diff against the current CLAUDE.md before writing the merged result.
|
|
99
110
|
- **.gitignore:** Merge new entries from the payload's `_gitignore` into the existing `.gitignore` — do not remove user-added lines.
|
|
100
111
|
|
|
101
|
-
### Step 4: Update version
|
|
112
|
+
### Step 4: Update version and write the baseline
|
|
102
113
|
|
|
103
114
|
Read `templateVersion` from `.workspace-update/.manifest.json` and update `templateVersion` in `workspace.json` to match.
|
|
104
115
|
|
|
116
|
+
Then write the template baseline so the NEXT update classifies three ways instead of asking per file:
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
node {payload}/.claude/scripts/classify-update.mjs --root . --payload {payload} --write-baseline
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Run it after every Step 3 decision has been made (it runs from the payload because the workspace's own copy may predate this update). It records the hash of every verbatim payload file — what the template now ships — with one deliberate exception: a file whose update was declined (the workspace still holds the old baseline content while the payload ships something new) keeps the OLD entry, so the change is offered again as `updated` next time instead of being filed away. Everything else records the payload hash: a file the user kept in their own version reads as `localOnly` (informational) until the template changes it again, and a file nobody touched never reads as a local edit. The command refuses to write an empty baseline — if it errors, the payload path is wrong; do not force it.
|
|
123
|
+
|
|
105
124
|
### Step 4a: Run idempotent migrators
|
|
106
125
|
|
|
107
|
-
|
|
126
|
+
Two migrators run on **every** update — both idempotent, safe on already-migrated workspaces. Run each and surface its action in the upgrade summary.
|
|
108
127
|
|
|
109
128
|
```bash
|
|
110
129
|
node {payload}/.claude/scripts/migrate-claude-md-freshness-include.mjs --root .
|
|
@@ -120,20 +139,20 @@ Output is JSON: `{"action":"appended"|"unchanged"|"skipped"}`.
|
|
|
120
139
|
node {payload}/.claude/scripts/migrate-canonical-priority.mjs --root .
|
|
121
140
|
```
|
|
122
141
|
|
|
123
|
-
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.
|
|
142
|
+
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.
|
|
124
143
|
|
|
125
|
-
|
|
144
|
+
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.
|
|
126
145
|
|
|
127
|
-
|
|
146
|
+
- `migrate-session-layout.mjs` — pre-v0.10.0 → v0.10.0: moves session content from launcher-side paths into each session's workspace worktree.
|
|
147
|
+
- `migrate-to-workspace-context.mjs` — pre-v0.15.0 → v0.15.0: renames `shared-context/` to `workspace-context/` and rebuilds its structure.
|
|
148
|
+
- `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).
|
|
149
|
+
- `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.
|
|
128
150
|
|
|
129
|
-
|
|
151
|
+
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/`.
|
|
130
152
|
|
|
131
|
-
|
|
132
|
-
- Broken references (new skills not in CLAUDE.md, removed rules still referenced)
|
|
133
|
-
- Contradictions between updated rules and existing shared context
|
|
134
|
-
- Structural mismatches
|
|
153
|
+
### Step 5: Post-update verification
|
|
135
154
|
|
|
136
|
-
|
|
155
|
+
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:
|
|
137
156
|
|
|
138
157
|
```bash
|
|
139
158
|
node .claude/scripts/build-workspace-context.mjs --write --root .
|
|
@@ -142,22 +161,36 @@ node .claude/scripts/build-workspace-context.mjs --check --root .
|
|
|
142
161
|
|
|
143
162
|
`--check` must exit clean. If it reports drift, re-run `--write` and check again before continuing.
|
|
144
163
|
|
|
164
|
+
Then run the scripted audit — once, covering everything (gh:180). Build the changed list — every file this update added or changed: the applied `new`, `updated`, and `differs` files, any restored `deletedLocally` 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:
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
node {payload}/.claude/scripts/maintenance-audit.mjs --root . --changed workspace-scratchpad/update-changed.txt
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
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)`.
|
|
171
|
+
|
|
172
|
+
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.
|
|
173
|
+
|
|
174
|
+
- 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).
|
|
175
|
+
- Other findings — pre-existing; mention briefly.
|
|
176
|
+
- 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.
|
|
177
|
+
|
|
145
178
|
Report: "Post-update verification: {N} issues found" or "Post-update verification clean."
|
|
146
179
|
|
|
147
180
|
### Step 6: Cleanup
|
|
148
181
|
|
|
149
|
-
Delete the `.workspace-update/` directory at the launcher — in the worktree flow (Step
|
|
182
|
+
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.
|
|
150
183
|
|
|
151
184
|
### Step 7: Commit
|
|
152
185
|
|
|
153
|
-
Where the commit lands was decided in Step
|
|
186
|
+
Where the commit lands was decided in Step 1.
|
|
154
187
|
|
|
155
188
|
- **No remote:** commit in place on the launcher's default branch — the one sanctioned launcher commit:
|
|
156
189
|
```bash
|
|
157
190
|
git add -A
|
|
158
191
|
git commit -m "chore: update workspace from template v{fromVersion} to v{templateVersion}"
|
|
159
192
|
```
|
|
160
|
-
- **Remote exists:** from the task worktree created in Step
|
|
193
|
+
- **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).
|
|
161
194
|
|
|
162
195
|
Report: "Workspace updated to v{templateVersion}. Restart Claude Code if rules or hooks changed."
|
|
163
196
|
|
|
@@ -168,10 +201,11 @@ After the update is applied, if the sessions directory (`workspace.workSessionsD
|
|
|
168
201
|
## Notes
|
|
169
202
|
|
|
170
203
|
- The CLI (`npx @ulysses-ai/create-workspace --upgrade`) stages the payload. This skill processes it.
|
|
171
|
-
- Never overwrites without asking — `new` files are batched behind one confirmation; `differs` files are asked per file
|
|
204
|
+
- Never overwrites without asking — `new` and `updated` files are batched behind one confirmation; `differs` files are asked per file; `localOnly` files are never asked about (local edits to files the template didn't touch)
|
|
172
205
|
- Preserves local modifications, custom content, existing `workspace.json` keys, and deliberately activated rules
|
|
206
|
+
- The template baseline (`.claude/.template-baseline.json`) is what separates `updated`, `differs`, and `localOnly`: entries hold the payload hash of the last-shipped content (unapplied updates keep the older entry), so a deliberately kept local edit stays visible across updates while an untouched file never prompts
|
|
173
207
|
- 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
|
|
174
208
|
- Can be run multiple times safely (idempotent) — if `.workspace-update/` doesn't exist, it reports no payload and exits
|
|
175
209
|
- Initial setup is handled by `npx @ulysses-ai/create-workspace --init` + `/workspace-init` — this skill is for subsequent updates only
|
|
176
210
|
- The `.sh` to `.mjs` hook migration is a one-time transition for workspaces created before hooks moved to JavaScript
|
|
177
|
-
- The
|
|
211
|
+
- 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
|