@jakkrichm/create-nexus-devflow 2.0.13 → 2.0.14
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/bin/create-nexus-devflow.d.ts +2 -0
- package/dist/bin/create-nexus-devflow.js +233 -0
- package/dist/bin/create-nexus-devflow.js.map +1 -0
- package/dist/lib/starter-templates.d.ts +6 -0
- package/dist/lib/starter-templates.js +8 -0
- package/dist/lib/starter-templates.js.map +1 -0
- package/dist/lib/update.d.ts +68 -0
- package/dist/lib/update.js +325 -0
- package/dist/lib/update.js.map +1 -0
- package/dist/scripts/clean-template.d.ts +1 -0
- package/dist/scripts/clean-template.js +14 -0
- package/dist/scripts/clean-template.js.map +1 -0
- package/dist/scripts/prepare-template.d.ts +1 -0
- package/dist/scripts/prepare-template.js +89 -0
- package/dist/scripts/prepare-template.js.map +1 -0
- package/package.json +11 -9
- package/template/.agents/skills/00-discover/SKILL.md +2 -0
- package/template/.agents/skills/60-report/SKILL.md +21 -75
- package/template/.agents/skills/70-release/SKILL.md +5 -4
- package/template/.agents/skills/check/SKILL.md +68 -0
- package/template/.agents/skills/complete/SKILL.md +73 -0
- package/template/.agents/skills/devflow/SKILL.md +89 -80
- package/template/.agents/skills/idea/SKILL.md +57 -0
- package/template/.agents/skills/implement/SKILL.md +64 -0
- package/template/.agents/skills/overview/SKILL.md +114 -0
- package/template/.agents/skills/report-html/SKILL.md +45 -0
- package/template/.agents/skills/spec/SKILL.md +105 -0
- package/template/.claude/skills/00-discover/SKILL.md +2 -0
- package/template/.claude/skills/60-report/SKILL.md +21 -75
- package/template/.claude/skills/70-release/SKILL.md +5 -4
- package/template/.claude/skills/check/SKILL.md +68 -0
- package/template/.claude/skills/complete/SKILL.md +73 -0
- package/template/.claude/skills/devflow/SKILL.md +89 -80
- package/template/.claude/skills/idea/SKILL.md +57 -0
- package/template/.claude/skills/implement/SKILL.md +64 -0
- package/template/.claude/skills/overview/SKILL.md +114 -0
- package/template/.claude/skills/report-html/SKILL.md +45 -0
- package/template/.claude/skills/spec/SKILL.md +105 -0
- package/template/AGENTS.md +71 -74
- package/template/devflow/context/ai-interaction.md +1 -0
- package/template/devflow/context/coding-standards.md +31 -18
- package/template/devflow/context/current-stage.md +3 -3
- package/template/devflow/context/findings.md +10 -6
- package/template/devflow/context/project-overview.md +29 -11
- package/template/devflow/discoveries/.gitkeep +0 -0
- package/template/devflow/history/HISTORY.md +4 -0
- package/template/devflow/ideas.md +15 -0
- package/template/devflow/runs/.gitkeep +0 -0
- package/bin/create-nexus-devflow.js +0 -287
- package/lib/starter-templates.js +0 -111
- package/lib/update.js +0 -393
- package/template/.nexus/nexus-devflow.json +0 -44
package/lib/update.js
DELETED
|
@@ -1,393 +0,0 @@
|
|
|
1
|
-
const crypto = require("node:crypto");
|
|
2
|
-
const fs = require("node:fs/promises");
|
|
3
|
-
const path = require("node:path");
|
|
4
|
-
|
|
5
|
-
const CONTROL_DIR = ".nexus";
|
|
6
|
-
const MANIFEST_PATH = `${CONTROL_DIR}/nexus-devflow.json`;
|
|
7
|
-
const MANIFEST_SCHEMA_VERSION = 1;
|
|
8
|
-
const MANAGED_ROOTS = {
|
|
9
|
-
common: ["AGENTS.md", "CLAUDE.md", "devflow", "LICENSE"],
|
|
10
|
-
codex: [".agents/skills"],
|
|
11
|
-
antigravity: [".agents/skills", ".agent/workflows"],
|
|
12
|
-
claude: [".claude/skills"]
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
function adapterListFromMode(adapter) {
|
|
16
|
-
if (adapter === "both" || adapter === "all") {
|
|
17
|
-
return ["codex", "claude"];
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
if (adapter === "antigravity") {
|
|
21
|
-
return ["codex"];
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
if (adapter === "codex" || adapter === "claude") {
|
|
25
|
-
return [adapter];
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
throw new Error(`Unknown adapter mode: ${adapter}`);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function createManifest(version, adapters, templateFiles) {
|
|
32
|
-
const managedFiles = {};
|
|
33
|
-
|
|
34
|
-
for (const [relativePath, file] of [...templateFiles.entries()].sort(([a], [b]) =>
|
|
35
|
-
a.localeCompare(b)
|
|
36
|
-
)) {
|
|
37
|
-
managedFiles[relativePath] = file.hash;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
return {
|
|
41
|
-
schemaVersion: MANIFEST_SCHEMA_VERSION,
|
|
42
|
-
name: "nexus-devflow",
|
|
43
|
-
package: "@jakkrichm/create-nexus-devflow",
|
|
44
|
-
version,
|
|
45
|
-
repository: "https://github.com/Jakkrich/nexus-devflow",
|
|
46
|
-
artifactLanguage: "th",
|
|
47
|
-
adapters: [...adapters].sort(),
|
|
48
|
-
workspace: {
|
|
49
|
-
contextDir: "devflow/context",
|
|
50
|
-
historyDir: "devflow/history",
|
|
51
|
-
referenceDir: "devflow/reference",
|
|
52
|
-
runsDir: "devflow/runs",
|
|
53
|
-
discoveriesDir: "devflow/discoveries"
|
|
54
|
-
},
|
|
55
|
-
lifecycle: {
|
|
56
|
-
mainlineStages: [
|
|
57
|
-
"00-discover",
|
|
58
|
-
"10-define",
|
|
59
|
-
"20-spec",
|
|
60
|
-
"30-plan",
|
|
61
|
-
"40-implement",
|
|
62
|
-
"50-verify",
|
|
63
|
-
"60-report",
|
|
64
|
-
"70-release"
|
|
65
|
-
],
|
|
66
|
-
companionCommands: [
|
|
67
|
-
"goal",
|
|
68
|
-
"brainstorm",
|
|
69
|
-
"research",
|
|
70
|
-
"debug",
|
|
71
|
-
"prd",
|
|
72
|
-
"issue-triage",
|
|
73
|
-
"security-review",
|
|
74
|
-
"check-for-updates",
|
|
75
|
-
"help"
|
|
76
|
-
]
|
|
77
|
-
},
|
|
78
|
-
managedFiles
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
async function collectManagedTemplateFiles(templateRoot, adapters) {
|
|
83
|
-
const files = new Map();
|
|
84
|
-
const roots = [
|
|
85
|
-
...MANAGED_ROOTS.common,
|
|
86
|
-
...adapters.flatMap((adapter) => MANAGED_ROOTS[adapter] || [])
|
|
87
|
-
];
|
|
88
|
-
|
|
89
|
-
for (const relativeRoot of roots) {
|
|
90
|
-
const sourceRoot = path.join(templateRoot, ...relativeRoot.split("/"));
|
|
91
|
-
await collectSourceFiles(sourceRoot, relativeRoot, files);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
return files;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
async function collectSourceFiles(sourcePath, relativePath, files) {
|
|
98
|
-
try {
|
|
99
|
-
const stats = await fs.lstat(sourcePath);
|
|
100
|
-
|
|
101
|
-
if (stats.isSymbolicLink()) {
|
|
102
|
-
throw new Error(`Managed template path cannot be a symbolic link: ${relativePath}`);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
if (stats.isDirectory()) {
|
|
106
|
-
const children = (await fs.readdir(sourcePath)).sort();
|
|
107
|
-
|
|
108
|
-
for (const child of children) {
|
|
109
|
-
await collectSourceFiles(
|
|
110
|
-
path.join(sourcePath, child),
|
|
111
|
-
`${relativePath}/${child}`,
|
|
112
|
-
files
|
|
113
|
-
);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
return;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
if (!stats.isFile()) {
|
|
120
|
-
throw new Error(`Managed template path is not a regular file: ${relativePath}`);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
files.set(relativePath, {
|
|
124
|
-
source: sourcePath,
|
|
125
|
-
hash: await hashFile(sourcePath)
|
|
126
|
-
});
|
|
127
|
-
} catch (err) {
|
|
128
|
-
if (err.code === "ENOENT") {
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
throw err;
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
async function readManifest(targetDir) {
|
|
136
|
-
const manifestFile = targetPath(targetDir, MANIFEST_PATH);
|
|
137
|
-
await assertNoSymlinkParents(targetDir, MANIFEST_PATH);
|
|
138
|
-
|
|
139
|
-
try {
|
|
140
|
-
const content = await fs.readFile(manifestFile, "utf8");
|
|
141
|
-
const data = JSON.parse(content);
|
|
142
|
-
|
|
143
|
-
if (!data || data.schemaVersion !== MANIFEST_SCHEMA_VERSION) {
|
|
144
|
-
throw new Error("Unsupported manifest schema version.");
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
return data;
|
|
148
|
-
} catch (error) {
|
|
149
|
-
if (error.code === "ENOENT") {
|
|
150
|
-
return null;
|
|
151
|
-
}
|
|
152
|
-
throw error;
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
async function prepareUpdate({ targetDir, templateRoot, version, adapter }) {
|
|
157
|
-
const requestedAdapters = new Set(adapterListFromMode(adapter));
|
|
158
|
-
const previousManifest = await readManifest(targetDir);
|
|
159
|
-
|
|
160
|
-
const activeAdapters = new Set([
|
|
161
|
-
...requestedAdapters,
|
|
162
|
-
...(previousManifest?.adapters || [])
|
|
163
|
-
]);
|
|
164
|
-
|
|
165
|
-
const templateFiles = await collectManagedTemplateFiles(
|
|
166
|
-
templateRoot,
|
|
167
|
-
[...activeAdapters]
|
|
168
|
-
);
|
|
169
|
-
const nextManifest = createManifest(version, activeAdapters, templateFiles);
|
|
170
|
-
|
|
171
|
-
const createList = [];
|
|
172
|
-
const updateList = [];
|
|
173
|
-
const conflictList = [];
|
|
174
|
-
|
|
175
|
-
for (const [relativePath, templateFile] of templateFiles) {
|
|
176
|
-
await assertNoSymlinkParents(targetDir, relativePath);
|
|
177
|
-
const targetFile = targetPath(targetDir, relativePath);
|
|
178
|
-
|
|
179
|
-
let targetStats = null;
|
|
180
|
-
try {
|
|
181
|
-
targetStats = await fs.lstat(targetFile);
|
|
182
|
-
} catch (error) {
|
|
183
|
-
if (error.code !== "ENOENT") {
|
|
184
|
-
throw error;
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
if (!targetStats) {
|
|
189
|
-
createList.push(relativePath);
|
|
190
|
-
continue;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
if (targetStats.isSymbolicLink()) {
|
|
194
|
-
conflictList.push({
|
|
195
|
-
relativePath,
|
|
196
|
-
reason: "symlink",
|
|
197
|
-
detail: "Target path is a symbolic link."
|
|
198
|
-
});
|
|
199
|
-
continue;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
if (!targetStats.isFile()) {
|
|
203
|
-
conflictList.push({
|
|
204
|
-
relativePath,
|
|
205
|
-
reason: "not_file",
|
|
206
|
-
detail: "Target path exists but is not a regular file."
|
|
207
|
-
});
|
|
208
|
-
continue;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
const currentHash = await hashFile(targetFile);
|
|
212
|
-
if (currentHash === templateFile.hash) {
|
|
213
|
-
continue;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
const recordedHash = previousManifest?.managedFiles?.[relativePath];
|
|
217
|
-
if (recordedHash && recordedHash === currentHash) {
|
|
218
|
-
updateList.push(relativePath);
|
|
219
|
-
continue;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
conflictList.push({
|
|
223
|
-
relativePath,
|
|
224
|
-
reason: "customized",
|
|
225
|
-
detail: recordedHash
|
|
226
|
-
? "File was modified locally since last install."
|
|
227
|
-
: "File exists in project and differs from template."
|
|
228
|
-
});
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
const orphanedFiles = [];
|
|
232
|
-
if (previousManifest) {
|
|
233
|
-
for (const [relativePath, recordedHash] of Object.entries(
|
|
234
|
-
previousManifest.managedFiles
|
|
235
|
-
)) {
|
|
236
|
-
if (templateFiles.has(relativePath)) {
|
|
237
|
-
continue;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
await assertNoSymlinkParents(targetDir, relativePath);
|
|
241
|
-
const targetFile = targetPath(targetDir, relativePath);
|
|
242
|
-
|
|
243
|
-
let targetStats = null;
|
|
244
|
-
try {
|
|
245
|
-
targetStats = await fs.lstat(targetFile);
|
|
246
|
-
} catch (error) {
|
|
247
|
-
if (error.code !== "ENOENT") {
|
|
248
|
-
throw error;
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
if (!targetStats || !targetStats.isFile()) {
|
|
253
|
-
continue;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
const currentHash = await hashFile(targetFile);
|
|
257
|
-
if (currentHash === recordedHash) {
|
|
258
|
-
orphanedFiles.push(relativePath);
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
return {
|
|
264
|
-
targetDir,
|
|
265
|
-
templateRoot,
|
|
266
|
-
previousManifest,
|
|
267
|
-
nextManifest,
|
|
268
|
-
activeAdapters: [...activeAdapters].sort(),
|
|
269
|
-
templateFiles,
|
|
270
|
-
createList,
|
|
271
|
-
updateList,
|
|
272
|
-
conflictList,
|
|
273
|
-
orphanedFiles
|
|
274
|
-
};
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
async function applyPreparedUpdate(prepared, { replaceConflicts = false } = {}) {
|
|
278
|
-
if (prepared.conflictList.length > 0 && !replaceConflicts) {
|
|
279
|
-
throw new Error(
|
|
280
|
-
`Cannot apply update with ${prepared.conflictList.length} conflict(s). Pass force/replace option or resolve conflicts.`
|
|
281
|
-
);
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
const writeTargets = [
|
|
285
|
-
...prepared.createList,
|
|
286
|
-
...prepared.updateList,
|
|
287
|
-
...(replaceConflicts ? prepared.conflictList.map((item) => item.relativePath) : [])
|
|
288
|
-
];
|
|
289
|
-
|
|
290
|
-
let appliedCount = 0;
|
|
291
|
-
for (const relativePath of writeTargets) {
|
|
292
|
-
const templateFile = prepared.templateFiles.get(relativePath);
|
|
293
|
-
if (!templateFile) {
|
|
294
|
-
continue;
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
await copyFileAtomic(prepared.targetDir, relativePath, templateFile.source);
|
|
298
|
-
appliedCount++;
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
let removedCount = 0;
|
|
302
|
-
for (const relativePath of prepared.orphanedFiles) {
|
|
303
|
-
const fileToRemove = targetPath(prepared.targetDir, relativePath);
|
|
304
|
-
try {
|
|
305
|
-
await fs.unlink(fileToRemove);
|
|
306
|
-
removedCount++;
|
|
307
|
-
|
|
308
|
-
let parentDir = path.dirname(fileToRemove);
|
|
309
|
-
while (parentDir !== prepared.targetDir && parentDir.startsWith(prepared.targetDir)) {
|
|
310
|
-
try {
|
|
311
|
-
const entries = await fs.readdir(parentDir);
|
|
312
|
-
if (entries.length === 0) {
|
|
313
|
-
await fs.rmdir(parentDir);
|
|
314
|
-
parentDir = path.dirname(parentDir);
|
|
315
|
-
} else {
|
|
316
|
-
break;
|
|
317
|
-
}
|
|
318
|
-
} catch {
|
|
319
|
-
break;
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
} catch (error) {
|
|
323
|
-
if (error.code !== "ENOENT") {
|
|
324
|
-
throw error;
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
await writeInstallManifest(prepared.targetDir, prepared.nextManifest);
|
|
330
|
-
|
|
331
|
-
return {
|
|
332
|
-
appliedCount,
|
|
333
|
-
removedCount
|
|
334
|
-
};
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
async function writeInstallManifest(targetDir, manifest) {
|
|
338
|
-
const manifestFile = targetPath(targetDir, MANIFEST_PATH);
|
|
339
|
-
await fs.mkdir(path.dirname(manifestFile), { recursive: true });
|
|
340
|
-
await fs.writeFile(manifestFile, `${JSON.stringify(manifest, null, 2)}\n`, "utf8");
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
async function copyFileAtomic(targetDir, relativePath, sourcePath) {
|
|
344
|
-
await assertNoSymlinkParents(targetDir, relativePath);
|
|
345
|
-
const destinationPath = targetPath(targetDir, relativePath);
|
|
346
|
-
await fs.mkdir(path.dirname(destinationPath), { recursive: true });
|
|
347
|
-
|
|
348
|
-
const tempPath = `${destinationPath}.tmp-${process.pid}-${Date.now()}`;
|
|
349
|
-
await fs.copyFile(sourcePath, tempPath);
|
|
350
|
-
await fs.rename(tempPath, destinationPath);
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
function targetPath(targetDir, relativePath) {
|
|
354
|
-
return path.resolve(targetDir, ...relativePath.split("/"));
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
async function assertNoSymlinkParents(targetDir, relativePath) {
|
|
358
|
-
const parts = relativePath.split("/");
|
|
359
|
-
let current = path.resolve(targetDir);
|
|
360
|
-
|
|
361
|
-
for (let i = 0; i < parts.length - 1; i++) {
|
|
362
|
-
current = path.join(current, parts[i]);
|
|
363
|
-
try {
|
|
364
|
-
const stats = await fs.lstat(current);
|
|
365
|
-
if (stats.isSymbolicLink()) {
|
|
366
|
-
throw new Error(
|
|
367
|
-
`Target sub-path "${parts.slice(0, i + 1).join("/")}" is a symbolic link.`
|
|
368
|
-
);
|
|
369
|
-
}
|
|
370
|
-
} catch (error) {
|
|
371
|
-
if (error.code === "ENOENT") {
|
|
372
|
-
break;
|
|
373
|
-
}
|
|
374
|
-
throw error;
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
async function hashFile(filePath) {
|
|
380
|
-
const content = await fs.readFile(filePath);
|
|
381
|
-
return crypto.createHash("sha256").update(content).digest("hex");
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
module.exports = {
|
|
385
|
-
CONTROL_DIR,
|
|
386
|
-
MANIFEST_PATH,
|
|
387
|
-
adapterListFromMode,
|
|
388
|
-
applyPreparedUpdate,
|
|
389
|
-
createManifest,
|
|
390
|
-
prepareUpdate,
|
|
391
|
-
readManifest,
|
|
392
|
-
writeInstallManifest
|
|
393
|
-
};
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"schemaVersion": 1,
|
|
3
|
-
"name": "nexus-devflow",
|
|
4
|
-
"package": "@jakkrichm/create-nexus-devflow",
|
|
5
|
-
"version": "2.0.6",
|
|
6
|
-
"repository": "https://github.com/Jakkrich/nexus-devflow",
|
|
7
|
-
"artifactLanguage": "th",
|
|
8
|
-
"adapters": [
|
|
9
|
-
"claude",
|
|
10
|
-
"codex"
|
|
11
|
-
],
|
|
12
|
-
"workspace": {
|
|
13
|
-
"contextDir": "devflow/context",
|
|
14
|
-
"historyDir": "devflow/history",
|
|
15
|
-
"referenceDir": "devflow/reference",
|
|
16
|
-
"runsDir": "devflow/runs",
|
|
17
|
-
"discoveriesDir": "devflow/discoveries"
|
|
18
|
-
},
|
|
19
|
-
"lifecycle": {
|
|
20
|
-
"mainlineStages": [
|
|
21
|
-
"00-discover",
|
|
22
|
-
"10-define",
|
|
23
|
-
"20-spec",
|
|
24
|
-
"30-plan",
|
|
25
|
-
"40-implement",
|
|
26
|
-
"50-verify",
|
|
27
|
-
"60-report",
|
|
28
|
-
"70-release"
|
|
29
|
-
],
|
|
30
|
-
"companionCommands": [
|
|
31
|
-
"goal",
|
|
32
|
-
"brainstorm",
|
|
33
|
-
"research",
|
|
34
|
-
"debug",
|
|
35
|
-
"prd",
|
|
36
|
-
"issue-triage",
|
|
37
|
-
"security-review",
|
|
38
|
-
"wiki",
|
|
39
|
-
"check-for-updates",
|
|
40
|
-
"help"
|
|
41
|
-
]
|
|
42
|
-
},
|
|
43
|
-
"installedAt": "2026-08-17T00:00:00.000Z"
|
|
44
|
-
}
|