@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.
@@ -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. Runs maintenance audit before and after.
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. Runs a maintenance audit before updating and verifies integrity after.
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: Pre-update health check
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 by content:
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 with three lists:
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
- - `differs` — installed file was locally modified; needs a per-file decision
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
- 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.
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
- Also list **removed files**: files present in the local `.claude/{component}/` with no counterpart in `{payload}/.claude/{component}/`.
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 the removed list are all empty, report: "Workspace is up to date (template v{templateVersion}). No changes needed."
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 case, ask on the rest:
90
+ Batch the safe cases, ask on the rest:
87
91
 
88
- - **New files (`new`):** present the list once — "Apply these {N} new files? [Y/n]" — and install them all on confirmation. No per-file prompting.
89
- - **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.
90
- - **Removed in template:** "Template removed {file}. Delete locally? [y/N]" — conservative default.
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
- - **Rules renamed to `.skip`:** For each active `.claude/rules/{name}.md` whose template counterpart now ships as `{name}.md.skip`, keep the active file — it was deliberately activated — and tell the user that's what happened.
98
- - **CLAUDE.md:** If `{payload}/CLAUDE.md.tmpl` exists, regenerate `CLAUDE.md` from the template. Preserve any user-added sections not present in the template.
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
- The payload may include migrator scripts at `{payload}/.claude/scripts/migrate-*.mjs` that bring older workspaces forward in shape. They are idempotent — safe to re-run on already-migrated workspaces. Run each one in document order and surface its action in the upgrade summary.
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. Idempotent.
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
- 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/`.
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
- Add other migrators here as the template ships them.
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
- ### Step 5: Post-update verification
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
- Run `/maintenance audit` again to verify the update didn't introduce:
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
- Then 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:
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 1b) that happens after the merge and pull in Step 7. The payload has been fully processed and is no longer needed.
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 1b.
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 1b, 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).
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 maintenance audits are read-only and non-blocking — they surface issues but don't prevent the update
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
@@ -10,7 +10,6 @@
10
10
  "subagentContextMaxBytes": 32768,
11
11
  "subagentInlineMaxBytes": 8192,
12
12
  "greeting": "Welcome back to {{project-name}}.",
13
- "releaseMode": "per-repo",
14
13
  "sessionModel": "session",
15
14
  "tracker": null,
16
15
  "forge": { "type": "github" }