@ulysses-ai/create-workspace 0.18.0-beta.0 → 0.20.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/lib/init.mjs +4 -1
- package/lib/payload.mjs +18 -1
- package/lib/payload.test.mjs +55 -0
- package/lib/scaffold.mjs +23 -6
- package/lib/scaffold.test.mjs +59 -0
- package/lib/upgrade.mjs +20 -0
- package/lib/upgrade.test.mjs +119 -0
- package/package.json +3 -3
- package/template/CLAUDE.md.tmpl +3 -0
- package/template/{.claude → _claude}/lib/freshness.mjs +20 -7
- package/template/_claude/lib/registry-check.mjs +172 -0
- package/template/{.claude → _claude}/rules/forge-operations.md +6 -0
- package/template/{.claude → _claude}/rules/memory-guidance.md +4 -0
- package/template/{.claude → _claude}/rules/task-list-mirroring.md +6 -0
- package/template/{.claude → _claude}/scripts/build-workspace-context.mjs +25 -14
- package/template/{.claude → _claude}/scripts/chat-record.mjs +62 -21
- package/template/_claude/scripts/classify-update.mjs +117 -0
- package/template/{.claude → _claude}/scripts/cleanup-work-session.mjs +4 -2
- package/template/{.claude → _claude}/scripts/context-footprint.mjs +139 -30
- package/template/{.claude → _claude}/scripts/forges/github.mjs +2 -1
- package/template/{.claude → _claude}/scripts/forges/interface.mjs +5 -4
- package/template/_claude/scripts/merge-mode.mjs +61 -0
- package/template/{.claude → _claude}/scripts/migrate-canonical-priority.mjs +7 -1
- package/template/_claude/scripts/migrate-claude-md-freshness-include.mjs +55 -0
- package/template/{.claude → _claude}/scripts/migrate-session-layout.mjs +19 -8
- package/template/{.claude → _claude}/scripts/migrate-sessions.mjs +444 -121
- package/template/_claude/scripts/task-pr.mjs +555 -0
- package/template/{.claude → _claude}/scripts/task-worktree.mjs +26 -16
- package/template/{.claude → _claude}/scripts/trackers/github-issues.mjs +11 -0
- package/template/{.claude → _claude}/scripts/trackers/interface.mjs +8 -0
- package/template/{.claude → _claude}/skills/braindump/SKILL.md +1 -0
- package/template/{.claude → _claude}/skills/complete-work/SKILL.md +25 -77
- package/template/{.claude → _claude}/skills/context-placement/SKILL.md +8 -5
- package/template/{.claude → _claude}/skills/goal-driven-work/SKILL.md +1 -1
- package/template/{.claude → _claude}/skills/handoff/SKILL.md +1 -0
- package/template/{.claude → _claude}/skills/maintenance/SKILL.md +49 -17
- package/template/{.claude → _claude}/skills/migrate-sessions/SKILL.md +17 -5
- package/template/{.claude → _claude}/skills/release/SKILL.md +6 -2
- package/template/{.claude → _claude}/skills/start-work/SKILL.md +5 -5
- package/template/{.claude → _claude}/skills/workspace-init/SKILL.md +20 -14
- package/template/_claude/skills/workspace-update/SKILL.md +177 -0
- package/template/_gitignore +3 -0
- package/template/workspace.json.tmpl +1 -1
- package/template/.claude/lib/registry-check.mjs +0 -106
- package/template/.claude/scripts/migrate-claude-md-freshness-include.mjs +0 -30
- package/template/.claude/skills/workspace-update/SKILL.md +0 -134
- /package/template/{.claude → _claude}/agents/aside-researcher.md +0 -0
- /package/template/{.claude → _claude}/agents/implementer.md +0 -0
- /package/template/{.claude → _claude}/agents/researcher.md +0 -0
- /package/template/{.claude → _claude}/agents/reviewer.md +0 -0
- /package/template/{.claude → _claude}/hooks/_utils.mjs +0 -0
- /package/template/{.claude → _claude}/hooks/bash-output-advisory.mjs +0 -0
- /package/template/{.claude → _claude}/hooks/post-compact.mjs +0 -0
- /package/template/{.claude → _claude}/hooks/pre-compact.mjs +0 -0
- /package/template/{.claude → _claude}/hooks/repo-write-detection.mjs +0 -0
- /package/template/{.claude → _claude}/hooks/session-end.mjs +0 -0
- /package/template/{.claude → _claude}/hooks/session-start.mjs +0 -0
- /package/template/{.claude → _claude}/hooks/subagent-start.mjs +0 -0
- /package/template/{.claude → _claude}/hooks/version-freshness-check.mjs +0 -0
- /package/template/{.claude → _claude}/hooks/workspace-update-check.mjs +0 -0
- /package/template/{.claude → _claude}/lib/require-node.mjs +0 -0
- /package/template/{.claude → _claude}/lib/session-frontmatter.mjs +0 -0
- /package/template/{.claude → _claude}/recipes/migrate-from-notion.md +0 -0
- /package/template/{.claude → _claude}/rules/agent-rules.md.skip +0 -0
- /package/template/{.claude → _claude}/rules/cloud-infrastructure.md.skip +0 -0
- /package/template/{.claude → _claude}/rules/coherent-revisions.md +0 -0
- /package/template/{.claude → _claude}/rules/config-review.md.skip +0 -0
- /package/template/{.claude → _claude}/rules/documentation.md.skip +0 -0
- /package/template/{.claude → _claude}/rules/git-conventions.md +0 -0
- /package/template/{.claude → _claude}/rules/goal-driven-work.md +0 -0
- /package/template/{.claude → _claude}/rules/honest-pushback.md +0 -0
- /package/template/{.claude → _claude}/rules/local-dev-environment.md.skip +0 -0
- /package/template/{.claude → _claude}/rules/product-integrity.md.skip +0 -0
- /package/template/{.claude → _claude}/rules/scope-guard.md.skip +0 -0
- /package/template/{.claude → _claude}/rules/superpowers-workflow.md.skip +0 -0
- /package/template/{.claude → _claude}/rules/token-economics.md.skip +0 -0
- /package/template/{.claude → _claude}/rules/work-item-tracking.md +0 -0
- /package/template/{.claude → _claude}/rules/workspace-structure.md +0 -0
- /package/template/{.claude → _claude}/scripts/add-repo-to-session.mjs +0 -0
- /package/template/{.claude → _claude}/scripts/capture-context.mjs +0 -0
- /package/template/{.claude → _claude}/scripts/create-work-session.mjs +0 -0
- /package/template/{.claude → _claude}/scripts/forges/gitlab.mjs +0 -0
- /package/template/{.claude → _claude}/scripts/generate-claude-local.mjs +0 -0
- /package/template/{.claude → _claude}/scripts/migrate-open-work.mjs +0 -0
- /package/template/{.claude → _claude}/scripts/migrate-to-workspace-context.mjs +0 -0
- /package/template/{.claude → _claude}/scripts/sweep-references.mjs +0 -0
- /package/template/{.claude → _claude}/scripts/sync-tasks.mjs +0 -0
- /package/template/{.claude → _claude}/scripts/workspace-diagnostics.mjs +0 -0
- /package/template/{.claude → _claude}/settings.json +0 -0
- /package/template/{.claude → _claude}/skills/aside/SKILL.md +0 -0
- /package/template/{.claude → _claude}/skills/build-docs-site/SKILL.md +0 -0
- /package/template/{.claude → _claude}/skills/build-docs-site/checklists/framing.md +0 -0
- /package/template/{.claude → _claude}/skills/build-docs-site/checklists/pitfalls.md +0 -0
- /package/template/{.claude → _claude}/skills/build-docs-site/checklists/review.md +0 -0
- /package/template/{.claude → _claude}/skills/build-docs-site/scripts/bulk-fill-migration.py +0 -0
- /package/template/{.claude → _claude}/skills/build-docs-site/scripts/forbidden-word-grep.mjs +0 -0
- /package/template/{.claude → _claude}/skills/build-docs-site/scripts/leak-grep.mjs +0 -0
- /package/template/{.claude → _claude}/skills/build-docs-site/templates/custom.css.tmpl +0 -0
- /package/template/{.claude → _claude}/skills/build-docs-site/templates/docusaurus.config.ts.tmpl +0 -0
- /package/template/{.claude → _claude}/skills/build-docs-site/templates/primitives/Arrow.tsx +0 -0
- /package/template/{.claude → _claude}/skills/build-docs-site/templates/primitives/Box.tsx +0 -0
- /package/template/{.claude → _claude}/skills/build-docs-site/templates/primitives/DiagramContainer.tsx +0 -0
- /package/template/{.claude → _claude}/skills/build-docs-site/templates/primitives/Region.tsx +0 -0
- /package/template/{.claude → _claude}/skills/build-docs-site/templates/primitives/SectionTitle.tsx +0 -0
- /package/template/{.claude → _claude}/skills/build-docs-site/templates/primitives/tokens.ts +0 -0
- /package/template/{.claude → _claude}/skills/build-docs-site/templates/sidebars.ts.tmpl +0 -0
- /package/template/{.claude → _claude}/skills/build-docs-site/templates/spec.md.tmpl +0 -0
- /package/template/{.claude → _claude}/skills/pause-work/SKILL.md +0 -0
- /package/template/{.claude → _claude}/skills/promote/SKILL.md +0 -0
- /package/template/{.claude → _claude}/skills/setup-tracker/SKILL.md +0 -0
- /package/template/{.claude → _claude}/skills/sync-work/SKILL.md +0 -0
- /package/template/{.mcp.json → _mcp.json} +0 -0
|
@@ -26,11 +26,11 @@ If `workspace.json` has `"initialized": true` and no `.workspace-update/` payloa
|
|
|
26
26
|
|
|
27
27
|
## Branching
|
|
28
28
|
|
|
29
|
-
Workspace-init creates a branch for all its work:
|
|
29
|
+
Workspace-init creates a branch for all its work. Record the default branch first (`git branch --show-current` before switching — usually `main`), then:
|
|
30
30
|
```bash
|
|
31
31
|
git checkout -b chore/workspace-init
|
|
32
32
|
```
|
|
33
|
-
All commits go on this branch.
|
|
33
|
+
All commits go on this branch. Step 18 merges the branch back into the default branch at the end — init must finish with every commit on the default branch, never stranded on the init branch (an unmerged init branch leaves `workspace.json` without `initialized: true` on the default branch, which blocks later `/workspace-update` runs). The granular per-step history stays on `chore/workspace-init` for review.
|
|
34
34
|
|
|
35
35
|
## Flow
|
|
36
36
|
|
|
@@ -373,9 +373,9 @@ git remote -v
|
|
|
373
373
|
- Detect the org from project repo remotes in workspace.json
|
|
374
374
|
- Ask: "Create workspace repo as `{org}/workspace-{project}`? Or provide a different name/URL."
|
|
375
375
|
- Create via `gh repo create {org}/{name} --private` and add as remote
|
|
376
|
-
- Do NOT push yet —
|
|
376
|
+
- Do NOT push yet — Step 18 merges the init branch and pushes the default branch after the merge
|
|
377
377
|
|
|
378
|
-
### Step 18: Mark initialized
|
|
378
|
+
### Step 18: Mark initialized, merge to the default branch, report
|
|
379
379
|
|
|
380
380
|
Update workspace.json:
|
|
381
381
|
- Set `initialized: true`
|
|
@@ -383,12 +383,20 @@ Update workspace.json:
|
|
|
383
383
|
|
|
384
384
|
**Commit:** `git commit -m "chore: mark workspace as initialized"`
|
|
385
385
|
|
|
386
|
+
**Merge to the default branch.** Init is not finished while its commits live only on `chore/workspace-init` — an unmerged init branch leaves the default branch without `initialized: true`, which blocks later `/workspace-update` runs. Ask: "Merge chore/workspace-init into {default-branch} now? [Y/n]" and on yes:
|
|
387
|
+
```bash
|
|
388
|
+
git checkout {default-branch}
|
|
389
|
+
git merge --squash chore/workspace-init
|
|
390
|
+
git commit -m "chore: workspace initialization"
|
|
391
|
+
```
|
|
392
|
+
If a remote is configured, push the default branch (`git push origin {default-branch}`; use `--force-with-lease` only if the operator explicitly accepts rewritten history after a Step 17 rebase). Keep `chore/workspace-init` — it carries the granular per-step history — unless the user asks for it to be deleted. If the user declines the merge, the final report must tell them exactly how to finish it themselves.
|
|
393
|
+
|
|
386
394
|
**Final report:**
|
|
387
395
|
|
|
388
396
|
```
|
|
389
397
|
"Workspace initialized. Restart Claude Code for all rules and hooks to take effect. Then run /start-work to begin.
|
|
390
398
|
|
|
391
|
-
Branch: chore/workspace-init
|
|
399
|
+
Branch: chore/workspace-init (merged to {default-branch} as "chore: workspace initialization")
|
|
392
400
|
|
|
393
401
|
Summary:
|
|
394
402
|
- {N} repos cloned
|
|
@@ -402,6 +410,7 @@ Summary:
|
|
|
402
410
|
- {V} self-contradictions found and fixed
|
|
403
411
|
- Template version: {version}
|
|
404
412
|
- Remote: {status}
|
|
413
|
+
- All init commits are on {default-branch}
|
|
405
414
|
|
|
406
415
|
Issues encountered:
|
|
407
416
|
- {list every expected behavior that failed}
|
|
@@ -414,15 +423,12 @@ Active work sessions (formalized from existing worktrees):
|
|
|
414
423
|
Items in workspace-scratchpad/unmigrated/:
|
|
415
424
|
- {list each item with a one-line description}
|
|
416
425
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
git merge --squash chore/workspace-init
|
|
424
|
-
git commit -m 'chore: workspace initialization'
|
|
425
|
-
git push origin main
|
|
426
|
+
If the merge was declined, replace the branch line with:
|
|
427
|
+
Not merged yet — finish with:
|
|
428
|
+
git checkout {default-branch}
|
|
429
|
+
git merge --squash chore/workspace-init
|
|
430
|
+
git commit -m 'chore: workspace initialization'
|
|
431
|
+
git push origin {default-branch}
|
|
426
432
|
|
|
427
433
|
This session is done. Start a fresh Claude Code session and run /start-work to begin."
|
|
428
434
|
```
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
---
|
|
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.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Workspace Update
|
|
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.
|
|
9
|
+
|
|
10
|
+
## Prerequisites
|
|
11
|
+
|
|
12
|
+
- `workspace.json` must have `initialized: true`
|
|
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."
|
|
18
|
+
- `.workspace-update/` payload directory must exist (staged by `npx @ulysses-ai/create-workspace --upgrade`)
|
|
19
|
+
- If no `.workspace-update/` payload exists, report: "No update payload found. Run `npx @ulysses-ai/create-workspace --upgrade` to stage the template."
|
|
20
|
+
- Read `.workspace-update/.manifest.json` for `fromVersion`, `templateVersion` (the target version), and `action`
|
|
21
|
+
- If `action` is `"init"`, report: "This payload is for initial setup. Run /workspace-init instead."
|
|
22
|
+
|
|
23
|
+
## Flow
|
|
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
|
|
30
|
+
|
|
31
|
+
Check the workspace repo for a remote (`git remote`). This decides where every later step works:
|
|
32
|
+
|
|
33
|
+
- **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/`.
|
|
34
|
+
- **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:
|
|
35
|
+
```bash
|
|
36
|
+
node .claude/scripts/task-worktree.mjs --root . --create --repo . --branch chore/template-update-{version}
|
|
37
|
+
```
|
|
38
|
+
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.
|
|
39
|
+
|
|
40
|
+
In the commands below, `{payload}` is `.workspace-update` in the no-remote flow and `{launcher}/.workspace-update` in the worktree flow.
|
|
41
|
+
|
|
42
|
+
### Step 2: Classify the payload
|
|
43
|
+
|
|
44
|
+
Run the classifier — it compares every verbatim-installed payload file (`.claude/**`, `.mcp.json`, `.claudeignore`) against the workspace by content:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
node {payload}/.claude/scripts/classify-update.mjs --root . --payload {payload}
|
|
48
|
+
```
|
|
49
|
+
|
|
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:
|
|
51
|
+
|
|
52
|
+
- `new` — no installed counterpart; safe to batch-apply (Step 3) behind one confirmation
|
|
53
|
+
- `identical` — installed file already equals the payload; skip silently
|
|
54
|
+
- `differs` — installed file was locally modified; needs a per-file decision
|
|
55
|
+
|
|
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.
|
|
57
|
+
|
|
58
|
+
Also list **removed files**: files present in the local `.claude/{component}/` with no counterpart in `{payload}/.claude/{component}/`.
|
|
59
|
+
|
|
60
|
+
Report with version info from the manifest:
|
|
61
|
+
```
|
|
62
|
+
"Template update: v{fromVersion} → v{templateVersion}. {N} new files, {M} locally modified, {R} removed files, {K} unchanged."
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
If `new`, `differs`, and the removed list are all empty, report: "Workspace is up to date (template v{templateVersion}). No changes needed."
|
|
66
|
+
|
|
67
|
+
### Step 2b: Historical .gitignore safety check
|
|
68
|
+
|
|
69
|
+
Workspaces created before v0.5.1 are vulnerable to a destructive symlink bug in the old layout. The v0.8.0 layout removes the symlink entirely, so new workspaces are not vulnerable — but a workspace being upgraded from a pre-v0.8.0 version may still have the bad `.gitignore` pattern left over.
|
|
70
|
+
|
|
71
|
+
Check the workspace `.gitignore` for the `repos/` trailing-slash pattern:
|
|
72
|
+
```bash
|
|
73
|
+
grep -E '^repos/$' .gitignore
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
If found, rewrite it in place to `repos` (no trailing slash). Also check for any tracked `repos` symlink that was already committed:
|
|
77
|
+
```bash
|
|
78
|
+
git ls-files | grep -E '^repos$'
|
|
79
|
+
```
|
|
80
|
+
If found, untrack it: `git rm --cached repos`.
|
|
81
|
+
|
|
82
|
+
Commit the fix **before** applying other template updates. This runs ahead of Step 3 because applying other updates while the bug is still present could itself trigger the destruction on workspaces that still have the old layout.
|
|
83
|
+
|
|
84
|
+
### Step 3: Selective update
|
|
85
|
+
|
|
86
|
+
Batch the safe case, ask on the rest:
|
|
87
|
+
|
|
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.
|
|
91
|
+
- **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
|
+
|
|
93
|
+
Also handle these non-component files from the payload:
|
|
94
|
+
|
|
95
|
+
- **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
|
+
- **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.
|
|
99
|
+
- **.gitignore:** Merge new entries from the payload's `_gitignore` into the existing `.gitignore` — do not remove user-added lines.
|
|
100
|
+
|
|
101
|
+
### Step 4: Update version
|
|
102
|
+
|
|
103
|
+
Read `templateVersion` from `.workspace-update/.manifest.json` and update `templateVersion` in `workspace.json` to match.
|
|
104
|
+
|
|
105
|
+
### Step 4a: Run idempotent migrators
|
|
106
|
+
|
|
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.
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
node {payload}/.claude/scripts/migrate-claude-md-freshness-include.mjs --root .
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Output is JSON: `{"action":"appended"|"unchanged"|"skipped"}`.
|
|
114
|
+
|
|
115
|
+
- `appended` — the workspace's `CLAUDE.md` got the `@local-only-template-freshness.md` include line added at the end.
|
|
116
|
+
- `unchanged` — the line was already present.
|
|
117
|
+
- `skipped` — no `CLAUDE.md` exists at the workspace root (rare; surface to the user).
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
node {payload}/.claude/scripts/migrate-canonical-priority.mjs --root .
|
|
121
|
+
```
|
|
122
|
+
|
|
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.
|
|
124
|
+
|
|
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/`.
|
|
126
|
+
|
|
127
|
+
Add other migrators here as the template ships them.
|
|
128
|
+
|
|
129
|
+
### Step 5: Post-update verification
|
|
130
|
+
|
|
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
|
|
135
|
+
|
|
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:
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
node .claude/scripts/build-workspace-context.mjs --write --root .
|
|
140
|
+
node .claude/scripts/build-workspace-context.mjs --check --root .
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
`--check` must exit clean. If it reports drift, re-run `--write` and check again before continuing.
|
|
144
|
+
|
|
145
|
+
Report: "Post-update verification: {N} issues found" or "Post-update verification clean."
|
|
146
|
+
|
|
147
|
+
### Step 6: Cleanup
|
|
148
|
+
|
|
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.
|
|
150
|
+
|
|
151
|
+
### Step 7: Commit
|
|
152
|
+
|
|
153
|
+
Where the commit lands was decided in Step 1b.
|
|
154
|
+
|
|
155
|
+
- **No remote:** commit in place on the launcher's default branch — the one sanctioned launcher commit:
|
|
156
|
+
```bash
|
|
157
|
+
git add -A
|
|
158
|
+
git commit -m "chore: update workspace from template v{fromVersion} to v{templateVersion}"
|
|
159
|
+
```
|
|
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).
|
|
161
|
+
|
|
162
|
+
Report: "Workspace updated to v{templateVersion}. Restart Claude Code if rules or hooks changed."
|
|
163
|
+
|
|
164
|
+
### Step 8: Session-model migration nudge
|
|
165
|
+
|
|
166
|
+
After the update is applied, if the sessions directory (`workspace.workSessionsDir`, default `work-sessions/`) has entries and `workspace.sessionModel` is not `"task"`, append one line to the report: "This workspace still has {N} session(s) under the session model — `/migrate-sessions` can inventory and drain them and switch to the task model whenever you're ready." Suggest only; the operator decides whether and when.
|
|
167
|
+
|
|
168
|
+
## Notes
|
|
169
|
+
|
|
170
|
+
- 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
|
|
172
|
+
- Preserves local modifications, custom content, existing `workspace.json` keys, and deliberately activated rules
|
|
173
|
+
- 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
|
+
- Can be run multiple times safely (idempotent) — if `.workspace-update/` doesn't exist, it reports no payload and exits
|
|
175
|
+
- Initial setup is handled by `npx @ulysses-ai/create-workspace --init` + `/workspace-init` — this skill is for subsequent updates only
|
|
176
|
+
- 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
|
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
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"scratchpadDir": "workspace-scratchpad",
|
|
7
7
|
"workSessionsDir": "work-sessions",
|
|
8
8
|
"workspaceContextDir": "workspace-context",
|
|
9
|
-
"
|
|
9
|
+
"alwaysLoadedBudgetBytes": 65536,
|
|
10
10
|
"subagentContextMaxBytes": 32768,
|
|
11
11
|
"subagentInlineMaxBytes": 8192,
|
|
12
12
|
"greeting": "Welcome back to {{project-name}}.",
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
import './require-node.mjs';
|
|
2
|
-
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
|
|
3
|
-
import { dirname } from 'path';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* SemVer 2.0 comparison limited to the formats this scaffolder publishes:
|
|
7
|
-
* `x.y.z` and `x.y.z-prerelease.N`. Returns -1, 0, or 1.
|
|
8
|
-
*
|
|
9
|
-
* Rules:
|
|
10
|
-
* - Compare major, minor, patch numerically.
|
|
11
|
-
* - A pre-release version is older than the same x.y.z without a tag.
|
|
12
|
-
* - Pre-release identifiers compare per-identifier; numeric identifiers
|
|
13
|
-
* compare numerically (so `beta.10 > beta.2`), non-numeric lexically.
|
|
14
|
-
*/
|
|
15
|
-
export function compareVersions(a, b) {
|
|
16
|
-
if (a === b) return 0;
|
|
17
|
-
const [aBase, aPre] = a.split('-', 2);
|
|
18
|
-
const [bBase, bPre] = b.split('-', 2);
|
|
19
|
-
const aParts = aBase.split('.').map(Number);
|
|
20
|
-
const bParts = bBase.split('.').map(Number);
|
|
21
|
-
for (let i = 0; i < 3; i++) {
|
|
22
|
-
if ((aParts[i] || 0) < (bParts[i] || 0)) return -1;
|
|
23
|
-
if ((aParts[i] || 0) > (bParts[i] || 0)) return 1;
|
|
24
|
-
}
|
|
25
|
-
if (!aPre && !bPre) return 0;
|
|
26
|
-
if (!aPre && bPre) return 1;
|
|
27
|
-
if (aPre && !bPre) return -1;
|
|
28
|
-
const aIds = aPre.split('.');
|
|
29
|
-
const bIds = bPre.split('.');
|
|
30
|
-
const len = Math.max(aIds.length, bIds.length);
|
|
31
|
-
for (let i = 0; i < len; i++) {
|
|
32
|
-
const ai = aIds[i];
|
|
33
|
-
const bi = bIds[i];
|
|
34
|
-
if (ai === undefined) return -1;
|
|
35
|
-
if (bi === undefined) return 1;
|
|
36
|
-
const aNum = /^\d+$/.test(ai);
|
|
37
|
-
const bNum = /^\d+$/.test(bi);
|
|
38
|
-
if (aNum && bNum) {
|
|
39
|
-
const an = Number(ai), bn = Number(bi);
|
|
40
|
-
if (an < bn) return -1;
|
|
41
|
-
if (an > bn) return 1;
|
|
42
|
-
} else if (aNum && !bNum) {
|
|
43
|
-
return -1;
|
|
44
|
-
} else if (!aNum && bNum) {
|
|
45
|
-
return 1;
|
|
46
|
-
} else {
|
|
47
|
-
if (ai < bi) return -1;
|
|
48
|
-
if (ai > bi) return 1;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
return 0;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const REGISTRY_URL = 'https://registry.npmjs.org/@ulysses-ai/create-workspace/latest';
|
|
55
|
-
const DEFAULT_TIMEOUT_MS = 3000;
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Fetch the latest version of the scaffolder from the npm registry.
|
|
59
|
-
* Returns { version, error } — exactly one of them is non-null.
|
|
60
|
-
*
|
|
61
|
-
* Caller injects fetchFn for testing. Default uses global fetch (Node 18+).
|
|
62
|
-
*/
|
|
63
|
-
export async function getLatestVersion({ fetchFn = fetch, timeoutMs = DEFAULT_TIMEOUT_MS } = {}) {
|
|
64
|
-
const controller = new AbortController();
|
|
65
|
-
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
66
|
-
try {
|
|
67
|
-
const res = await fetchFn(REGISTRY_URL, { signal: controller.signal });
|
|
68
|
-
if (!res.ok) {
|
|
69
|
-
return { version: null, error: `registry returned ${res.status} ${res.statusText || ''}`.trim() };
|
|
70
|
-
}
|
|
71
|
-
const body = await res.json();
|
|
72
|
-
if (typeof body?.version !== 'string') {
|
|
73
|
-
return { version: null, error: 'registry response missing version field' };
|
|
74
|
-
}
|
|
75
|
-
return { version: body.version, error: null };
|
|
76
|
-
} catch (err) {
|
|
77
|
-
return { version: null, error: err?.message || String(err) };
|
|
78
|
-
} finally {
|
|
79
|
-
clearTimeout(timer);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Read the version cache file. Returns the parsed object if it has a string
|
|
85
|
-
* `latestVersion` field; otherwise null. Treats missing file, malformed JSON,
|
|
86
|
-
* and shape mismatches all as "no cache".
|
|
87
|
-
*/
|
|
88
|
-
export function readCache(path) {
|
|
89
|
-
if (!existsSync(path)) return null;
|
|
90
|
-
try {
|
|
91
|
-
const data = JSON.parse(readFileSync(path, 'utf-8'));
|
|
92
|
-
if (typeof data?.latestVersion !== 'string') return null;
|
|
93
|
-
return data;
|
|
94
|
-
} catch {
|
|
95
|
-
return null;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Write the version cache file, creating parent directories as needed.
|
|
101
|
-
*/
|
|
102
|
-
export function writeCache(path, data) {
|
|
103
|
-
const dir = dirname(path);
|
|
104
|
-
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
105
|
-
writeFileSync(path, JSON.stringify(data, null, 2) + '\n');
|
|
106
|
-
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// Idempotent migrator: ensures CLAUDE.md includes @local-only-template-freshness.md.
|
|
3
|
-
// Appends one line at end if missing. Preserves the rest of the file byte-for-byte.
|
|
4
|
-
//
|
|
5
|
-
// Run standalone: node .claude/scripts/migrate-claude-md-freshness-include.mjs
|
|
6
|
-
// Or import { runMigration } and call programmatically.
|
|
7
|
-
import { existsSync, readFileSync, writeFileSync } from 'fs';
|
|
8
|
-
import { join, dirname, resolve } from 'path';
|
|
9
|
-
import { fileURLToPath } from 'url';
|
|
10
|
-
|
|
11
|
-
const INCLUDE_LINE = '@local-only-template-freshness.md';
|
|
12
|
-
|
|
13
|
-
export function runMigration({ workspaceRoot }) {
|
|
14
|
-
const path = join(workspaceRoot, 'CLAUDE.md');
|
|
15
|
-
if (!existsSync(path)) return { action: 'skipped', reason: 'no-claude-md' };
|
|
16
|
-
const before = readFileSync(path, 'utf-8');
|
|
17
|
-
if (before.includes(INCLUDE_LINE)) return { action: 'unchanged' };
|
|
18
|
-
const after = before.endsWith('\n') ? before + INCLUDE_LINE + '\n' : before + '\n' + INCLUDE_LINE + '\n';
|
|
19
|
-
writeFileSync(path, after);
|
|
20
|
-
return { action: 'appended' };
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// CLI entry point — workspace root is two levels up from this file
|
|
24
|
-
// (.claude/scripts/migrate-... → workspace root).
|
|
25
|
-
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
26
|
-
const here = dirname(fileURLToPath(import.meta.url));
|
|
27
|
-
const root = resolve(here, '..', '..');
|
|
28
|
-
const result = runMigration({ workspaceRoot: root });
|
|
29
|
-
console.log(JSON.stringify(result));
|
|
30
|
-
}
|
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
---
|
|
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.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Workspace Update
|
|
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.
|
|
9
|
-
|
|
10
|
-
## Prerequisites
|
|
11
|
-
|
|
12
|
-
- `workspace.json` must have `initialized: true`
|
|
13
|
-
- If not initialized, report: "Workspace not initialized. Run /workspace-init first."
|
|
14
|
-
- `.workspace-update/` payload directory must exist (staged by `npx @ulysses-ai/create-workspace --upgrade`)
|
|
15
|
-
- 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`, `toVersion`, and `action`
|
|
17
|
-
- If `action` is `"init"`, report: "This payload is for initial setup. Run /workspace-init instead."
|
|
18
|
-
|
|
19
|
-
## Flow
|
|
20
|
-
|
|
21
|
-
### Step 1: Pre-update health check
|
|
22
|
-
|
|
23
|
-
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.
|
|
24
|
-
|
|
25
|
-
### Step 2: Compare current vs payload
|
|
26
|
-
|
|
27
|
-
For each component directory in `.workspace-update/.claude/` (skills, hooks, agents, rules, recipes), compare files against the corresponding `.claude/{component}/` directory locally:
|
|
28
|
-
|
|
29
|
-
- **New files:** present in `.workspace-update/.claude/{component}/` but not in `.claude/{component}/`
|
|
30
|
-
- **Updated files:** present in both but contents differ
|
|
31
|
-
- **Unchanged files:** present in both with identical contents
|
|
32
|
-
- **Removed files:** present in `.claude/{component}/` locally but not in `.workspace-update/.claude/{component}/`
|
|
33
|
-
|
|
34
|
-
Report with version info from the manifest:
|
|
35
|
-
```
|
|
36
|
-
"Template update: v{fromVersion} → v{toVersion}. {N} new files, {M} updated files, {R} removed files, {K} unchanged."
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
If everything is unchanged and there are no new or removed files, report: "Workspace is up to date (template v{toVersion}). No changes needed."
|
|
40
|
-
|
|
41
|
-
### Step 2b: Historical .gitignore safety check
|
|
42
|
-
|
|
43
|
-
Workspaces created before v0.5.1 are vulnerable to a destructive symlink bug in the old layout. The v0.8.0 layout removes the symlink entirely, so new workspaces are not vulnerable — but a workspace being upgraded from a pre-v0.8.0 version may still have the bad `.gitignore` pattern left over.
|
|
44
|
-
|
|
45
|
-
Check the workspace `.gitignore` for the `repos/` trailing-slash pattern:
|
|
46
|
-
```bash
|
|
47
|
-
grep -E '^repos/$' .gitignore
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
If found, rewrite it in place to `repos` (no trailing slash). Also check for any tracked `repos` symlink that was already committed:
|
|
51
|
-
```bash
|
|
52
|
-
git ls-files | grep -E '^repos$'
|
|
53
|
-
```
|
|
54
|
-
If found, untrack it: `git rm --cached repos`.
|
|
55
|
-
|
|
56
|
-
Commit the fix **before** applying other template updates. This runs ahead of Step 3 because applying other updates while the bug is still present could itself trigger the destruction on workspaces that still have the old layout.
|
|
57
|
-
|
|
58
|
-
### Step 3: Selective update
|
|
59
|
-
|
|
60
|
-
For each change, ask before applying:
|
|
61
|
-
|
|
62
|
-
- **New file:** "Add {file}? [Y/n]"
|
|
63
|
-
- **Updated file (no local mods):** "Update {file} to latest template? [Y/n]"
|
|
64
|
-
- **Updated file (locally modified):** "Template updated {file} but you have local changes. Show diff? [y/N]" — let user decide
|
|
65
|
-
- **Removed in template:** "Template removed {file}. Delete locally? [y/N]" — conservative default
|
|
66
|
-
- **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
|
-
|
|
68
|
-
Also handle these non-component files from the payload:
|
|
69
|
-
|
|
70
|
-
- **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
|
-
- **CLAUDE.md:** If `.workspace-update/CLAUDE.md.tmpl` exists, regenerate `CLAUDE.md` from the template. Preserve any user-added sections not present in the template.
|
|
72
|
-
- **.gitignore:** Merge new entries from the payload into the existing `.gitignore` — do not remove user-added lines.
|
|
73
|
-
|
|
74
|
-
### Step 4: Update version
|
|
75
|
-
|
|
76
|
-
Read `toVersion` from `.workspace-update/.manifest.json` and update `templateVersion` in `workspace.json` to match.
|
|
77
|
-
|
|
78
|
-
### Step 4a: Run idempotent migrators
|
|
79
|
-
|
|
80
|
-
The payload may include migrator scripts at `.workspace-update/.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.
|
|
81
|
-
|
|
82
|
-
```bash
|
|
83
|
-
node .workspace-update/.claude/scripts/migrate-claude-md-freshness-include.mjs
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
Output is JSON: `{"action":"appended"|"unchanged"|"skipped"}`.
|
|
87
|
-
|
|
88
|
-
- `appended` — the workspace's `CLAUDE.md` got the `@local-only-template-freshness.md` include line added at the end.
|
|
89
|
-
- `unchanged` — the line was already present.
|
|
90
|
-
- `skipped` — no `CLAUDE.md` exists at the workspace root (rare; surface to the user).
|
|
91
|
-
|
|
92
|
-
```bash
|
|
93
|
-
node .workspace-update/.claude/scripts/migrate-canonical-priority.mjs --root .
|
|
94
|
-
```
|
|
95
|
-
|
|
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. Idempotent — safe to re-run on already-migrated workspaces.
|
|
97
|
-
|
|
98
|
-
Add other migrators here as the template ships them.
|
|
99
|
-
|
|
100
|
-
### Step 5: Post-update verification
|
|
101
|
-
|
|
102
|
-
Run `/maintenance audit` again to verify the update didn't introduce:
|
|
103
|
-
- Broken references (new skills not in CLAUDE.md, removed rules still referenced)
|
|
104
|
-
- Contradictions between updated rules and existing shared context
|
|
105
|
-
- Structural mismatches
|
|
106
|
-
|
|
107
|
-
Report: "Post-update verification: {N} issues found" or "Post-update verification clean."
|
|
108
|
-
|
|
109
|
-
### Step 6: Cleanup
|
|
110
|
-
|
|
111
|
-
Delete the `.workspace-update/` directory entirely. The payload has been fully processed and is no longer needed.
|
|
112
|
-
|
|
113
|
-
### Step 7: Commit
|
|
114
|
-
|
|
115
|
-
```bash
|
|
116
|
-
git add -A
|
|
117
|
-
git commit -m "chore: update workspace from template v{fromVersion} to v{toVersion}"
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
Report: "Workspace updated to v{toVersion}. Restart Claude Code if rules or hooks changed."
|
|
121
|
-
|
|
122
|
-
### Step 8: Session-model migration nudge
|
|
123
|
-
|
|
124
|
-
After the update is applied, if the sessions directory (`workspace.workSessionsDir`, default `work-sessions/`) has entries and `workspace.sessionModel` is not `"task"`, append one line to the report: "This workspace still has {N} session(s) under the session model — `/migrate-sessions` can inventory and drain them and switch to the task model whenever you're ready." Suggest only; the operator decides whether and when.
|
|
125
|
-
|
|
126
|
-
## Notes
|
|
127
|
-
|
|
128
|
-
- 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 custom content
|
|
131
|
-
- Can be run multiple times safely (idempotent) — if `.workspace-update/` doesn't exist, it reports no payload and exits
|
|
132
|
-
- Initial setup is handled by `npx @ulysses-ai/create-workspace --init` + `/workspace-init` — this skill is for subsequent updates only
|
|
133
|
-
- The `.sh` to `.mjs` hook migration is a one-time transition for workspaces created before hooks moved to JavaScript
|
|
134
|
-
- The maintenance audits are read-only and non-blocking — they surface issues but don't prevent the update
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|