codebyplan 1.13.60 → 1.13.62

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.
Files changed (65) hide show
  1. package/dist/ci.js +518 -0
  2. package/dist/cli.js +427 -359
  3. package/package.json +12 -2
  4. package/templates/agents/cbp-e2e-maestro.md +1 -1
  5. package/templates/agents/cbp-e2e-playwright.md +1 -1
  6. package/templates/agents/cbp-e2e-tauri.md +1 -1
  7. package/templates/agents/cbp-e2e-vscode.md +1 -1
  8. package/templates/agents/cbp-e2e-xcuitest.md +1 -1
  9. package/templates/rules/effort-and-ultracode.md +70 -0
  10. package/templates/rules/model-invocation-convention.md +1 -0
  11. package/templates/rules/workflow-orchestration.md +59 -0
  12. package/templates/skills/cbp-build-cc-agent/SKILL.md +1 -0
  13. package/templates/skills/cbp-build-cc-claude-file/SKILL.md +1 -0
  14. package/templates/skills/cbp-build-cc-mode/SKILL.md +25 -17
  15. package/templates/skills/cbp-build-cc-rule/SKILL.md +1 -0
  16. package/templates/skills/cbp-build-cc-settings/SKILL.md +1 -0
  17. package/templates/skills/cbp-build-cc-settings/reference/settings-fields.md +1 -1
  18. package/templates/skills/cbp-build-cc-skill/SKILL.md +2 -1
  19. package/templates/skills/cbp-build-cc-skill/reference/frontmatter-fields.md +1 -1
  20. package/templates/skills/cbp-build-cc-skill/scripts/validate-skill.sh +12 -9
  21. package/templates/skills/cbp-checkpoint-check/SKILL.md +5 -0
  22. package/templates/skills/cbp-checkpoint-complete/SKILL.md +1 -0
  23. package/templates/skills/cbp-checkpoint-create/SKILL.md +1 -0
  24. package/templates/skills/cbp-checkpoint-end/SKILL.md +5 -0
  25. package/templates/skills/cbp-checkpoint-plan/SKILL.md +5 -0
  26. package/templates/skills/cbp-checkpoint-start/SKILL.md +1 -0
  27. package/templates/skills/cbp-checkpoint-update/SKILL.md +2 -1
  28. package/templates/skills/cbp-clear-continue/SKILL.md +2 -1
  29. package/templates/skills/cbp-clear-prep/SKILL.md +2 -1
  30. package/templates/skills/cbp-finalize/SKILL.md +1 -0
  31. package/templates/skills/cbp-frontend-design/SKILL.md +1 -0
  32. package/templates/skills/cbp-frontend-ui/SKILL.md +2 -1
  33. package/templates/skills/cbp-frontend-ux/SKILL.md +2 -1
  34. package/templates/skills/cbp-git-branch-feat-create/SKILL.md +1 -0
  35. package/templates/skills/cbp-git-commit/SKILL.md +1 -0
  36. package/templates/skills/cbp-map-architecture/SKILL.md +5 -0
  37. package/templates/skills/cbp-merge-main/SKILL.md +1 -0
  38. package/templates/skills/cbp-refresh-arch-map/SKILL.md +1 -0
  39. package/templates/skills/cbp-round-build/SKILL.md +5 -0
  40. package/templates/skills/cbp-round-complete/SKILL.md +1 -0
  41. package/templates/skills/cbp-round-plan/SKILL.md +5 -0
  42. package/templates/skills/cbp-session-end/SKILL.md +2 -1
  43. package/templates/skills/cbp-session-start/SKILL.md +1 -0
  44. package/templates/skills/cbp-setup-cd/SKILL.md +2 -1
  45. package/templates/skills/cbp-setup-ci/SKILL.md +2 -1
  46. package/templates/skills/cbp-setup-e2e/SKILL.md +2 -1
  47. package/templates/skills/cbp-setup-eslint/SKILL.md +2 -1
  48. package/templates/skills/cbp-ship/SKILL.md +5 -0
  49. package/templates/skills/cbp-ship-configure/SKILL.md +2 -1
  50. package/templates/skills/cbp-ship-main/SKILL.md +1 -0
  51. package/templates/skills/cbp-standalone-task-check/SKILL.md +1 -0
  52. package/templates/skills/cbp-standalone-task-complete/SKILL.md +1 -0
  53. package/templates/skills/cbp-standalone-task-create/SKILL.md +2 -1
  54. package/templates/skills/cbp-standalone-task-start/SKILL.md +2 -1
  55. package/templates/skills/cbp-standalone-task-testing/SKILL.md +2 -1
  56. package/templates/skills/cbp-stripe/SKILL.md +2 -1
  57. package/templates/skills/cbp-supabase-branch-check/SKILL.md +2 -1
  58. package/templates/skills/cbp-supabase-migrate/SKILL.md +1 -0
  59. package/templates/skills/cbp-supabase-setup/SKILL.md +2 -1
  60. package/templates/skills/cbp-task-create/SKILL.md +2 -1
  61. package/templates/skills/cbp-task-start/SKILL.md +1 -0
  62. package/templates/skills/cbp-todo/SKILL.md +2 -1
  63. package/templates/skills/cbp-verify/SKILL.md +5 -0
  64. package/templates/skills/supabase/SKILL.md +2 -0
  65. package/templates/skills/supabase-postgres-best-practices/SKILL.md +2 -0
package/dist/ci.js ADDED
@@ -0,0 +1,518 @@
1
+ // src/lib/repo-reader.ts
2
+ import * as fsPromises from "node:fs/promises";
3
+ import * as path from "node:path";
4
+ var LocalFsReader = class {
5
+ constructor(rootDir) {
6
+ this.rootDir = rootDir;
7
+ }
8
+ rootDir;
9
+ resolve(p) {
10
+ return path.isAbsolute(p) ? p : path.resolve(this.rootDir, p);
11
+ }
12
+ async list(dir) {
13
+ const abs = this.resolve(dir);
14
+ try {
15
+ const entries = await fsPromises.readdir(abs, { withFileTypes: true });
16
+ return entries.filter((e) => e.isDirectory()).map((e) => e.name);
17
+ } catch {
18
+ return [];
19
+ }
20
+ }
21
+ async read(filePath) {
22
+ const abs = this.resolve(filePath);
23
+ try {
24
+ return await fsPromises.readFile(abs, "utf-8");
25
+ } catch (err) {
26
+ throw new Error(
27
+ `repo-reader: could not read '${abs}': ${err instanceof Error ? err.message : String(err)}`
28
+ );
29
+ }
30
+ }
31
+ async exists(filePath) {
32
+ const abs = this.resolve(filePath);
33
+ try {
34
+ await fsPromises.access(abs);
35
+ return true;
36
+ } catch {
37
+ return false;
38
+ }
39
+ }
40
+ };
41
+
42
+ // src/lib/ci-init.ts
43
+ import * as fs2 from "node:fs";
44
+ import * as path2 from "node:path";
45
+
46
+ // src/lib/atomic-write.ts
47
+ import * as fs from "node:fs";
48
+ function writeJsonAtomic(filePath, value) {
49
+ const tmpPath = filePath + ".tmp";
50
+ try {
51
+ fs.writeFileSync(tmpPath, JSON.stringify(value, null, 2) + "\n", "utf-8");
52
+ fs.renameSync(tmpPath, filePath);
53
+ } catch (err) {
54
+ try {
55
+ fs.unlinkSync(tmpPath);
56
+ } catch {
57
+ }
58
+ throw err;
59
+ }
60
+ }
61
+
62
+ // src/lib/ci-init.ts
63
+ var PLATFORM_COMMAND_MAP = {
64
+ next_js: {
65
+ unit_test: {
66
+ command: "pnpm turbo test",
67
+ scope: "per_app_changed",
68
+ hard_fail: true
69
+ },
70
+ typecheck: {
71
+ command: "pnpm turbo typecheck",
72
+ scope: "per_app_changed",
73
+ hard_fail: true
74
+ },
75
+ build: {
76
+ command: "pnpm turbo build",
77
+ scope: "per_app_changed",
78
+ hard_fail: true
79
+ },
80
+ lint: {
81
+ scope: "per_app_changed",
82
+ hard_fail: true,
83
+ delegates_to: ".codebyplan/eslint.json"
84
+ },
85
+ e2e: {
86
+ scope: "per_app_changed",
87
+ hard_fail: false,
88
+ delegates_to: ".codebyplan/e2e.json"
89
+ },
90
+ audit: {
91
+ command: "pnpm audit --json",
92
+ scope: "full_repo",
93
+ hard_fail: true
94
+ }
95
+ },
96
+ nestjs: {
97
+ unit_test: {
98
+ command: "pnpm turbo test",
99
+ scope: "per_app_changed",
100
+ hard_fail: true
101
+ },
102
+ typecheck: {
103
+ command: "pnpm turbo typecheck",
104
+ scope: "per_app_changed",
105
+ hard_fail: true
106
+ },
107
+ build: {
108
+ command: "pnpm turbo build",
109
+ scope: "per_app_changed",
110
+ hard_fail: true
111
+ },
112
+ lint: {
113
+ scope: "per_app_changed",
114
+ hard_fail: true,
115
+ delegates_to: ".codebyplan/eslint.json"
116
+ },
117
+ e2e: {
118
+ scope: "per_app_changed",
119
+ hard_fail: false,
120
+ delegates_to: ".codebyplan/e2e.json"
121
+ },
122
+ audit: {
123
+ command: "pnpm audit --json",
124
+ scope: "full_repo",
125
+ hard_fail: true
126
+ }
127
+ },
128
+ tauri: {
129
+ unit_test: {
130
+ command: "pnpm turbo test",
131
+ scope: "per_app_changed",
132
+ hard_fail: true
133
+ },
134
+ typecheck: {
135
+ command: "pnpm turbo typecheck",
136
+ scope: "per_app_changed",
137
+ hard_fail: true
138
+ },
139
+ build: {
140
+ command: "pnpm turbo build",
141
+ scope: "per_app_changed",
142
+ hard_fail: true
143
+ },
144
+ lint: {
145
+ scope: "per_app_changed",
146
+ hard_fail: true,
147
+ delegates_to: ".codebyplan/eslint.json"
148
+ },
149
+ e2e: {
150
+ scope: "per_app_changed",
151
+ hard_fail: false,
152
+ delegates_to: ".codebyplan/e2e.json"
153
+ },
154
+ audit: {
155
+ command: "pnpm audit --json",
156
+ scope: "full_repo",
157
+ hard_fail: true
158
+ }
159
+ },
160
+ vscode: {
161
+ unit_test: {
162
+ command: "pnpm turbo test",
163
+ scope: "per_app_changed",
164
+ hard_fail: true
165
+ },
166
+ typecheck: {
167
+ command: "pnpm turbo typecheck",
168
+ scope: "per_app_changed",
169
+ hard_fail: true
170
+ },
171
+ build: {
172
+ command: "pnpm turbo build",
173
+ scope: "per_app_changed",
174
+ hard_fail: true
175
+ },
176
+ lint: {
177
+ scope: "per_app_changed",
178
+ hard_fail: true,
179
+ delegates_to: ".codebyplan/eslint.json"
180
+ },
181
+ e2e: {
182
+ scope: "per_app_changed",
183
+ hard_fail: false,
184
+ delegates_to: ".codebyplan/e2e.json"
185
+ },
186
+ audit: {
187
+ command: "pnpm audit --json",
188
+ scope: "full_repo",
189
+ hard_fail: true
190
+ }
191
+ },
192
+ expo: {
193
+ unit_test: {
194
+ command: "pnpm turbo test",
195
+ scope: "per_app_changed",
196
+ hard_fail: true
197
+ },
198
+ typecheck: {
199
+ command: "pnpm turbo typecheck",
200
+ scope: "per_app_changed",
201
+ hard_fail: true
202
+ },
203
+ build: {
204
+ command: "pnpm turbo build",
205
+ scope: "per_app_changed",
206
+ hard_fail: true
207
+ },
208
+ lint: {
209
+ scope: "per_app_changed",
210
+ hard_fail: true,
211
+ delegates_to: ".codebyplan/eslint.json"
212
+ },
213
+ e2e: {
214
+ scope: "per_app_changed",
215
+ hard_fail: false,
216
+ delegates_to: ".codebyplan/e2e.json"
217
+ },
218
+ audit: {
219
+ command: "pnpm audit --json",
220
+ scope: "full_repo",
221
+ hard_fail: true
222
+ }
223
+ },
224
+ package: {
225
+ unit_test: {
226
+ command: "pnpm turbo test",
227
+ scope: "per_app_changed",
228
+ hard_fail: true
229
+ },
230
+ typecheck: {
231
+ command: "pnpm turbo typecheck",
232
+ scope: "per_app_changed",
233
+ hard_fail: true
234
+ },
235
+ build: {
236
+ command: "pnpm turbo build",
237
+ scope: "per_app_changed",
238
+ hard_fail: true
239
+ },
240
+ lint: {
241
+ scope: "per_app_changed",
242
+ hard_fail: true,
243
+ delegates_to: ".codebyplan/eslint.json"
244
+ },
245
+ e2e: {
246
+ scope: "per_app_changed",
247
+ hard_fail: false,
248
+ delegates_to: ".codebyplan/e2e.json"
249
+ },
250
+ audit: {
251
+ command: "pnpm audit --json",
252
+ scope: "full_repo",
253
+ hard_fail: true
254
+ }
255
+ }
256
+ };
257
+ async function tryReadJson(reader, filePath) {
258
+ try {
259
+ const raw = await reader.read(filePath);
260
+ return JSON.parse(raw);
261
+ } catch {
262
+ return null;
263
+ }
264
+ }
265
+ function hasDep(pkg, name) {
266
+ if (!pkg) return false;
267
+ const deps = pkg["dependencies"];
268
+ const devDeps = pkg["devDependencies"];
269
+ return Boolean(deps?.[name] ?? devDeps?.[name]);
270
+ }
271
+ function hasDevDep(pkg, name) {
272
+ if (!pkg) return false;
273
+ const devDeps = pkg["devDependencies"];
274
+ return Boolean(devDeps?.[name]);
275
+ }
276
+ async function detectPlatforms(reader) {
277
+ const detected = /* @__PURE__ */ new Set();
278
+ const dirsToScan = [""];
279
+ const appsChildren = await reader.list("apps");
280
+ for (const name of appsChildren) {
281
+ dirsToScan.push(`apps/${name}`);
282
+ }
283
+ for (const dir of dirsToScan) {
284
+ const pkgPath = dir ? `${dir}/package.json` : "package.json";
285
+ const pkg = await tryReadJson(reader, pkgPath);
286
+ const nextBase = dir ? `${dir}/` : "";
287
+ if (await reader.exists(`${nextBase}next.config.ts`) || await reader.exists(`${nextBase}next.config.js`) || await reader.exists(`${nextBase}next.config.mjs`)) {
288
+ detected.add("next_js");
289
+ }
290
+ if (hasDep(pkg, "@nestjs/core")) {
291
+ detected.add("nestjs");
292
+ }
293
+ if (await reader.exists(`${nextBase}tauri.conf.json`) || await reader.exists(`${nextBase}src-tauri`)) {
294
+ detected.add("tauri");
295
+ }
296
+ if (hasDevDep(pkg, "@types/vscode")) {
297
+ detected.add("vscode");
298
+ }
299
+ const deps = pkg?.["dependencies"];
300
+ if (deps?.["expo"]) {
301
+ detected.add("expo");
302
+ }
303
+ }
304
+ const packagesChildren = await reader.list("packages");
305
+ if (packagesChildren.length > 0) {
306
+ detected.add("package");
307
+ }
308
+ if (detected.size === 0) {
309
+ detected.add("package");
310
+ }
311
+ return Array.from(detected);
312
+ }
313
+ function buildDefaultCiConfig(platforms) {
314
+ const config = {
315
+ platforms: {},
316
+ delegation: {
317
+ lint: ".codebyplan/eslint.json",
318
+ e2e: ".codebyplan/e2e.json",
319
+ detection: ".claude/docs/architecture/testing-matrix.md"
320
+ },
321
+ workflow: {
322
+ required_check_enforced: false
323
+ }
324
+ };
325
+ for (const platform of platforms) {
326
+ const checks = PLATFORM_COMMAND_MAP[platform] ?? PLATFORM_COMMAND_MAP["package"];
327
+ if (checks) {
328
+ config.platforms[platform] = { ...checks };
329
+ }
330
+ }
331
+ return config;
332
+ }
333
+ async function runCiInit(opts) {
334
+ const projectDir = path2.resolve(opts?.projectDir ?? process.cwd());
335
+ const dryRun = opts?.dryRun ?? false;
336
+ const force = opts?.force ?? false;
337
+ const ciPath = path2.join(projectDir, ".codebyplan", "ci.json");
338
+ const reader = opts?.reader ?? new LocalFsReader(projectDir);
339
+ const platforms = await detectPlatforms(reader);
340
+ if (dryRun) {
341
+ return { status: "dry_run", path: ciPath, platforms };
342
+ }
343
+ let existing = null;
344
+ if (fs2.existsSync(ciPath)) {
345
+ try {
346
+ existing = JSON.parse(fs2.readFileSync(ciPath, "utf-8"));
347
+ } catch {
348
+ }
349
+ }
350
+ const newConfig = buildDefaultCiConfig(platforms);
351
+ if (existing !== null && !force) {
352
+ if (!existing.platforms) existing.platforms = {};
353
+ const newPlatforms = platforms.filter((p) => !(p in existing.platforms));
354
+ for (const p of newPlatforms) {
355
+ const checks = newConfig.platforms[p];
356
+ if (checks) {
357
+ existing.platforms[p] = checks;
358
+ }
359
+ }
360
+ let categoriesAdded = false;
361
+ for (const p of platforms) {
362
+ if (newPlatforms.includes(p)) continue;
363
+ const existingChecks = existing.platforms[p];
364
+ const defaultChecks = newConfig.platforms[p];
365
+ if (!existingChecks || !defaultChecks) continue;
366
+ for (const [cat, check] of Object.entries(defaultChecks)) {
367
+ if (!(cat in existingChecks)) {
368
+ existingChecks[cat] = check;
369
+ categoriesAdded = true;
370
+ }
371
+ }
372
+ }
373
+ if (newPlatforms.length === 0 && !categoriesAdded) {
374
+ return { status: "skipped", path: ciPath, platforms };
375
+ }
376
+ writeJsonAtomic(ciPath, existing);
377
+ return {
378
+ status: "written",
379
+ path: ciPath,
380
+ platforms: newPlatforms.length > 0 ? newPlatforms : platforms
381
+ };
382
+ }
383
+ const codebyplanDir = path2.join(projectDir, ".codebyplan");
384
+ fs2.mkdirSync(codebyplanDir, { recursive: true });
385
+ writeJsonAtomic(ciPath, newConfig);
386
+ return { status: "written", path: ciPath, platforms };
387
+ }
388
+
389
+ // src/lib/scaffold-ci-workflow.ts
390
+ import * as fs4 from "node:fs";
391
+ import * as path4 from "node:path";
392
+
393
+ // src/lib/templates-dir.ts
394
+ import * as fs3 from "node:fs";
395
+ import * as path3 from "node:path";
396
+ import { fileURLToPath } from "node:url";
397
+ function resolveTemplatesDir() {
398
+ const here = path3.dirname(fileURLToPath(import.meta.url));
399
+ const candidates = [
400
+ path3.resolve(here, "..", "templates"),
401
+ path3.resolve(here, "..", "..", "templates"),
402
+ path3.resolve(here, "..", "..", "..", "templates")
403
+ ];
404
+ for (const c of candidates) {
405
+ if (fs3.existsSync(c) && fs3.statSync(c).isDirectory()) {
406
+ return c;
407
+ }
408
+ }
409
+ throw new Error(
410
+ `codebyplan: could not locate templates/ directory. Probed:
411
+ ${candidates.join(
412
+ "\n "
413
+ )}`
414
+ );
415
+ }
416
+
417
+ // src/lib/scaffold-ci-workflow.ts
418
+ function substituteTokens(template, tokens) {
419
+ let result = template;
420
+ for (const [token, value] of Object.entries(tokens)) {
421
+ result = result.split(`{{${token}}}`).join(value);
422
+ }
423
+ return result;
424
+ }
425
+ async function detectPnpmVersionFromPackageJson(reader) {
426
+ try {
427
+ const raw = await reader.read("package.json");
428
+ const pkg = JSON.parse(raw);
429
+ const pm = pkg.packageManager;
430
+ if (typeof pm === "string" && pm.startsWith("pnpm@")) {
431
+ const version = pm.slice("pnpm@".length).split("+")[0];
432
+ if (version && /^\d/.test(version)) {
433
+ return version;
434
+ }
435
+ }
436
+ return "10";
437
+ } catch {
438
+ return "10";
439
+ }
440
+ }
441
+ async function detectStrictEnforcedFromCiJson(reader) {
442
+ try {
443
+ const raw = await reader.read(".codebyplan/ci.json");
444
+ const parsed = JSON.parse(raw);
445
+ return parsed.workflow?.strict_check_enforced === true;
446
+ } catch {
447
+ return false;
448
+ }
449
+ }
450
+ async function renderCiWorkflowContent(opts) {
451
+ const projectDir = path4.resolve(opts?.projectDir ?? process.cwd());
452
+ const reader = opts?.reader ?? new LocalFsReader(projectDir);
453
+ const pnpmVersion = opts?.pnpmVersion ?? await detectPnpmVersionFromPackageJson(reader);
454
+ const nodeVersion = opts?.nodeVersion ?? "22";
455
+ const strictEnforced = opts?.strictEnforced ?? await detectStrictEnforcedFromCiJson(reader);
456
+ const templatesDir = opts?.templatesDir ?? resolveTemplatesDir();
457
+ const templatePath = path4.join(templatesDir, "github-workflows", "ci.yml");
458
+ if (!fs4.existsSync(templatePath)) {
459
+ throw new Error(
460
+ `scaffold-ci-workflow: template not found at ${templatePath}`
461
+ );
462
+ }
463
+ const rawTemplate = fs4.readFileSync(templatePath, "utf-8");
464
+ return substituteTokens(rawTemplate, {
465
+ PNPM_VERSION: pnpmVersion,
466
+ NODE_VERSION: nodeVersion,
467
+ STRICT_NAME_SUFFIX: strictEnforced ? "" : " (report-only)",
468
+ STRICT_CONTINUE_ON_ERROR_LINE: strictEnforced ? "" : "\n continue-on-error: true"
469
+ });
470
+ }
471
+ async function runScaffoldCiWorkflow(opts) {
472
+ const dryRun = opts?.dryRun ?? false;
473
+ const force = opts?.force ?? false;
474
+ const projectDir = path4.resolve(opts?.projectDir ?? process.cwd());
475
+ const renderedContent = await renderCiWorkflowContent(opts);
476
+ const targetPath = path4.join(projectDir, ".github", "workflows", "ci.yml");
477
+ if (dryRun) {
478
+ return { status: "dry_run", path: targetPath };
479
+ }
480
+ if (fs4.existsSync(targetPath)) {
481
+ const existingContent = fs4.readFileSync(targetPath, "utf-8");
482
+ if (existingContent === renderedContent) {
483
+ return {
484
+ status: "skipped",
485
+ path: targetPath,
486
+ reason: "already up to date"
487
+ };
488
+ }
489
+ if (!force) {
490
+ throw new Error(
491
+ `scaffold-ci-workflow: ${targetPath} already exists and differs from the template. Pass --force to overwrite.`
492
+ );
493
+ }
494
+ }
495
+ const targetDir = path4.dirname(targetPath);
496
+ fs4.mkdirSync(targetDir, { recursive: true });
497
+ const tmpPath = targetPath + ".tmp";
498
+ try {
499
+ fs4.writeFileSync(tmpPath, renderedContent, "utf-8");
500
+ fs4.renameSync(tmpPath, targetPath);
501
+ } catch (err) {
502
+ try {
503
+ fs4.unlinkSync(tmpPath);
504
+ } catch {
505
+ }
506
+ throw err;
507
+ }
508
+ return { status: "written", path: targetPath };
509
+ }
510
+ export {
511
+ LocalFsReader,
512
+ PLATFORM_COMMAND_MAP,
513
+ buildDefaultCiConfig,
514
+ detectPlatforms,
515
+ renderCiWorkflowContent,
516
+ runCiInit,
517
+ runScaffoldCiWorkflow
518
+ };