contextdevkit 1.8.0 → 1.8.1
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/CHANGELOG.md +12 -0
- package/install.mjs +13 -0
- package/package.json +3 -3
- package/tools/install/cli.mjs +8 -1
- package/tools/install/migrate.mjs +162 -0
- package/tools/integration-test-migrate.mjs +151 -0
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,18 @@ this project follows [Semantic Versioning](https://semver.org/).
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
9
|
### Added
|
|
10
|
+
- **Legacy-install migration (rename follow-through).** `install.mjs` now carries
|
|
11
|
+
an old `vibekit/` install forward to `contextkit/` automatically on `npx
|
|
12
|
+
contextdevkit --update` (and via an explicit `node install.mjs --migrate
|
|
13
|
+
[--dry-run]`). New `tools/install/migrate.mjs`: atomically MOVES the folder
|
|
14
|
+
(preserving memory/ADRs, config + level, pipeline tasks, `.env`), rewrites the
|
|
15
|
+
rename tokens in `settings.json` (killing the duplicate-hook trap),
|
|
16
|
+
`.gitignore`, `.gitattributes`, git-hook wrappers, `contextkit/.env`, and
|
|
17
|
+
`CLAUDE.md` (the last two backed up to `*.bak`), and deletes the stale
|
|
18
|
+
`/vibe-*` + `setupvibedevkit` command files. Refuses (no-op + warning) when
|
|
19
|
+
BOTH folders exist; idempotent; never throws into the installer (rule 2). New
|
|
20
|
+
`tools/integration-test-migrate.mjs` (25 asserts) wired into `test` +
|
|
21
|
+
`prepublishOnly`.
|
|
10
22
|
- **agent-forge squad — Fase 6: declarative pipeline DSL + dry-run engine
|
|
11
23
|
(ADR-0015 Part A).** The forge's orchestration is now a diffable, simulate-
|
|
12
24
|
impact-mappable plan. New `templates/contextkit/squads/agent-forge/pipeline.yaml`
|
package/install.mjs
CHANGED
|
@@ -29,6 +29,7 @@ import { ensureDir, read, writeIfMissing, overwrite, copyTree, copyTreeIfMissing
|
|
|
29
29
|
import { detectStack, requireBasename, looksGreenfield } from './tools/install/project.mjs';
|
|
30
30
|
import { installGitHooks, patchGitignore, patchGitattributes } from './tools/install/git.mjs';
|
|
31
31
|
import { uninstall } from './tools/install/uninstall.mjs';
|
|
32
|
+
import { migrateLegacy } from './tools/install/migrate.mjs';
|
|
32
33
|
import { isValidLevel } from './templates/contextkit/runtime/config/levels.mjs';
|
|
33
34
|
import { parseArgs, HELP, prompt, LEVEL_LABELS } from './tools/install/cli.mjs';
|
|
34
35
|
|
|
@@ -63,6 +64,18 @@ async function main() {
|
|
|
63
64
|
return;
|
|
64
65
|
}
|
|
65
66
|
|
|
67
|
+
// Standalone migration: carry a legacy vibekit/ install forward, then stop.
|
|
68
|
+
if (args.migrate) {
|
|
69
|
+
const { report } = await migrateLegacy(target, { dryRun: args.dryRun });
|
|
70
|
+
console.log(report.length ? '\n' + report.join('\n') + '\n' : '\nℹ️ no legacy vibekit/ install found — nothing to migrate.\n');
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Auto-migration: before ANYTHING reads contextkit/ (config, settings), carry a
|
|
75
|
+
// legacy vibekit/ install forward so `npx contextdevkit --update` just works.
|
|
76
|
+
const migration = await migrateLegacy(target, { dryRun: false });
|
|
77
|
+
if (migration.report.length) console.log('\n' + migration.report.join('\n') + '\n');
|
|
78
|
+
|
|
66
79
|
const interactive = !args.yes && process.stdout.isTTY;
|
|
67
80
|
let level = Number.isInteger(args.level) ? args.level : undefined;
|
|
68
81
|
let name = args.name;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "contextdevkit",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.1",
|
|
4
4
|
"description": "Portable, level-based AI-assisted development platform for Claude Code — hooks, slash commands, sub-agents, and durable project memory for any stack.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
"scripts": {
|
|
10
10
|
"install:here": "node install.mjs --target .",
|
|
11
11
|
"check": "node tools/selfcheck.mjs",
|
|
12
|
-
"test": "node tools/selfcheck.mjs && node tools/integration-test.mjs && node tools/integration-test-tooling.mjs && node tools/integration-test-tooling-pipeline.mjs && node tools/integration-test-tooling-agent-forge.mjs && node tools/integration-test-guards.mjs && node tools/integration-test-compozy.mjs",
|
|
13
|
-
"prepublishOnly": "node tools/selfcheck.mjs && node tools/integration-test.mjs && node tools/integration-test-tooling.mjs && node tools/integration-test-tooling-pipeline.mjs && node tools/integration-test-tooling-agent-forge.mjs && node tools/integration-test-guards.mjs && node tools/integration-test-compozy.mjs"
|
|
12
|
+
"test": "node tools/selfcheck.mjs && node tools/integration-test.mjs && node tools/integration-test-tooling.mjs && node tools/integration-test-tooling-pipeline.mjs && node tools/integration-test-tooling-agent-forge.mjs && node tools/integration-test-guards.mjs && node tools/integration-test-compozy.mjs && node tools/integration-test-migrate.mjs",
|
|
13
|
+
"prepublishOnly": "node tools/selfcheck.mjs && node tools/integration-test.mjs && node tools/integration-test-tooling.mjs && node tools/integration-test-tooling-pipeline.mjs && node tools/integration-test-tooling-agent-forge.mjs && node tools/integration-test-guards.mjs && node tools/integration-test-compozy.mjs && node tools/integration-test-migrate.mjs"
|
|
14
14
|
},
|
|
15
15
|
"files": [
|
|
16
16
|
"install.mjs",
|
package/tools/install/cli.mjs
CHANGED
|
@@ -5,11 +5,13 @@
|
|
|
5
5
|
export { LEVEL_LABELS } from '../../templates/contextkit/runtime/config/levels.mjs';
|
|
6
6
|
|
|
7
7
|
export function parseArgs(argv) {
|
|
8
|
-
const args = { yes: false, rewire: false, force: false, uninstall: false, help: false, version: false, purge: false, update: false };
|
|
8
|
+
const args = { yes: false, rewire: false, force: false, uninstall: false, help: false, version: false, purge: false, update: false, migrate: false, dryRun: false };
|
|
9
9
|
for (let i = 0; i < argv.length; i++) {
|
|
10
10
|
const a = argv[i];
|
|
11
11
|
if (a === '--yes' || a === '-y') args.yes = true;
|
|
12
12
|
else if (a === '--update') { args.update = true; args.yes = true; }
|
|
13
|
+
else if (a === '--migrate') args.migrate = true;
|
|
14
|
+
else if (a === '--dry-run') args.dryRun = true;
|
|
13
15
|
else if (a === '--rewire') args.rewire = true;
|
|
14
16
|
else if (a === '--force') args.force = true;
|
|
15
17
|
else if (a === '--uninstall') args.uninstall = true;
|
|
@@ -33,6 +35,8 @@ Usage:
|
|
|
33
35
|
[--mode greenfield|existing] [--yes] [--force]
|
|
34
36
|
node install.mjs --update safe update: refresh engine + commands,
|
|
35
37
|
keep your level/config/memory/CLAUDE.md
|
|
38
|
+
node install.mjs --migrate [--dry-run] carry a legacy vibekit/ install forward
|
|
39
|
+
to contextkit/ (preview with --dry-run)
|
|
36
40
|
node install.mjs --rewire --level <1-7> only recompose .claude/settings.json
|
|
37
41
|
node install.mjs --uninstall [--purge] unwire hooks (--purge also removes engine)
|
|
38
42
|
node install.mjs --help | --version
|
|
@@ -47,6 +51,9 @@ Flags:
|
|
|
47
51
|
--force overwrite CLAUDE.md / memory seeds if they exist
|
|
48
52
|
--update safe update: refresh engine/commands/agents + re-wire hooks for
|
|
49
53
|
the CURRENT level; never touches CLAUDE.md, config, or memory
|
|
54
|
+
--migrate carry a legacy vibekit/ install forward to contextkit/ (moves the
|
|
55
|
+
folder, preserves memory/config, rewires settings/CLAUDE.md/hooks)
|
|
56
|
+
--dry-run with --migrate: report what would change without writing
|
|
50
57
|
--rewire only recompose settings.json for the given --level
|
|
51
58
|
--uninstall remove ContextDevKit hook wiring + git hooks (keeps memory)
|
|
52
59
|
--purge with --uninstall, also delete contextkit/ engine + commands/agents
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Legacy-install migration: VibeDevKit (`vibekit/`) → ContextDevKit (`contextkit/`).
|
|
3
|
+
*
|
|
4
|
+
* Existing users installed the old `vibedevkit` package, which laid down a
|
|
5
|
+
* `vibekit/` platform folder, `/vibe-*` slash commands, `vibekit/...` hook
|
|
6
|
+
* wiring and `VibeDevKit` / `VIBE_*` references. After the rename, running
|
|
7
|
+
* `npx contextdevkit --update` must carry that install FORWARD — without data
|
|
8
|
+
* loss and without leaving two installs side by side.
|
|
9
|
+
*
|
|
10
|
+
* Strategy (idempotent, refuse-on-ambiguity — constitution rule 8):
|
|
11
|
+
* 1. detect a legacy `vibekit/` with no `contextkit/`;
|
|
12
|
+
* 2. MOVE the folder (atomic rename → preserves memory / config / pipeline / .env);
|
|
13
|
+
* 3. rewrite the rename tokens in the control files (settings.json, .gitignore,
|
|
14
|
+
* .gitattributes, git hooks, contextkit/.env, CLAUDE.md — the last two backed
|
|
15
|
+
* up to `*.bak` first, as they hold user content);
|
|
16
|
+
* 4. delete the stale `/vibe-*` + `setupvibedevkit` command files.
|
|
17
|
+
* The normal installer flow then refreshes the engine into `contextkit/`.
|
|
18
|
+
*
|
|
19
|
+
* Zero third-party deps (runs via `npx` on a bare machine). Rule 2: it never
|
|
20
|
+
* throws into the installer — all I/O is defensive; a failure degrades to a
|
|
21
|
+
* warning and leaves the project untouched.
|
|
22
|
+
*/
|
|
23
|
+
import { rename, cp, rm, readFile, writeFile } from 'node:fs/promises';
|
|
24
|
+
import { existsSync } from 'node:fs';
|
|
25
|
+
import { join } from 'node:path';
|
|
26
|
+
|
|
27
|
+
const LEGACY_DIR = 'vibekit';
|
|
28
|
+
const NEW_DIR = 'contextkit';
|
|
29
|
+
|
|
30
|
+
// Order matters: longest / most-specific tokens first so no rule eats another.
|
|
31
|
+
const TOKENS = [
|
|
32
|
+
['VibeDevKit', 'ContextDevKit'],
|
|
33
|
+
['VIBEDEVKIT', 'CONTEXTDEVKIT'],
|
|
34
|
+
['vibedevkit', 'contextdevkit'],
|
|
35
|
+
['vibekit', 'contextkit'],
|
|
36
|
+
['VIBE_', 'CONTEXT_'],
|
|
37
|
+
['vibe-', 'context-'],
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
/** Control files whose kit-managed content carries old tokens. `backup` files hold user content. */
|
|
41
|
+
const CONTROL = [
|
|
42
|
+
{ rel: '.claude/settings.json', backup: false },
|
|
43
|
+
{ rel: '.gitignore', backup: false },
|
|
44
|
+
{ rel: '.gitattributes', backup: false },
|
|
45
|
+
{ rel: 'CLAUDE.md', backup: true },
|
|
46
|
+
{ rel: join(NEW_DIR, '.env'), backup: true }, // after the move
|
|
47
|
+
];
|
|
48
|
+
const GIT_HOOKS = ['pre-commit', 'commit-msg', 'pre-push'];
|
|
49
|
+
const STALE_COMMANDS = [
|
|
50
|
+
'.claude/commands/vibe-stats.md',
|
|
51
|
+
'.claude/commands/setup/vibe-config.md',
|
|
52
|
+
'.claude/commands/setup/vibe-doctor.md',
|
|
53
|
+
'.claude/commands/setup/vibe-level.md',
|
|
54
|
+
'.claude/commands/setup/setupvibedevkit.md',
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
function rewriteTokens(text) {
|
|
58
|
+
let out = text;
|
|
59
|
+
for (const [from, to] of TOKENS) out = out.split(from).join(to);
|
|
60
|
+
return out;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Detects whether `target` holds a legacy install and whether the new folder
|
|
65
|
+
* already exists. A legacy install is a `vibekit/` with a config or runtime.
|
|
66
|
+
*/
|
|
67
|
+
export function detectLegacy(target) {
|
|
68
|
+
const legacy = join(target, LEGACY_DIR);
|
|
69
|
+
const isLegacy = existsSync(legacy) && (existsSync(join(legacy, 'config.json')) || existsSync(join(legacy, 'runtime')));
|
|
70
|
+
return { isLegacy, hasNew: existsSync(join(target, NEW_DIR)) };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function rewriteFile(path, { backup, dryRun }) {
|
|
74
|
+
if (!existsSync(path)) return false;
|
|
75
|
+
let text;
|
|
76
|
+
try {
|
|
77
|
+
text = await readFile(path, 'utf-8');
|
|
78
|
+
} catch {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
const next = rewriteTokens(text);
|
|
82
|
+
if (next === text) return false;
|
|
83
|
+
if (dryRun) return true;
|
|
84
|
+
if (backup && !existsSync(`${path}.bak`)) await writeFile(`${path}.bak`, text, 'utf-8').catch(() => {});
|
|
85
|
+
await writeFile(path, next, 'utf-8').catch(() => {});
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async function moveFolder(from, to) {
|
|
90
|
+
try {
|
|
91
|
+
await rename(from, to);
|
|
92
|
+
} catch (err) {
|
|
93
|
+
if (err && err.code === 'EXDEV') {
|
|
94
|
+
// Cross-device (e.g. target on another volume): copy then remove.
|
|
95
|
+
await cp(from, to, { recursive: true, force: true });
|
|
96
|
+
await rm(from, { recursive: true, force: true });
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
throw err;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Runs the legacy → new migration on `target`. No-ops cleanly when there is
|
|
105
|
+
* nothing to migrate. NEVER throws — returns `{ migrated, report }`.
|
|
106
|
+
*
|
|
107
|
+
* @param {string} target project root
|
|
108
|
+
* @param {{ dryRun?: boolean }} [opts] `dryRun` reports without writing
|
|
109
|
+
* @returns {Promise<{ migrated: boolean, report: string[] }>}
|
|
110
|
+
*/
|
|
111
|
+
export async function migrateLegacy(target, opts = {}) {
|
|
112
|
+
const dryRun = !!opts.dryRun;
|
|
113
|
+
const report = [];
|
|
114
|
+
let det;
|
|
115
|
+
try {
|
|
116
|
+
det = detectLegacy(target);
|
|
117
|
+
} catch {
|
|
118
|
+
return { migrated: false, report };
|
|
119
|
+
}
|
|
120
|
+
if (!det.isLegacy) return { migrated: false, report };
|
|
121
|
+
|
|
122
|
+
if (det.hasNew) {
|
|
123
|
+
report.push('⚠️ found BOTH vibekit/ (legacy) and contextkit/ — not merging automatically.');
|
|
124
|
+
report.push(' Your old data is in vibekit/. Move what you need into contextkit/, then delete vibekit/.');
|
|
125
|
+
return { migrated: false, report };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const tag = dryRun ? '[dry-run] would' : '✓';
|
|
129
|
+
report.push(dryRun ? '🔎 legacy VibeDevKit install detected (dry-run — no changes):' : '🔄 migrating legacy VibeDevKit install → ContextDevKit…');
|
|
130
|
+
|
|
131
|
+
// 1) move the folder — carries ALL user data (memory, config, pipeline, .env) forward.
|
|
132
|
+
if (!dryRun) {
|
|
133
|
+
try {
|
|
134
|
+
await moveFolder(join(target, LEGACY_DIR), join(target, NEW_DIR));
|
|
135
|
+
} catch (err) {
|
|
136
|
+
report.push(`⚠️ could not move vibekit/ → contextkit/ (${err?.code || err}); migration aborted, nothing changed.`);
|
|
137
|
+
return { migrated: false, report };
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
report.push(` ${tag} vibekit/ → contextkit/ (memory, config, pipeline, .env preserved)`);
|
|
141
|
+
|
|
142
|
+
// 2) rewrite the control files (CLAUDE.md + .env are backed up to *.bak first).
|
|
143
|
+
for (const { rel, backup } of CONTROL) {
|
|
144
|
+
if (await rewriteFile(join(target, rel), { backup, dryRun })) report.push(` ${tag} updated ${rel}${backup ? ' (backup *.bak)' : ''}`);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// 3) git-hook wrappers (best-effort; the installer re-installs them properly afterwards).
|
|
148
|
+
for (const hook of GIT_HOOKS) {
|
|
149
|
+
if (await rewriteFile(join(target, '.git', 'hooks', hook), { backup: false, dryRun })) report.push(` ${tag} rewired .git/hooks/${hook}`);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// 4) delete the stale /vibe-* + setupvibedevkit command files (new ones reinstalled by the flow).
|
|
153
|
+
for (const rel of STALE_COMMANDS) {
|
|
154
|
+
const path = join(target, rel);
|
|
155
|
+
if (!existsSync(path)) continue;
|
|
156
|
+
if (!dryRun) await rm(path, { force: true }).catch(() => {});
|
|
157
|
+
report.push(` ${tag} removed stale ${rel}`);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
report.push(dryRun ? '🔎 dry-run complete — re-run without --dry-run to apply.' : '✅ migration complete — continuing with the engine refresh…');
|
|
161
|
+
return { migrated: !dryRun, report };
|
|
162
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integration test — legacy `vibekit/` → `contextkit/` migration (the rename).
|
|
3
|
+
*
|
|
4
|
+
* Scaffolds a throwaway project that looks like an OLD `vibedevkit` install,
|
|
5
|
+
* then drives the real installer to prove the migration:
|
|
6
|
+
* - `--migrate --dry-run` changes nothing;
|
|
7
|
+
* - `--migrate` moves the folder (preserving user memory/config/.env), rewrites
|
|
8
|
+
* settings.json / CLAUDE.md / .gitignore / git hooks, backs up user files,
|
|
9
|
+
* and deletes the stale /vibe-* commands;
|
|
10
|
+
* - `--update` auto-migrates end-to-end and refreshes the engine (no dup hooks);
|
|
11
|
+
* - BOTH folders present → refuse (no changes);
|
|
12
|
+
* - re-running on a migrated project is a no-op.
|
|
13
|
+
*/
|
|
14
|
+
import { mkdtempSync, mkdirSync, writeFileSync, readFileSync, existsSync, rmSync } from 'node:fs';
|
|
15
|
+
import { tmpdir } from 'node:os';
|
|
16
|
+
import { join } from 'node:path';
|
|
17
|
+
import { KIT, node, run, git, reporter } from './it-helpers.mjs';
|
|
18
|
+
|
|
19
|
+
const rep = reporter();
|
|
20
|
+
const read = (p) => readFileSync(p, 'utf-8');
|
|
21
|
+
const tmp = () => mkdtempSync(join(tmpdir(), 'contextkit-mig-'));
|
|
22
|
+
|
|
23
|
+
/** Scaffolds a project that looks like a legacy VibeDevKit install. */
|
|
24
|
+
function makeLegacy(proj, { withGit = false } = {}) {
|
|
25
|
+
if (withGit) {
|
|
26
|
+
git(['init', '-b', 'main'], proj);
|
|
27
|
+
git(['config', 'user.email', 'it@example.com'], proj);
|
|
28
|
+
git(['config', 'user.name', 'IT'], proj);
|
|
29
|
+
}
|
|
30
|
+
const w = (rel, body) => {
|
|
31
|
+
const p = join(proj, rel);
|
|
32
|
+
mkdirSync(join(p, '..'), { recursive: true });
|
|
33
|
+
writeFileSync(p, body, 'utf-8');
|
|
34
|
+
};
|
|
35
|
+
w('vibekit/config.json', JSON.stringify({ level: 5, setup: { completed: true }, ledger: {} }, null, 2));
|
|
36
|
+
w('vibekit/runtime/hooks/session-start.mjs', '// legacy dummy engine\n');
|
|
37
|
+
w('vibekit/memory/decisions/0001-user-decision.md', '# 0001 — a precious user ADR\nkeep me\n');
|
|
38
|
+
w('vibekit/.env', 'GOOGLE_AI_API_KEY=secret\nVIBE_GIT_TIMEOUT_MS=5000\n');
|
|
39
|
+
w('.claude/settings.json', JSON.stringify({
|
|
40
|
+
hooks: { SessionStart: [{ hooks: [{ type: 'command', command: 'node vibekit/runtime/hooks/session-start.mjs' }] }] } },
|
|
41
|
+
null, 2));
|
|
42
|
+
w('.claude/commands/setup/vibe-level.md', 'old vibe-level command\n');
|
|
43
|
+
w('.claude/commands/vibe-stats.md', 'old vibe-stats command\n');
|
|
44
|
+
w('CLAUDE.md', 'This project uses VibeDevKit. Engine in vibekit/runtime. Run /vibe-level to change level.\n');
|
|
45
|
+
w('.gitignore', '# VibeDevKit — local runtime state (do not commit)\nvibekit/memory/tech-debt-findings.json\n');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ── Scenario A: dry-run changes nothing ──────────────────────────────────────
|
|
49
|
+
(() => {
|
|
50
|
+
const proj = tmp();
|
|
51
|
+
try {
|
|
52
|
+
makeLegacy(proj);
|
|
53
|
+
const out = run([join(KIT, 'install.mjs'), '--target', proj, '--migrate', '--dry-run']);
|
|
54
|
+
out.status === 0 ? rep.ok('--migrate --dry-run exits 0') : rep.bad(`dry-run status ${out.status}: ${out.stderr}`);
|
|
55
|
+
existsSync(join(proj, 'vibekit')) && !existsSync(join(proj, 'contextkit'))
|
|
56
|
+
? rep.ok('dry-run left vibekit/ in place and created no contextkit/')
|
|
57
|
+
: rep.bad('dry-run mutated the filesystem');
|
|
58
|
+
read(join(proj, 'CLAUDE.md')).includes('vibekit/') ? rep.ok('dry-run left CLAUDE.md untouched') : rep.bad('dry-run rewrote CLAUDE.md');
|
|
59
|
+
/dry-run/i.test(out.stdout) ? rep.ok('dry-run output flags itself') : rep.bad('dry-run output missing the marker');
|
|
60
|
+
} finally { rmSync(proj, { recursive: true, force: true }); }
|
|
61
|
+
})();
|
|
62
|
+
|
|
63
|
+
// ── Scenario B: real migration preserves data + rewrites references ──────────
|
|
64
|
+
(() => {
|
|
65
|
+
const proj = tmp();
|
|
66
|
+
try {
|
|
67
|
+
makeLegacy(proj);
|
|
68
|
+
const out = run([join(KIT, 'install.mjs'), '--target', proj, '--migrate']);
|
|
69
|
+
out.status === 0 ? rep.ok('--migrate exits 0') : rep.bad(`--migrate status ${out.status}: ${out.stderr}`);
|
|
70
|
+
|
|
71
|
+
!existsSync(join(proj, 'vibekit')) ? rep.ok('vibekit/ is gone') : rep.bad('vibekit/ still present');
|
|
72
|
+
existsSync(join(proj, 'contextkit')) ? rep.ok('contextkit/ exists') : rep.bad('contextkit/ missing');
|
|
73
|
+
|
|
74
|
+
// user data preserved
|
|
75
|
+
const adr = join(proj, 'contextkit', 'memory', 'decisions', '0001-user-decision.md');
|
|
76
|
+
existsSync(adr) && read(adr).includes('precious user ADR') ? rep.ok('user ADR preserved through the move') : rep.bad('user ADR lost');
|
|
77
|
+
try {
|
|
78
|
+
JSON.parse(read(join(proj, 'contextkit', 'config.json'))).level === 5 ? rep.ok('config level 5 preserved') : rep.bad('config level changed');
|
|
79
|
+
} catch { rep.bad('config.json unreadable after migration'); }
|
|
80
|
+
|
|
81
|
+
// .env migrated + backed up
|
|
82
|
+
const env = read(join(proj, 'contextkit', '.env'));
|
|
83
|
+
env.includes('CONTEXT_GIT_TIMEOUT_MS') && !env.includes('VIBE_') ? rep.ok('.env VIBE_* → CONTEXT_*') : rep.bad('.env env-var not migrated');
|
|
84
|
+
existsSync(join(proj, 'contextkit', '.env.bak')) ? rep.ok('.env backed up to .env.bak') : rep.bad('.env.bak missing');
|
|
85
|
+
|
|
86
|
+
// settings.json rewired (no legacy paths, no duplicate)
|
|
87
|
+
const settings = read(join(proj, '.claude', 'settings.json'));
|
|
88
|
+
!settings.includes('vibekit/') && settings.includes('contextkit/runtime/hooks') ? rep.ok('settings.json rewired to contextkit/') : rep.bad('settings.json still references vibekit/');
|
|
89
|
+
|
|
90
|
+
// CLAUDE.md rewritten + backed up
|
|
91
|
+
const claude = read(join(proj, 'CLAUDE.md'));
|
|
92
|
+
!claude.includes('vibekit/') && claude.includes('contextkit/') && claude.includes('/context-level') && claude.includes('ContextDevKit')
|
|
93
|
+
? rep.ok('CLAUDE.md references rewritten') : rep.bad('CLAUDE.md not fully rewritten');
|
|
94
|
+
existsSync(join(proj, 'CLAUDE.md.bak')) && read(join(proj, 'CLAUDE.md.bak')).includes('VibeDevKit')
|
|
95
|
+
? rep.ok('CLAUDE.md backed up to .bak') : rep.bad('CLAUDE.md.bak missing/wrong');
|
|
96
|
+
|
|
97
|
+
// stale commands removed
|
|
98
|
+
!existsSync(join(proj, '.claude', 'commands', 'setup', 'vibe-level.md')) && !existsSync(join(proj, '.claude', 'commands', 'vibe-stats.md'))
|
|
99
|
+
? rep.ok('stale /vibe-* command files removed') : rep.bad('stale command files left behind');
|
|
100
|
+
|
|
101
|
+
// .gitignore de-duplicated (now ContextDevKit)
|
|
102
|
+
read(join(proj, '.gitignore')).includes('ContextDevKit') ? rep.ok('.gitignore block rewritten') : rep.bad('.gitignore not migrated');
|
|
103
|
+
|
|
104
|
+
// idempotent: second run is a clean no-op
|
|
105
|
+
const again = run([join(KIT, 'install.mjs'), '--target', proj, '--migrate']);
|
|
106
|
+
again.status === 0 && /nothing to migrate/i.test(again.stdout) ? rep.ok('re-running --migrate is a no-op') : rep.bad('second --migrate was not a clean no-op');
|
|
107
|
+
} finally { rmSync(proj, { recursive: true, force: true }); }
|
|
108
|
+
})();
|
|
109
|
+
|
|
110
|
+
// ── Scenario C: BOTH folders present → refuse, change nothing ────────────────
|
|
111
|
+
(() => {
|
|
112
|
+
const proj = tmp();
|
|
113
|
+
try {
|
|
114
|
+
makeLegacy(proj);
|
|
115
|
+
mkdirSync(join(proj, 'contextkit'), { recursive: true });
|
|
116
|
+
writeFileSync(join(proj, 'contextkit', 'config.json'), JSON.stringify({ level: 3 }), 'utf-8');
|
|
117
|
+
const out = run([join(KIT, 'install.mjs'), '--target', proj, '--migrate']);
|
|
118
|
+
out.status === 0 ? rep.ok('refuse-both exits 0') : rep.bad(`refuse-both status ${out.status}`);
|
|
119
|
+
/BOTH/i.test(out.stdout) ? rep.ok('warns about BOTH installs') : rep.bad('no BOTH warning');
|
|
120
|
+
existsSync(join(proj, 'vibekit')) ? rep.ok('refuse-both left vibekit/ untouched') : rep.bad('refuse-both deleted vibekit/');
|
|
121
|
+
} finally { rmSync(proj, { recursive: true, force: true }); }
|
|
122
|
+
})();
|
|
123
|
+
|
|
124
|
+
// ── Scenario D: --update auto-migrates AND refreshes the real engine ─────────
|
|
125
|
+
(() => {
|
|
126
|
+
const proj = tmp();
|
|
127
|
+
try {
|
|
128
|
+
makeLegacy(proj, { withGit: true });
|
|
129
|
+
const out = run([join(KIT, 'install.mjs'), '--target', proj, '--update']);
|
|
130
|
+
out.status === 0 ? rep.ok('--update on a legacy install exits 0') : rep.bad(`--update status ${out.status}: ${out.stderr}`);
|
|
131
|
+
!existsSync(join(proj, 'vibekit')) ? rep.ok('--update migrated away vibekit/') : rep.bad('--update left vibekit/');
|
|
132
|
+
// real engine refreshed (the dummy 1-line hook is replaced by the actual script)
|
|
133
|
+
const hook = join(proj, 'contextkit', 'runtime', 'hooks', 'session-start.mjs');
|
|
134
|
+
existsSync(hook) && read(hook).length > 200 ? rep.ok('--update refreshed the real engine') : rep.bad('engine not refreshed');
|
|
135
|
+
// no duplicate hooks: exactly one SessionStart group, referencing contextkit/
|
|
136
|
+
const s = JSON.parse(read(join(proj, '.claude', 'settings.json')));
|
|
137
|
+
const ss = s.hooks?.SessionStart || [];
|
|
138
|
+
ss.length === 1 && !JSON.stringify(s).includes('vibekit/') ? rep.ok('no duplicate hooks after --update') : rep.bad(`duplicate/legacy hooks remain (SessionStart groups: ${ss.length})`);
|
|
139
|
+
} finally { rmSync(proj, { recursive: true, force: true }); }
|
|
140
|
+
})();
|
|
141
|
+
|
|
142
|
+
// ── Scenario E: fresh install is unaffected (no legacy → no-op) ──────────────
|
|
143
|
+
(() => {
|
|
144
|
+
const proj = tmp();
|
|
145
|
+
try {
|
|
146
|
+
const out = run([join(KIT, 'install.mjs'), '--target', proj, '--migrate']);
|
|
147
|
+
out.status === 0 && /nothing to migrate/i.test(out.stdout) ? rep.ok('no-legacy --migrate is a clean no-op') : rep.bad('no-legacy --migrate misbehaved');
|
|
148
|
+
} finally { rmSync(proj, { recursive: true, force: true }); }
|
|
149
|
+
})();
|
|
150
|
+
|
|
151
|
+
rep.finish('Integration (migration)');
|