@a-company/paradigm 3.27.0 → 3.34.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/dist/{accept-orchestration-6EM5EHXA.js → accept-orchestration-XXANWJVZ.js} +3 -3
- package/dist/{aggregate-M5WMUI6B.js → aggregate-XHQ6GI3Z.js} +2 -2
- package/dist/{beacon-XL2ALH5O.js → beacon-BTLQMYQL.js} +2 -2
- package/dist/{chunk-AK5M6KJB.js → chunk-36TKPM5Z.js} +20 -2
- package/dist/{chunk-ZOH24ZPF.js → chunk-3BGSDKWD.js} +3 -3
- package/dist/{chunk-6P4IFIK2.js → chunk-CTF6RHKG.js} +37 -3
- package/dist/{chunk-SCC77UUP.js → chunk-H4TVBJD4.js} +3 -3
- package/dist/{chunk-KWDTBXP2.js → chunk-LHLIAYQ3.js} +1 -1
- package/dist/{chunk-7IJ5JVKT.js → chunk-PFLWLC6J.js} +59 -7
- package/dist/{chunk-W4VFKZVF.js → chunk-S5TDFT5Q.js} +2 -2
- package/dist/{chunk-N6RNYCZD.js → chunk-UQNTJ5VB.js} +1 -1
- package/dist/{chunk-MRENOFTR.js → chunk-VUSCJJ4A.js} +6 -1
- package/dist/{chunk-OXG5GVDJ.js → chunk-WOONGZ3C.js} +1 -1
- package/dist/{commands-6ZVTD74M.js → commands-KPT2T2OZ.js} +1 -1
- package/dist/{conductor-CAIY5LJA.js → conductor-HLWYWUVH.js} +14 -0
- package/dist/{constellation-NXU6Q2HM.js → constellation-LZ6XIKDT.js} +2 -2
- package/dist/{cost-CTGSLSOC.js → cost-4SZM7OUS.js} +1 -1
- package/dist/{cursorrules-XBWFX66V.js → cursorrules-3TKZ4E4R.js} +2 -2
- package/dist/{diff-AH7L4PRQ.js → diff-T6YJSAAC.js} +3 -3
- package/dist/{dist-KY5HGDDL.js → dist-OH4DBV2O.js} +42 -3
- package/dist/{dist-7U64HDSC.js → dist-QSBAGCZT.js} +8 -2
- package/dist/index.js +44 -40
- package/dist/{lint-53GPXKKI.js → lint-MTRZB5EC.js} +1 -1
- package/dist/mcp.js +117 -42
- package/dist/migrate-HRN5TUBQ.js +871 -0
- package/dist/{orchestrate-HMSQ2CED.js → orchestrate-3SI6ON33.js} +3 -3
- package/dist/{probe-SN4BNXOC.js → probe-ABMGCXQG.js} +4 -4
- package/dist/{reindex-YG3KIXAK.js → reindex-YC7LD4MN.js} +1 -1
- package/dist/{remember-IEBQHXHZ.js → remember-WR6ZVXLT.js} +1 -1
- package/dist/{ripple-DFMXLFWI.js → ripple-QTXKJCEI.js} +2 -2
- package/dist/sentinel.js +6 -6
- package/dist/{setup-HOI52TN3.js → setup-ASR6OMKV.js} +4 -4
- package/dist/{shift-DRF5M3G6.js → shift-7XLSBLDW.js} +19 -9
- package/dist/{snapshot-XHINQBZS.js → snapshot-QZFD7YBI.js} +2 -2
- package/dist/{summary-NV7SBV5O.js → summary-R4CSYNNP.js} +2 -2
- package/dist/{team-YOGT2Q2X.js → team-VH3HYABB.js} +4 -4
- package/dist/university-content/courses/para-101.json +53 -0
- package/dist/university-content/plsat/v3.0.json +78 -1
- package/dist/{upgrade-65QOQXRC.js → upgrade-ANX3LVSA.js} +1 -0
- package/dist/{validate-TKKRGJKC.js → validate-OUHUBZPO.js} +1 -1
- package/dist/{workspace-L27RR5MF.js → workspace-5RBSALXC.js} +5 -5
- package/package.json +1 -1
|
@@ -0,0 +1,871 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import "./chunk-ZXMDA7VB.js";
|
|
3
|
+
|
|
4
|
+
// src/commands/migrate/index.ts
|
|
5
|
+
import * as fs4 from "fs";
|
|
6
|
+
import * as path4 from "path";
|
|
7
|
+
import chalk from "chalk";
|
|
8
|
+
import ora from "ora";
|
|
9
|
+
|
|
10
|
+
// src/commands/migrate/detector.ts
|
|
11
|
+
import * as fs2 from "fs";
|
|
12
|
+
import * as path2 from "path";
|
|
13
|
+
import * as yaml2 from "js-yaml";
|
|
14
|
+
import { createRequire } from "module";
|
|
15
|
+
|
|
16
|
+
// src/commands/migrate/migrations.ts
|
|
17
|
+
import * as fs from "fs";
|
|
18
|
+
import * as path from "path";
|
|
19
|
+
import * as yaml from "js-yaml";
|
|
20
|
+
function dirExists(rootDir, ...segments) {
|
|
21
|
+
return fs.existsSync(path.join(rootDir, ...segments));
|
|
22
|
+
}
|
|
23
|
+
function ensureDir(rootDir, ...segments) {
|
|
24
|
+
fs.mkdirSync(path.join(rootDir, ...segments), { recursive: true });
|
|
25
|
+
}
|
|
26
|
+
function readConfig(rootDir) {
|
|
27
|
+
const p = path.join(rootDir, ".paradigm", "config.yaml");
|
|
28
|
+
if (!fs.existsSync(p)) return null;
|
|
29
|
+
try {
|
|
30
|
+
return yaml.load(fs.readFileSync(p, "utf8"));
|
|
31
|
+
} catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function readConfigRaw(rootDir) {
|
|
36
|
+
const p = path.join(rootDir, ".paradigm", "config.yaml");
|
|
37
|
+
if (!fs.existsSync(p)) return null;
|
|
38
|
+
return fs.readFileSync(p, "utf8");
|
|
39
|
+
}
|
|
40
|
+
function writeConfigRaw(rootDir, content) {
|
|
41
|
+
fs.writeFileSync(path.join(rootDir, ".paradigm", "config.yaml"), content, "utf8");
|
|
42
|
+
}
|
|
43
|
+
function configHasField(config, field) {
|
|
44
|
+
return field in config || field.replace(/-/g, "_") in config || field.replace(/_/g, "-") in config;
|
|
45
|
+
}
|
|
46
|
+
var legacyFileToDirectory = {
|
|
47
|
+
id: "legacy-file-to-directory",
|
|
48
|
+
introducedIn: "1.0.0",
|
|
49
|
+
description: "Convert .paradigm file to .paradigm/ directory",
|
|
50
|
+
category: "directory",
|
|
51
|
+
auto: true,
|
|
52
|
+
async check(rootDir) {
|
|
53
|
+
const p = path.join(rootDir, ".paradigm");
|
|
54
|
+
if (!fs.existsSync(p)) return { needed: false, reason: "No .paradigm found" };
|
|
55
|
+
if (fs.statSync(p).isFile()) {
|
|
56
|
+
return { needed: true, reason: ".paradigm is a file, not a directory", details: ["Legacy format \u2014 must convert to directory"] };
|
|
57
|
+
}
|
|
58
|
+
return { needed: false, reason: ".paradigm/ is already a directory" };
|
|
59
|
+
},
|
|
60
|
+
async apply(rootDir, opts) {
|
|
61
|
+
if (opts.dryRun) return { status: "skipped", message: "Would convert .paradigm file to directory" };
|
|
62
|
+
const p = path.join(rootDir, ".paradigm");
|
|
63
|
+
const backup = path.join(rootDir, ".paradigm.backup");
|
|
64
|
+
const content = fs.readFileSync(p, "utf8");
|
|
65
|
+
fs.copyFileSync(p, backup);
|
|
66
|
+
fs.unlinkSync(p);
|
|
67
|
+
fs.mkdirSync(p, { recursive: true });
|
|
68
|
+
fs.mkdirSync(path.join(p, "specs"), { recursive: true });
|
|
69
|
+
fs.mkdirSync(path.join(p, "docs"), { recursive: true });
|
|
70
|
+
fs.mkdirSync(path.join(p, "prompts"), { recursive: true });
|
|
71
|
+
fs.writeFileSync(path.join(p, "config.yaml"), content, "utf8");
|
|
72
|
+
return {
|
|
73
|
+
status: "applied",
|
|
74
|
+
message: "Converted .paradigm file to directory (backup at .paradigm.backup)",
|
|
75
|
+
filesCreated: [".paradigm/", ".paradigm/config.yaml", ".paradigm/specs/", ".paradigm/docs/", ".paradigm/prompts/"]
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
var legacyScanIndexLocation = {
|
|
80
|
+
id: "legacy-scan-index-location",
|
|
81
|
+
introducedIn: "1.0.0",
|
|
82
|
+
description: "Move .paradigm-scan-index.json into .paradigm/",
|
|
83
|
+
category: "format",
|
|
84
|
+
auto: true,
|
|
85
|
+
async check(rootDir) {
|
|
86
|
+
const old = path.join(rootDir, ".paradigm-scan-index.json");
|
|
87
|
+
if (fs.existsSync(old)) {
|
|
88
|
+
return { needed: true, reason: "Scan index at root level needs relocation" };
|
|
89
|
+
}
|
|
90
|
+
return { needed: false, reason: "No legacy scan index at root" };
|
|
91
|
+
},
|
|
92
|
+
async apply(rootDir, opts) {
|
|
93
|
+
if (opts.dryRun) return { status: "skipped", message: "Would move .paradigm-scan-index.json" };
|
|
94
|
+
const old = path.join(rootDir, ".paradigm-scan-index.json");
|
|
95
|
+
const dest = path.join(rootDir, ".paradigm", "scan-index.json");
|
|
96
|
+
fs.renameSync(old, dest);
|
|
97
|
+
return { status: "applied", message: "Moved scan index into .paradigm/", filesModified: [".paradigm/scan-index.json"] };
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
function makeDirectoryMigration(id, version, relPath, description, details) {
|
|
101
|
+
return {
|
|
102
|
+
id,
|
|
103
|
+
introducedIn: version,
|
|
104
|
+
description,
|
|
105
|
+
category: "directory",
|
|
106
|
+
auto: true,
|
|
107
|
+
async check(rootDir) {
|
|
108
|
+
if (dirExists(rootDir, ".paradigm", relPath)) {
|
|
109
|
+
return { needed: false, reason: `.paradigm/${relPath}/ already exists` };
|
|
110
|
+
}
|
|
111
|
+
return { needed: true, reason: `Missing .paradigm/${relPath}/`, details };
|
|
112
|
+
},
|
|
113
|
+
async apply(rootDir, opts) {
|
|
114
|
+
if (opts.dryRun) return { status: "skipped", message: `Would create .paradigm/${relPath}/` };
|
|
115
|
+
ensureDir(rootDir, ".paradigm", relPath);
|
|
116
|
+
return { status: "applied", message: `Created .paradigm/${relPath}/`, filesCreated: [`.paradigm/${relPath}/`] };
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
var addSpecsDirectory = makeDirectoryMigration(
|
|
121
|
+
"add-specs-directory",
|
|
122
|
+
"3.0.0",
|
|
123
|
+
"specs",
|
|
124
|
+
"Create .paradigm/specs/ for specifications",
|
|
125
|
+
["Stores logger spec, symbols spec, scan spec, etc."]
|
|
126
|
+
);
|
|
127
|
+
var addDocsDirectory = makeDirectoryMigration(
|
|
128
|
+
"add-docs-directory",
|
|
129
|
+
"3.0.0",
|
|
130
|
+
"docs",
|
|
131
|
+
"Create .paradigm/docs/ for documentation",
|
|
132
|
+
["Stores commands, patterns, troubleshooting docs"]
|
|
133
|
+
);
|
|
134
|
+
var addPromptsDirectory = makeDirectoryMigration(
|
|
135
|
+
"add-prompts-directory",
|
|
136
|
+
"3.0.0",
|
|
137
|
+
"prompts",
|
|
138
|
+
"Create .paradigm/prompts/ for task templates",
|
|
139
|
+
["Stores add-feature, refactor, trace-flow prompts"]
|
|
140
|
+
);
|
|
141
|
+
var addTagBankConfig = {
|
|
142
|
+
id: "add-tag-bank-config",
|
|
143
|
+
introducedIn: "3.2.0",
|
|
144
|
+
description: "Add tag-bank section to config.yaml",
|
|
145
|
+
category: "config",
|
|
146
|
+
auto: true,
|
|
147
|
+
async check(rootDir) {
|
|
148
|
+
const config = readConfig(rootDir);
|
|
149
|
+
if (!config) return { needed: false, reason: "No config.yaml found" };
|
|
150
|
+
if (configHasField(config, "tag-bank")) {
|
|
151
|
+
return { needed: false, reason: "tag-bank already configured" };
|
|
152
|
+
}
|
|
153
|
+
return { needed: true, reason: "Missing tag-bank configuration" };
|
|
154
|
+
},
|
|
155
|
+
async apply(rootDir, opts) {
|
|
156
|
+
if (opts.dryRun) return { status: "skipped", message: "Would add tag-bank to config.yaml" };
|
|
157
|
+
const raw = readConfigRaw(rootDir);
|
|
158
|
+
if (!raw) return { status: "error", message: "Cannot read config.yaml" };
|
|
159
|
+
const tagBankBlock = `
|
|
160
|
+
# Tag bank configuration
|
|
161
|
+
tag-bank:
|
|
162
|
+
file: tags.yaml
|
|
163
|
+
core-tags:
|
|
164
|
+
- feature
|
|
165
|
+
- integration
|
|
166
|
+
- state
|
|
167
|
+
- idea
|
|
168
|
+
- deprecated
|
|
169
|
+
- critical
|
|
170
|
+
- security
|
|
171
|
+
- compliance
|
|
172
|
+
allow-suggestions: true
|
|
173
|
+
`;
|
|
174
|
+
writeConfigRaw(rootDir, raw.trimEnd() + "\n" + tagBankBlock);
|
|
175
|
+
return { status: "applied", message: "Added tag-bank to config.yaml", filesModified: [".paradigm/config.yaml"] };
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
var addPurposeRequiredConfig = {
|
|
179
|
+
id: "add-purpose-required-config",
|
|
180
|
+
introducedIn: "3.3.0",
|
|
181
|
+
description: "Add purpose-required patterns to config.yaml",
|
|
182
|
+
category: "config",
|
|
183
|
+
auto: true,
|
|
184
|
+
async check(rootDir) {
|
|
185
|
+
const config = readConfig(rootDir);
|
|
186
|
+
if (!config) return { needed: false, reason: "No config.yaml found" };
|
|
187
|
+
if (configHasField(config, "purpose-required")) {
|
|
188
|
+
return { needed: false, reason: "purpose-required already configured" };
|
|
189
|
+
}
|
|
190
|
+
return { needed: true, reason: "Missing purpose-required patterns" };
|
|
191
|
+
},
|
|
192
|
+
async apply(rootDir, opts) {
|
|
193
|
+
if (opts.dryRun) return { status: "skipped", message: "Would add purpose-required to config.yaml" };
|
|
194
|
+
const raw = readConfigRaw(rootDir);
|
|
195
|
+
if (!raw) return { status: "error", message: "Cannot read config.yaml" };
|
|
196
|
+
const block = `
|
|
197
|
+
# Where .purpose files should exist
|
|
198
|
+
purpose-required:
|
|
199
|
+
- pattern: "src/*"
|
|
200
|
+
depth: 1
|
|
201
|
+
- pattern: "lib/*"
|
|
202
|
+
depth: 1
|
|
203
|
+
- pattern: "packages/*"
|
|
204
|
+
depth: 1
|
|
205
|
+
`;
|
|
206
|
+
writeConfigRaw(rootDir, raw.trimEnd() + "\n" + block);
|
|
207
|
+
return { status: "applied", message: "Added purpose-required to config.yaml", filesModified: [".paradigm/config.yaml"] };
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
var addLoreDirectory = makeDirectoryMigration(
|
|
211
|
+
"add-lore-directory",
|
|
212
|
+
"3.7.0",
|
|
213
|
+
"lore",
|
|
214
|
+
"Create .paradigm/lore/ for project reflections and decisions",
|
|
215
|
+
["Stores lore entries (reflections, decisions, arcs)"]
|
|
216
|
+
);
|
|
217
|
+
var addTasksDirectory = makeDirectoryMigration(
|
|
218
|
+
"add-tasks-directory",
|
|
219
|
+
"3.8.0",
|
|
220
|
+
"tasks",
|
|
221
|
+
"Create .paradigm/tasks/ for work item tracking",
|
|
222
|
+
["Stores task YAML files for persistent task management"]
|
|
223
|
+
);
|
|
224
|
+
var addPersonasDirectory = makeDirectoryMigration(
|
|
225
|
+
"add-personas-directory",
|
|
226
|
+
"3.9.0",
|
|
227
|
+
"personas",
|
|
228
|
+
"Create .paradigm/personas/ for actor-driven journey testing",
|
|
229
|
+
["Stores .persona files with traits, triggers, fixtures, journey steps"]
|
|
230
|
+
);
|
|
231
|
+
var addProtocolsDirectory = makeDirectoryMigration(
|
|
232
|
+
"add-protocols-directory",
|
|
233
|
+
"3.10.0",
|
|
234
|
+
"protocols",
|
|
235
|
+
"Create .paradigm/protocols/ for repeatable implementation patterns",
|
|
236
|
+
["Stores protocol YAML files captured from implementation workflows"]
|
|
237
|
+
);
|
|
238
|
+
var addHabitsDirectory = makeDirectoryMigration(
|
|
239
|
+
"add-habits-directory",
|
|
240
|
+
"3.14.0",
|
|
241
|
+
"habits",
|
|
242
|
+
"Create .paradigm/habits/ for session habit tracking"
|
|
243
|
+
);
|
|
244
|
+
var addWisdomDirectory = makeDirectoryMigration(
|
|
245
|
+
"add-wisdom-directory",
|
|
246
|
+
"3.5.0",
|
|
247
|
+
"wisdom",
|
|
248
|
+
"Create .paradigm/wisdom/ for antipatterns and team knowledge",
|
|
249
|
+
["Stores antipatterns.yaml and other team knowledge files"]
|
|
250
|
+
);
|
|
251
|
+
var addHistoryDirectory = makeDirectoryMigration(
|
|
252
|
+
"add-history-directory",
|
|
253
|
+
"3.6.0",
|
|
254
|
+
"history",
|
|
255
|
+
"Create .paradigm/history/ for symbol change history",
|
|
256
|
+
["Stores log.jsonl for commit-level symbol tracking"]
|
|
257
|
+
);
|
|
258
|
+
var migrateAssessmentsToLore = {
|
|
259
|
+
id: "migrate-assessments-to-lore",
|
|
260
|
+
introducedIn: "3.19.0",
|
|
261
|
+
description: "Convert old assessment entries to lore format",
|
|
262
|
+
category: "schema",
|
|
263
|
+
auto: false,
|
|
264
|
+
// Manual — delegates to `paradigm lore migrate-assessments`
|
|
265
|
+
async check(rootDir) {
|
|
266
|
+
const assessDir = path.join(rootDir, ".paradigm", "assessments");
|
|
267
|
+
if (!fs.existsSync(assessDir)) return { needed: false, reason: "No assessments directory found" };
|
|
268
|
+
try {
|
|
269
|
+
const files = fs.readdirSync(assessDir);
|
|
270
|
+
if (files.length === 0) return { needed: false, reason: "Assessments directory is empty" };
|
|
271
|
+
return {
|
|
272
|
+
needed: true,
|
|
273
|
+
reason: `${files.length} assessment file(s) to migrate`,
|
|
274
|
+
details: ["Run: paradigm lore migrate-assessments --dry-run"]
|
|
275
|
+
};
|
|
276
|
+
} catch {
|
|
277
|
+
return { needed: false, reason: "Cannot read assessments directory" };
|
|
278
|
+
}
|
|
279
|
+
},
|
|
280
|
+
async apply(_rootDir, _opts) {
|
|
281
|
+
return { status: "skipped", message: "Run `paradigm lore migrate-assessments` manually" };
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
var addComponentTypesConfig = {
|
|
285
|
+
id: "add-component-types-config",
|
|
286
|
+
introducedIn: "3.33.0",
|
|
287
|
+
description: "Add component_types glossary to config.yaml",
|
|
288
|
+
category: "config",
|
|
289
|
+
auto: true,
|
|
290
|
+
async check(rootDir) {
|
|
291
|
+
const config = readConfig(rootDir);
|
|
292
|
+
if (!config) return { needed: false, reason: "No config.yaml found" };
|
|
293
|
+
if (configHasField(config, "component_types")) {
|
|
294
|
+
return { needed: false, reason: "component_types already configured" };
|
|
295
|
+
}
|
|
296
|
+
return { needed: true, reason: "Missing component_types glossary" };
|
|
297
|
+
},
|
|
298
|
+
async apply(rootDir, opts) {
|
|
299
|
+
if (opts.dryRun) return { status: "skipped", message: "Would add component_types to config.yaml" };
|
|
300
|
+
const raw = readConfigRaw(rootDir);
|
|
301
|
+
if (!raw) return { status: "error", message: "Cannot read config.yaml" };
|
|
302
|
+
const block = `
|
|
303
|
+
# Component types glossary \u2014 defines the project vocabulary for the \`type\` field on components
|
|
304
|
+
component_types:
|
|
305
|
+
command: "CLI command handler \u2014 registers subcommand, parses args, calls core logic"
|
|
306
|
+
tool: "MCP tool definition + handler \u2014 serves AI agents via JSON-RPC"
|
|
307
|
+
utility: "Shared helper function or module \u2014 no side effects, pure logic"
|
|
308
|
+
engine: "Stateful processing core \u2014 owns data transformation lifecycle"
|
|
309
|
+
loader: "Reads and parses data sources into typed structures"
|
|
310
|
+
writer: "Writes structured data to files (YAML, JSON)"
|
|
311
|
+
service: "Business logic coordinator \u2014 orchestrates tools, loaders, writers"
|
|
312
|
+
model: "Data shape \u2014 TypeScript interface or type definition"
|
|
313
|
+
view: "UI rendering unit \u2014 SwiftUI view, React component"
|
|
314
|
+
provider: "Protocol-conforming platform capability wrapper \u2014 owns lifecycle"
|
|
315
|
+
manager: "Stateful coordinator that owns child lifecycles"
|
|
316
|
+
detector: "Observes external state and emits events"
|
|
317
|
+
router: "Maps input signals to targets based on rules"
|
|
318
|
+
filter: "Transforms or smooths data in a pipeline"
|
|
319
|
+
store: "Persistent or reactive state container"
|
|
320
|
+
handler: "Event handler, webhook receiver, or request processor"
|
|
321
|
+
config: "Configuration, environment, constants"
|
|
322
|
+
`;
|
|
323
|
+
writeConfigRaw(rootDir, raw.trimEnd() + "\n" + block);
|
|
324
|
+
return { status: "applied", message: "Added component_types glossary to config.yaml", filesModified: [".paradigm/config.yaml"] };
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
|
+
var addDisciplineConfig = {
|
|
328
|
+
id: "add-discipline-config",
|
|
329
|
+
introducedIn: "3.4.0",
|
|
330
|
+
description: "Add discipline field to config.yaml",
|
|
331
|
+
category: "config",
|
|
332
|
+
auto: true,
|
|
333
|
+
async check(rootDir) {
|
|
334
|
+
const config = readConfig(rootDir);
|
|
335
|
+
if (!config) return { needed: false, reason: "No config.yaml found" };
|
|
336
|
+
if (configHasField(config, "discipline")) {
|
|
337
|
+
return { needed: false, reason: "discipline already set" };
|
|
338
|
+
}
|
|
339
|
+
return { needed: true, reason: "Missing discipline field" };
|
|
340
|
+
},
|
|
341
|
+
async apply(rootDir, opts) {
|
|
342
|
+
if (opts.dryRun) return { status: "skipped", message: "Would add discipline to config.yaml" };
|
|
343
|
+
const raw = readConfigRaw(rootDir);
|
|
344
|
+
if (!raw) return { status: "error", message: "Cannot read config.yaml" };
|
|
345
|
+
const updated = raw.replace(
|
|
346
|
+
/^(project:\s*.+)$/m,
|
|
347
|
+
"$1\ndiscipline: auto"
|
|
348
|
+
);
|
|
349
|
+
if (updated === raw) {
|
|
350
|
+
writeConfigRaw(rootDir, raw.trimEnd() + "\ndiscipline: auto\n");
|
|
351
|
+
} else {
|
|
352
|
+
writeConfigRaw(rootDir, updated);
|
|
353
|
+
}
|
|
354
|
+
return { status: "applied", message: "Added discipline: auto to config.yaml", filesModified: [".paradigm/config.yaml"] };
|
|
355
|
+
}
|
|
356
|
+
};
|
|
357
|
+
var syncTemplates = {
|
|
358
|
+
id: "sync-templates",
|
|
359
|
+
introducedIn: "evergreen",
|
|
360
|
+
description: "Update specs/ and docs/ from installed CLI templates",
|
|
361
|
+
category: "template",
|
|
362
|
+
auto: true,
|
|
363
|
+
async check(rootDir) {
|
|
364
|
+
const templatesDir = getTemplatesDir();
|
|
365
|
+
if (!templatesDir) return { needed: false, reason: "Templates directory not found" };
|
|
366
|
+
const newFiles = [];
|
|
367
|
+
const subdirs = ["specs", "docs", "prompts"];
|
|
368
|
+
for (const sub of subdirs) {
|
|
369
|
+
const templateSub = path.join(templatesDir, sub);
|
|
370
|
+
const projectSub = path.join(rootDir, ".paradigm", sub);
|
|
371
|
+
if (!fs.existsSync(templateSub)) continue;
|
|
372
|
+
try {
|
|
373
|
+
const files = fs.readdirSync(templateSub).filter((f) => {
|
|
374
|
+
const s = fs.statSync(path.join(templateSub, f));
|
|
375
|
+
return s.isFile();
|
|
376
|
+
});
|
|
377
|
+
for (const file of files) {
|
|
378
|
+
if (!fs.existsSync(path.join(projectSub, file))) {
|
|
379
|
+
newFiles.push(`${sub}/${file}`);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
} catch {
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
if (newFiles.length === 0) return { needed: false, reason: "All templates are up to date" };
|
|
386
|
+
return {
|
|
387
|
+
needed: true,
|
|
388
|
+
reason: `${newFiles.length} new template file(s) available`,
|
|
389
|
+
details: newFiles.map((f) => `New: ${f}`)
|
|
390
|
+
};
|
|
391
|
+
},
|
|
392
|
+
async apply(rootDir, opts) {
|
|
393
|
+
const templatesDir = getTemplatesDir();
|
|
394
|
+
if (!templatesDir) return { status: "error", message: "Templates directory not found" };
|
|
395
|
+
const projectName = path.basename(rootDir);
|
|
396
|
+
const created = [];
|
|
397
|
+
const subdirs = ["specs", "docs", "prompts"];
|
|
398
|
+
for (const sub of subdirs) {
|
|
399
|
+
const templateSub = path.join(templatesDir, sub);
|
|
400
|
+
const projectSub = path.join(rootDir, ".paradigm", sub);
|
|
401
|
+
if (!fs.existsSync(templateSub)) continue;
|
|
402
|
+
if (!opts.dryRun) {
|
|
403
|
+
fs.mkdirSync(projectSub, { recursive: true });
|
|
404
|
+
}
|
|
405
|
+
try {
|
|
406
|
+
const entries = fs.readdirSync(templateSub, { withFileTypes: true });
|
|
407
|
+
for (const entry of entries) {
|
|
408
|
+
if (entry.isDirectory()) continue;
|
|
409
|
+
const dest = path.join(projectSub, entry.name);
|
|
410
|
+
if (!fs.existsSync(dest)) {
|
|
411
|
+
if (opts.dryRun) {
|
|
412
|
+
created.push(`${sub}/${entry.name}`);
|
|
413
|
+
continue;
|
|
414
|
+
}
|
|
415
|
+
let content = fs.readFileSync(path.join(templateSub, entry.name), "utf8");
|
|
416
|
+
content = content.replace(/\{\{PROJECT_NAME\}\}/g, projectName);
|
|
417
|
+
fs.writeFileSync(dest, content, "utf8");
|
|
418
|
+
created.push(`${sub}/${entry.name}`);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
} catch {
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
if (opts.dryRun) {
|
|
425
|
+
return { status: "skipped", message: `Would copy ${created.length} template file(s)` };
|
|
426
|
+
}
|
|
427
|
+
if (created.length === 0) return { status: "skipped", message: "No new templates to sync" };
|
|
428
|
+
return {
|
|
429
|
+
status: "applied",
|
|
430
|
+
message: `Synced ${created.length} template file(s)`,
|
|
431
|
+
filesCreated: created.map((f) => `.paradigm/${f}`)
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
};
|
|
435
|
+
var refreshHooks = {
|
|
436
|
+
id: "refresh-hooks",
|
|
437
|
+
introducedIn: "evergreen",
|
|
438
|
+
description: "Reinstall hook scripts from plugin",
|
|
439
|
+
category: "hook",
|
|
440
|
+
auto: true,
|
|
441
|
+
async check(rootDir) {
|
|
442
|
+
const hookLocations = [
|
|
443
|
+
path.join(rootDir, ".claude", "hooks"),
|
|
444
|
+
path.join(rootDir, ".cursor", "hooks")
|
|
445
|
+
];
|
|
446
|
+
for (const hookDir of hookLocations) {
|
|
447
|
+
if (!fs.existsSync(hookDir)) continue;
|
|
448
|
+
try {
|
|
449
|
+
const files = fs.readdirSync(hookDir);
|
|
450
|
+
if (files.some((f) => f.includes("paradigm"))) {
|
|
451
|
+
return { needed: false, reason: "Hooks are installed" };
|
|
452
|
+
}
|
|
453
|
+
} catch {
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
const claudeSettings = path.join(rootDir, ".claude", "settings.local.json");
|
|
457
|
+
if (fs.existsSync(claudeSettings)) {
|
|
458
|
+
try {
|
|
459
|
+
const settings = JSON.parse(fs.readFileSync(claudeSettings, "utf8"));
|
|
460
|
+
if (settings.hooks) {
|
|
461
|
+
return { needed: false, reason: "Plugin-managed hooks are configured" };
|
|
462
|
+
}
|
|
463
|
+
} catch {
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
return { needed: true, reason: "No paradigm hooks found" };
|
|
467
|
+
},
|
|
468
|
+
async apply(rootDir, opts) {
|
|
469
|
+
if (opts.dryRun) return { status: "skipped", message: "Would reinstall hooks" };
|
|
470
|
+
try {
|
|
471
|
+
const { hooksInstallCommand } = await import("./hooks-TCUHQMPF.js");
|
|
472
|
+
await hooksInstallCommand({ force: true });
|
|
473
|
+
return { status: "applied", message: "Hooks reinstalled" };
|
|
474
|
+
} catch (err) {
|
|
475
|
+
return { status: "error", message: `Hook install failed: ${err.message}` };
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
};
|
|
479
|
+
var migrations = [
|
|
480
|
+
// Pre-3.0 legacy
|
|
481
|
+
legacyFileToDirectory,
|
|
482
|
+
legacyScanIndexLocation,
|
|
483
|
+
// 3.0–3.5 foundation
|
|
484
|
+
addSpecsDirectory,
|
|
485
|
+
addDocsDirectory,
|
|
486
|
+
addPromptsDirectory,
|
|
487
|
+
addTagBankConfig,
|
|
488
|
+
addPurposeRequiredConfig,
|
|
489
|
+
addDisciplineConfig,
|
|
490
|
+
// 3.5–3.18 features
|
|
491
|
+
addWisdomDirectory,
|
|
492
|
+
addHistoryDirectory,
|
|
493
|
+
addLoreDirectory,
|
|
494
|
+
addTasksDirectory,
|
|
495
|
+
addPersonasDirectory,
|
|
496
|
+
addProtocolsDirectory,
|
|
497
|
+
addHabitsDirectory,
|
|
498
|
+
// 3.19+ refinements
|
|
499
|
+
migrateAssessmentsToLore,
|
|
500
|
+
addComponentTypesConfig,
|
|
501
|
+
// Evergreen
|
|
502
|
+
syncTemplates,
|
|
503
|
+
refreshHooks
|
|
504
|
+
];
|
|
505
|
+
|
|
506
|
+
// src/commands/migrate/detector.ts
|
|
507
|
+
var require2 = createRequire(import.meta.url);
|
|
508
|
+
var { version: CLI_VERSION } = require2("../package.json");
|
|
509
|
+
function loadMigrationState(rootDir) {
|
|
510
|
+
const p = path2.join(rootDir, ".paradigm", "migrate.yaml");
|
|
511
|
+
if (!fs2.existsSync(p)) return null;
|
|
512
|
+
try {
|
|
513
|
+
const content = fs2.readFileSync(p, "utf8");
|
|
514
|
+
return yaml2.load(content);
|
|
515
|
+
} catch {
|
|
516
|
+
return null;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
async function detectProjectState(rootDir) {
|
|
520
|
+
const paradigmDir = path2.join(rootDir, ".paradigm");
|
|
521
|
+
const state = loadMigrationState(rootDir);
|
|
522
|
+
const appliedIds = state?.applied.map((a) => a.id) ?? [];
|
|
523
|
+
let configVersion = "unknown";
|
|
524
|
+
const configPath = path2.join(paradigmDir, "config.yaml");
|
|
525
|
+
if (fs2.existsSync(configPath)) {
|
|
526
|
+
try {
|
|
527
|
+
const config = yaml2.load(fs2.readFileSync(configPath, "utf8"));
|
|
528
|
+
configVersion = String(config.version ?? "unknown");
|
|
529
|
+
} catch {
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
const expectedDirs = ["specs", "docs", "prompts", "lore", "tasks", "protocols", "personas"];
|
|
533
|
+
const missingDirectories = expectedDirs.filter((d) => !fs2.existsSync(path2.join(paradigmDir, d)));
|
|
534
|
+
const expectedConfigFields = ["discipline", "tag-bank", "purpose-required", "component_types"];
|
|
535
|
+
let missingConfigFields = [];
|
|
536
|
+
if (fs2.existsSync(configPath)) {
|
|
537
|
+
try {
|
|
538
|
+
const config = yaml2.load(fs2.readFileSync(configPath, "utf8"));
|
|
539
|
+
missingConfigFields = expectedConfigFields.filter((f) => {
|
|
540
|
+
const key = f.replace(/-/g, "_");
|
|
541
|
+
return !(f in config) && !(key in config) && !(f.replace(/_/g, "-") in config);
|
|
542
|
+
});
|
|
543
|
+
} catch {
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
const staleTemplates = [];
|
|
547
|
+
const templatesDir = getTemplatesDir();
|
|
548
|
+
if (templatesDir) {
|
|
549
|
+
const templateSubdirs = ["specs", "docs"];
|
|
550
|
+
for (const sub of templateSubdirs) {
|
|
551
|
+
const templateSub = path2.join(templatesDir, sub);
|
|
552
|
+
const projectSub = path2.join(paradigmDir, sub);
|
|
553
|
+
if (fs2.existsSync(templateSub) && fs2.existsSync(projectSub)) {
|
|
554
|
+
const templateFiles = fs2.readdirSync(templateSub).filter((f) => !fs2.statSync(path2.join(templateSub, f)).isDirectory());
|
|
555
|
+
for (const file of templateFiles) {
|
|
556
|
+
const projectFile = path2.join(projectSub, file);
|
|
557
|
+
if (!fs2.existsSync(projectFile)) {
|
|
558
|
+
staleTemplates.push(`${sub}/${file}`);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
const hooksOutdated = !fs2.existsSync(path2.join(rootDir, ".claude", "hooks"));
|
|
565
|
+
const pendingMigrations = [];
|
|
566
|
+
for (const migration of migrations) {
|
|
567
|
+
const isEvergreen = migration.introducedIn === "evergreen";
|
|
568
|
+
if (appliedIds.includes(migration.id) && !isEvergreen) continue;
|
|
569
|
+
const result = await migration.check(rootDir);
|
|
570
|
+
if (result.needed) {
|
|
571
|
+
pendingMigrations.push(migration);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
if (!state) {
|
|
575
|
+
}
|
|
576
|
+
return {
|
|
577
|
+
configVersion,
|
|
578
|
+
cliVersion: CLI_VERSION,
|
|
579
|
+
pendingMigrations,
|
|
580
|
+
appliedIds,
|
|
581
|
+
health: {
|
|
582
|
+
missingDirectories,
|
|
583
|
+
missingConfigFields,
|
|
584
|
+
staleTemplates,
|
|
585
|
+
hooksOutdated
|
|
586
|
+
}
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
async function bootstrapAppliedList(rootDir) {
|
|
590
|
+
const applied = [];
|
|
591
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
592
|
+
for (const migration of migrations) {
|
|
593
|
+
if (migration.introducedIn === "evergreen") continue;
|
|
594
|
+
const result = await migration.check(rootDir);
|
|
595
|
+
if (!result.needed) {
|
|
596
|
+
applied.push({ id: migration.id, appliedAt: now, cliVersion: CLI_VERSION });
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
return applied;
|
|
600
|
+
}
|
|
601
|
+
function getTemplatesDir() {
|
|
602
|
+
const __filename = new URL(import.meta.url).pathname;
|
|
603
|
+
const __dirname = path2.dirname(__filename);
|
|
604
|
+
const candidates = [
|
|
605
|
+
path2.join(__dirname, "..", "..", "..", "templates", "paradigm"),
|
|
606
|
+
path2.join(__dirname, "..", "..", "templates", "paradigm"),
|
|
607
|
+
path2.join(__dirname, "..", "templates", "paradigm")
|
|
608
|
+
];
|
|
609
|
+
for (const p of candidates) {
|
|
610
|
+
if (fs2.existsSync(p)) return p;
|
|
611
|
+
}
|
|
612
|
+
return null;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
// src/commands/migrate/runner.ts
|
|
616
|
+
import * as fs3 from "fs";
|
|
617
|
+
import * as path3 from "path";
|
|
618
|
+
import * as yaml3 from "js-yaml";
|
|
619
|
+
import { createRequire as createRequire2 } from "module";
|
|
620
|
+
var require3 = createRequire2(import.meta.url);
|
|
621
|
+
var { version: CLI_VERSION2 } = require3("../package.json");
|
|
622
|
+
async function getOrCreateState(rootDir) {
|
|
623
|
+
const existing = loadMigrationState(rootDir);
|
|
624
|
+
if (existing) return existing;
|
|
625
|
+
const applied = await bootstrapAppliedList(rootDir);
|
|
626
|
+
const state = {
|
|
627
|
+
version: "1.0",
|
|
628
|
+
cliVersion: CLI_VERSION2,
|
|
629
|
+
lastMigrated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
630
|
+
applied
|
|
631
|
+
};
|
|
632
|
+
return state;
|
|
633
|
+
}
|
|
634
|
+
function saveMigrationState(rootDir, state) {
|
|
635
|
+
const p = path3.join(rootDir, ".paradigm", "migrate.yaml");
|
|
636
|
+
fs3.mkdirSync(path3.dirname(p), { recursive: true });
|
|
637
|
+
state.cliVersion = CLI_VERSION2;
|
|
638
|
+
state.lastMigrated = (/* @__PURE__ */ new Date()).toISOString();
|
|
639
|
+
fs3.writeFileSync(p, yaml3.dump(state, {
|
|
640
|
+
indent: 2,
|
|
641
|
+
lineWidth: 120,
|
|
642
|
+
noRefs: true,
|
|
643
|
+
sortKeys: false,
|
|
644
|
+
quotingType: '"'
|
|
645
|
+
}), "utf8");
|
|
646
|
+
}
|
|
647
|
+
async function runMigrations(rootDir, pending, options, state) {
|
|
648
|
+
const summary = { applied: 0, skipped: 0, errors: 0, manual: 0, results: [] };
|
|
649
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
650
|
+
for (const migration of pending) {
|
|
651
|
+
if (!migration.auto) {
|
|
652
|
+
summary.manual++;
|
|
653
|
+
summary.results.push({ id: migration.id, result: { status: "skipped", message: "Manual review recommended" } });
|
|
654
|
+
continue;
|
|
655
|
+
}
|
|
656
|
+
try {
|
|
657
|
+
const result = await migration.apply(rootDir, options);
|
|
658
|
+
summary.results.push({ id: migration.id, result });
|
|
659
|
+
if (result.status === "applied") {
|
|
660
|
+
summary.applied++;
|
|
661
|
+
state.applied.push({ id: migration.id, appliedAt: now, cliVersion: CLI_VERSION2 });
|
|
662
|
+
} else if (result.status === "skipped") {
|
|
663
|
+
summary.skipped++;
|
|
664
|
+
} else {
|
|
665
|
+
summary.errors++;
|
|
666
|
+
}
|
|
667
|
+
} catch (err) {
|
|
668
|
+
summary.errors++;
|
|
669
|
+
summary.results.push({
|
|
670
|
+
id: migration.id,
|
|
671
|
+
result: { status: "error", message: err.message }
|
|
672
|
+
});
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
return summary;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
// src/commands/migrate/index.ts
|
|
679
|
+
async function migrateCommand(options = {}) {
|
|
680
|
+
const cwd = process.cwd();
|
|
681
|
+
const paradigmDir = path4.join(cwd, ".paradigm");
|
|
682
|
+
if (!fs4.existsSync(paradigmDir) || !fs4.statSync(paradigmDir).isDirectory()) {
|
|
683
|
+
if (fs4.existsSync(paradigmDir) && fs4.statSync(paradigmDir).isFile()) {
|
|
684
|
+
} else {
|
|
685
|
+
if (!options.quiet) {
|
|
686
|
+
console.log(chalk.yellow("\n No .paradigm/ directory found. Run `paradigm init` or `paradigm shift` first.\n"));
|
|
687
|
+
}
|
|
688
|
+
return;
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
const spinner = ora();
|
|
692
|
+
if (!options.quiet) {
|
|
693
|
+
spinner.start("Detecting project state...");
|
|
694
|
+
}
|
|
695
|
+
const detection = await detectProjectState(cwd);
|
|
696
|
+
if (!options.quiet) {
|
|
697
|
+
spinner.stop();
|
|
698
|
+
}
|
|
699
|
+
const state = await getOrCreateState(cwd);
|
|
700
|
+
let pending = detection.pendingMigrations;
|
|
701
|
+
if (options.only && options.only.length > 0) {
|
|
702
|
+
pending = pending.filter((m) => options.only.includes(m.id));
|
|
703
|
+
}
|
|
704
|
+
if (options.category) {
|
|
705
|
+
pending = pending.filter((m) => m.category === options.category);
|
|
706
|
+
}
|
|
707
|
+
if (options.force) {
|
|
708
|
+
const all = options.only ? migrations.filter((m) => options.only.includes(m.id)) : options.category ? migrations.filter((m) => m.category === options.category) : migrations;
|
|
709
|
+
pending = [];
|
|
710
|
+
for (const m of all) {
|
|
711
|
+
const result = await m.check(cwd);
|
|
712
|
+
if (result.needed || options.force) {
|
|
713
|
+
pending.push(m);
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
const autoMigrations = pending.filter((m) => m.auto);
|
|
718
|
+
const manualMigrations = pending.filter((m) => !m.auto);
|
|
719
|
+
if (options.list) {
|
|
720
|
+
printMigrationList(state, detection, options);
|
|
721
|
+
return;
|
|
722
|
+
}
|
|
723
|
+
if (options.quiet) {
|
|
724
|
+
if (autoMigrations.length === 0) return;
|
|
725
|
+
const summary = await runMigrations(cwd, autoMigrations, options, state);
|
|
726
|
+
saveMigrationState(cwd, state);
|
|
727
|
+
return summary;
|
|
728
|
+
}
|
|
729
|
+
const existingState = loadMigrationState(cwd);
|
|
730
|
+
console.log(chalk.blue("\n\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"));
|
|
731
|
+
console.log(chalk.blue("\u2502") + chalk.white.bold(" paradigm migrate ") + chalk.blue("\u2502"));
|
|
732
|
+
console.log(chalk.blue("\u2502") + chalk.gray(" Bring your project up to date ") + chalk.blue("\u2502"));
|
|
733
|
+
console.log(chalk.blue("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n"));
|
|
734
|
+
console.log(chalk.white(` Project: ${chalk.cyan(path4.basename(cwd))}`));
|
|
735
|
+
console.log(chalk.white(` Config version: ${chalk.cyan(detection.configVersion)}`));
|
|
736
|
+
console.log(chalk.white(` CLI version: ${chalk.cyan(detection.cliVersion)}`));
|
|
737
|
+
console.log(chalk.white(` Last migrated: ${chalk.cyan(existingState?.lastMigrated ?? "never")}`));
|
|
738
|
+
console.log("");
|
|
739
|
+
if (pending.length === 0) {
|
|
740
|
+
console.log(chalk.green(" All migrations are up to date.\n"));
|
|
741
|
+
if (!existingState) {
|
|
742
|
+
saveMigrationState(cwd, state);
|
|
743
|
+
if (options.verbose) {
|
|
744
|
+
console.log(chalk.gray(" Created .paradigm/migrate.yaml with bootstrap state.\n"));
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
return;
|
|
748
|
+
}
|
|
749
|
+
if (options.verbose) {
|
|
750
|
+
const h = detection.health;
|
|
751
|
+
if (h.missingDirectories.length > 0) {
|
|
752
|
+
console.log(chalk.yellow(` Missing directories: ${h.missingDirectories.join(", ")}`));
|
|
753
|
+
}
|
|
754
|
+
if (h.missingConfigFields.length > 0) {
|
|
755
|
+
console.log(chalk.yellow(` Missing config fields: ${h.missingConfigFields.join(", ")}`));
|
|
756
|
+
}
|
|
757
|
+
if (h.staleTemplates.length > 0) {
|
|
758
|
+
console.log(chalk.yellow(` New templates available: ${h.staleTemplates.join(", ")}`));
|
|
759
|
+
}
|
|
760
|
+
if (h.hooksOutdated) {
|
|
761
|
+
console.log(chalk.yellow(" Hooks need installation"));
|
|
762
|
+
}
|
|
763
|
+
console.log("");
|
|
764
|
+
}
|
|
765
|
+
const autoCount = autoMigrations.length;
|
|
766
|
+
const manualCount = manualMigrations.length;
|
|
767
|
+
console.log(chalk.white(` Pending Migrations (${autoCount} auto${manualCount > 0 ? `, ${manualCount} manual` : ""})`));
|
|
768
|
+
console.log(chalk.gray(" " + "\u2500".repeat(49)));
|
|
769
|
+
if (autoMigrations.length > 0) {
|
|
770
|
+
console.log(chalk.white(" AUTO:"));
|
|
771
|
+
for (const m of autoMigrations) {
|
|
772
|
+
const idPad = m.id.padEnd(30);
|
|
773
|
+
console.log(chalk.green(" \u2713 ") + chalk.white(idPad) + chalk.gray(m.description));
|
|
774
|
+
if (options.verbose) {
|
|
775
|
+
const checkResult = await m.check(cwd);
|
|
776
|
+
if (checkResult.details) {
|
|
777
|
+
for (const d of checkResult.details) {
|
|
778
|
+
console.log(chalk.gray(" \u21B3 ") + chalk.gray(d));
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
if (manualMigrations.length > 0) {
|
|
785
|
+
console.log("");
|
|
786
|
+
console.log(chalk.white(" MANUAL (review recommended):"));
|
|
787
|
+
for (const m of manualMigrations) {
|
|
788
|
+
const idPad = m.id.padEnd(30);
|
|
789
|
+
console.log(chalk.yellow(" \u25CB ") + chalk.white(idPad) + chalk.gray(m.description));
|
|
790
|
+
const checkResult = await m.check(cwd);
|
|
791
|
+
if (checkResult.details) {
|
|
792
|
+
for (const d of checkResult.details) {
|
|
793
|
+
console.log(chalk.gray(" \u2192 ") + chalk.cyan(d));
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
console.log("");
|
|
799
|
+
if (options.dryRun) {
|
|
800
|
+
console.log(chalk.yellow(" Dry run \u2014 no changes applied.\n"));
|
|
801
|
+
return;
|
|
802
|
+
}
|
|
803
|
+
if (autoMigrations.length > 0) {
|
|
804
|
+
if (!options.apply) {
|
|
805
|
+
const prompts = (await import("prompts")).default;
|
|
806
|
+
const { confirm } = await prompts({
|
|
807
|
+
type: "confirm",
|
|
808
|
+
name: "confirm",
|
|
809
|
+
message: `Apply ${autoMigrations.length} automatic migration(s)?`,
|
|
810
|
+
initial: true
|
|
811
|
+
});
|
|
812
|
+
if (!confirm) {
|
|
813
|
+
console.log(chalk.gray("\n Skipped. Run with --apply to auto-apply.\n"));
|
|
814
|
+
return;
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
spinner.start(`Applying ${autoMigrations.length} migration(s)...`);
|
|
818
|
+
const summary = await runMigrations(cwd, autoMigrations, options, state);
|
|
819
|
+
spinner.stop();
|
|
820
|
+
for (const { id, result } of summary.results) {
|
|
821
|
+
if (result.status === "applied") {
|
|
822
|
+
console.log(chalk.green(" \u2713 ") + chalk.white(id) + chalk.gray(` \u2014 ${result.message}`));
|
|
823
|
+
} else if (result.status === "error") {
|
|
824
|
+
console.log(chalk.red(" \u2717 ") + chalk.white(id) + chalk.gray(` \u2014 ${result.message}`));
|
|
825
|
+
} else {
|
|
826
|
+
console.log(chalk.yellow(" \u25CB ") + chalk.white(id) + chalk.gray(` \u2014 ${result.message}`));
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
console.log("");
|
|
830
|
+
console.log(chalk.green(` Applied ${summary.applied} migration(s).`));
|
|
831
|
+
if (summary.errors > 0) {
|
|
832
|
+
console.log(chalk.red(` ${summary.errors} error(s).`));
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
saveMigrationState(cwd, state);
|
|
836
|
+
console.log(chalk.gray(" Updated .paradigm/migrate.yaml"));
|
|
837
|
+
console.log("");
|
|
838
|
+
}
|
|
839
|
+
function printMigrationList(state, detection, options) {
|
|
840
|
+
const appliedIds = new Set(state.applied.map((a) => a.id));
|
|
841
|
+
const pendingIds = new Set(detection.pendingMigrations.map((m) => m.id));
|
|
842
|
+
console.log(chalk.blue("\n All Migrations"));
|
|
843
|
+
console.log(chalk.gray(" " + "\u2500".repeat(70)));
|
|
844
|
+
console.log(
|
|
845
|
+
chalk.gray(" Status ") + chalk.gray("ID".padEnd(34)) + chalk.gray("Version".padEnd(12)) + chalk.gray("Category")
|
|
846
|
+
);
|
|
847
|
+
console.log(chalk.gray(" " + "\u2500".repeat(70)));
|
|
848
|
+
for (const m of migrations) {
|
|
849
|
+
let status;
|
|
850
|
+
if (appliedIds.has(m.id)) {
|
|
851
|
+
status = chalk.green(" \u2713 ");
|
|
852
|
+
} else if (pendingIds.has(m.id)) {
|
|
853
|
+
status = m.auto ? chalk.yellow(" \u25CF ") : chalk.yellow(" \u25CB ");
|
|
854
|
+
} else {
|
|
855
|
+
status = chalk.gray(" - ");
|
|
856
|
+
}
|
|
857
|
+
const id = m.id.padEnd(34);
|
|
858
|
+
const version = (m.introducedIn === "evergreen" ? "evergreen" : m.introducedIn).padEnd(12);
|
|
859
|
+
const cat = m.category;
|
|
860
|
+
console.log(status + chalk.white(id) + chalk.gray(version) + chalk.gray(cat));
|
|
861
|
+
if (options.verbose) {
|
|
862
|
+
console.log(chalk.gray(` ${m.description}`));
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
console.log("");
|
|
866
|
+
console.log(chalk.gray(` \u2713 = applied \u25CF = pending (auto) \u25CB = pending (manual) - = not needed`));
|
|
867
|
+
console.log("");
|
|
868
|
+
}
|
|
869
|
+
export {
|
|
870
|
+
migrateCommand
|
|
871
|
+
};
|