@goodtek/vibeops 0.7.22 → 1.0.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.
Files changed (121) hide show
  1. package/CHANGELOG.md +21 -1
  2. package/README.md +62 -382
  3. package/dist/bootstrap/installer.js +4 -16
  4. package/dist/cli.js +26 -160
  5. package/dist/commands/init.js +3 -3
  6. package/dist/commands/status.js +145 -8
  7. package/dist/commands/task-add.js +64 -164
  8. package/dist/commands/task-done.js +117 -74
  9. package/dist/lib/config.js +2 -118
  10. package/dist/lib/git.js +1 -1
  11. package/dist/lib/paths.js +7 -3
  12. package/dist/lib/plan-llm-openai.js +0 -63
  13. package/dist/lib/resolve-task.js +34 -0
  14. package/dist/lib/task-add-llm.js +46 -233
  15. package/dist/lib/task-context.js +68 -0
  16. package/dist/lib/task-done-llm.js +148 -0
  17. package/dist/lib/task-merge.js +54 -159
  18. package/dist/lib/task-scaffold.js +39 -43
  19. package/dist/lib/task-start.js +72 -0
  20. package/dist/lib/task-summary.js +12 -239
  21. package/dist/lib/task.js +15 -24
  22. package/dist/types/config.js +0 -16
  23. package/package.json +3 -5
  24. package/templates/.cursor/rules/01-task-source-of-truth.mdc +12 -0
  25. package/templates/.cursor/rules/02-git-safety.mdc +11 -0
  26. package/templates/.cursor/rules/03-docs-on-done.mdc +13 -0
  27. package/templates/.cursor/skills/implement-task/SKILL.md +22 -0
  28. package/templates/.cursor/skills/plan-task/SKILL.md +19 -0
  29. package/templates/AGENTS.md +26 -87
  30. package/templates/docs/logs/README.md +4 -35
  31. package/templates/docs/project/03-architecture.md +10 -29
  32. package/templates/docs/project/05-current-state.md +7 -27
  33. package/templates/docs/project/06-decisions.md +6 -17
  34. package/templates/docs/tasks/TASK-000-template.md +9 -44
  35. package/dist/agent/loader.js +0 -71
  36. package/dist/agent/prompt.js +0 -66
  37. package/dist/commands/agent-list.js +0 -32
  38. package/dist/commands/agent-prompt.js +0 -59
  39. package/dist/commands/agent-show.js +0 -26
  40. package/dist/commands/done.js +0 -160
  41. package/dist/commands/github-init.js +0 -554
  42. package/dist/commands/github-status.js +0 -164
  43. package/dist/commands/next.js +0 -283
  44. package/dist/commands/notion-init.js +0 -764
  45. package/dist/commands/notion-sync.js +0 -405
  46. package/dist/commands/notion-test.js +0 -595
  47. package/dist/commands/plan.js +0 -146
  48. package/dist/commands/rollback.js +0 -126
  49. package/dist/commands/start.js +0 -129
  50. package/dist/commands/task-check.js +0 -155
  51. package/dist/commands/task-generate.js +0 -206
  52. package/dist/commands/task-pull.js +0 -277
  53. package/dist/commands/task-rollback.js +0 -174
  54. package/dist/commands/task-start.js +0 -102
  55. package/dist/commands/task-status.js +0 -122
  56. package/dist/lib/brief.js +0 -356
  57. package/dist/lib/cursor-task-prompt.js +0 -60
  58. package/dist/lib/done-follow-up.js +0 -308
  59. package/dist/lib/github-cli.js +0 -247
  60. package/dist/lib/guide-state.js +0 -69
  61. package/dist/lib/mvp-artifacts.js +0 -204
  62. package/dist/lib/mvp-constants.js +0 -15
  63. package/dist/lib/mvp-diff-summary.js +0 -43
  64. package/dist/lib/mvp-done-summary.js +0 -335
  65. package/dist/lib/notion-client.js +0 -459
  66. package/dist/lib/notion-discovery.js +0 -671
  67. package/dist/lib/notion-env.js +0 -140
  68. package/dist/lib/notion-mappers.js +0 -148
  69. package/dist/lib/notion-schema.js +0 -272
  70. package/dist/lib/notion-sync.js +0 -337
  71. package/dist/lib/notion-target.js +0 -247
  72. package/dist/lib/package-json.js +0 -133
  73. package/dist/lib/plan-apply-planner.js +0 -131
  74. package/dist/lib/plan-codex-pkce.js +0 -234
  75. package/dist/lib/plan-llm-models.js +0 -237
  76. package/dist/lib/plan-llm-normalize.js +0 -146
  77. package/dist/lib/plan-llm-session.js +0 -682
  78. package/dist/lib/plan-post-apply-setup.js +0 -116
  79. package/dist/lib/project-docs.js +0 -95
  80. package/dist/lib/prompt-builder.js +0 -135
  81. package/dist/lib/resolve-command-task.js +0 -40
  82. package/dist/lib/task-add-build-prompt.js +0 -39
  83. package/dist/lib/task-add-cursor.js +0 -81
  84. package/dist/lib/task-generator.js +0 -183
  85. package/dist/lib/task-prompt.js +0 -23
  86. package/dist/lib/task-pull.js +0 -354
  87. package/dist/lib/task-status-brief.js +0 -132
  88. package/dist/lib/workflow-guide.js +0 -485
  89. package/dist/status/collector.js +0 -103
  90. package/dist/status/format.js +0 -181
  91. package/dist/types/brief.js +0 -126
  92. package/templates/.cursor/rules/00-project-governance.mdc +0 -28
  93. package/templates/.cursor/rules/01-agent-orchestration.mdc +0 -48
  94. package/templates/.cursor/rules/02-task-workflow.mdc +0 -38
  95. package/templates/.cursor/rules/03-git-safety.mdc +0 -30
  96. package/templates/.cursor/rules/04-docs-update.mdc +0 -22
  97. package/templates/.vibeops/agents/architect.md +0 -47
  98. package/templates/.vibeops/agents/builder.md +0 -38
  99. package/templates/.vibeops/agents/docs.md +0 -54
  100. package/templates/.vibeops/agents/orchestrator.md +0 -40
  101. package/templates/.vibeops/agents/planner.md +0 -60
  102. package/templates/.vibeops/agents/recovery.md +0 -49
  103. package/templates/.vibeops/agents/reviewer.md +0 -47
  104. package/templates/.vibeops/agents/tester.md +0 -43
  105. package/templates/.vibeops/prompts/create-plan.md +0 -33
  106. package/templates/.vibeops/prompts/generate-tasks.md +0 -41
  107. package/templates/.vibeops/prompts/implement-task.md +0 -39
  108. package/templates/.vibeops/prompts/review-task.md +0 -34
  109. package/templates/.vibeops/prompts/rollback.md +0 -32
  110. package/templates/.vibeops/prompts/start-project.md +0 -39
  111. package/templates/.vibeops/workflows/notion-sync.md +0 -53
  112. package/templates/.vibeops/workflows/project-start.md +0 -73
  113. package/templates/.vibeops/workflows/rollback.md +0 -45
  114. package/templates/.vibeops/workflows/task-lifecycle.md +0 -71
  115. package/templates/docs/project/00-overview.md +0 -27
  116. package/templates/docs/project/01-requirements.md +0 -30
  117. package/templates/docs/project/02-mvp-scope.md +0 -36
  118. package/templates/docs/project/04-tech-stack.md +0 -29
  119. package/templates/docs/project/07-backlog.md +0 -23
  120. package/templates/docs/project/08-env.md +0 -29
  121. package/templates/docs/project/09-deployment.md +0 -28
@@ -1,554 +0,0 @@
1
- import { basename, resolve } from "node:path";
2
- import { select } from "@inquirer/prompts";
3
- import { mergeGithubConfig, readConfig, writeConfig } from "../lib/config.js";
4
- import { pathExists } from "../lib/filesystem.js";
5
- import { buildGhCreateRepoArgs, ghAuthLoginInteractive, ghAuthStatus, ghCreateRepo, ghCurrentUser, gitRemoteAdd, gitRemoteList, gitRemoteSetUrl, isGhInstalled, parseGitHubRemote, } from "../lib/github-cli.js";
6
- import { askInput, askYesNo } from "../lib/inquirer-helpers.js";
7
- import { bold, cyan, dim, green, log, yellow } from "../lib/logger.js";
8
- import { buildRepositoryFieldsPatch, readPackageJson, readBugsUrl, readHomepage, readRepositoryUrl, updatePackageRepositoryFields, } from "../lib/package-json.js";
9
- const REMOTE_DEFAULT = "origin";
10
- export async function githubInitCommand(options = {}) {
11
- const cwd = resolve(options.cwd ?? process.cwd());
12
- const dryRun = options.dryRun === true;
13
- const explicitYes = options.yes === true;
14
- const isTty = process.stdin.isTTY === true;
15
- const interactive = !dryRun && !explicitYes && isTty;
16
- const ctx = {
17
- cwd,
18
- dryRun,
19
- interactive,
20
- remoteName: options.remote && options.remote.length > 0 ? options.remote : REMOTE_DEFAULT,
21
- forcedVisibility: options.public === true ? "public" : options.private === true ? "private" : "",
22
- connectTarget: typeof options.connect === "string" && options.connect.length > 0
23
- ? parseGitHubRemote(options.connect)
24
- : null,
25
- packageUpdate: options.noPackageUpdate === true
26
- ? false
27
- : typeof options.packageUpdate === "boolean"
28
- ? options.packageUpdate
29
- : true,
30
- ownerFlag: typeof options.owner === "string" ? options.owner.trim() : "",
31
- repoFlag: typeof options.repo === "string" ? options.repo.trim() : "",
32
- configPath: resolve(cwd, ".vibeops.json"),
33
- packageJsonPath: resolve(cwd, "package.json"),
34
- };
35
- log.info(bold("vibeops github init"));
36
- log.info(` ${dim("cwd")} ${cwd}`);
37
- log.info(` ${dim("mode")} ${dryRun
38
- ? "dry-run (no gh / git / file mutation)"
39
- : interactive
40
- ? "interactive (arrow keys + Enter, no y/n typing)"
41
- : "non-interactive (flags only)"}`);
42
- if (ctx.connectTarget !== null) {
43
- if (!ctx.connectTarget.isGithub) {
44
- log.error(`--connect ${cyan(ctx.connectTarget.url)} is not a GitHub URL or owner/repo slug. Expected 'https://github.com/<owner>/<repo>' or 'owner/repo'.`);
45
- process.exitCode = 1;
46
- return;
47
- }
48
- log.info(` ${dim("connect")} ${cyan(`${ctx.connectTarget.owner}/${ctx.connectTarget.repo}`)}`);
49
- }
50
- log.blank();
51
- if (!(await pathExists(ctx.configPath))) {
52
- log.error(`.vibeops.json is missing. Run ${cyan("vibeops init")} first to install the VibeOps workflow files.`);
53
- process.exitCode = 1;
54
- return;
55
- }
56
- const baseConfig = await readConfig(cwd);
57
- if (baseConfig === null) {
58
- log.error(`Failed to read .vibeops.json (schema mismatch or invalid JSON). Inspect the file, or re-run ${cyan("vibeops init")} to recreate it.`);
59
- process.exitCode = 1;
60
- return;
61
- }
62
- const plan = {
63
- ghInstalled: false,
64
- ghAuthenticated: false,
65
- remoteUsed: false,
66
- remoteAdded: false,
67
- remoteUrl: null,
68
- remoteSetUrl: false,
69
- repoCreated: false,
70
- repoCreationCommand: null,
71
- owner: ctx.ownerFlag,
72
- repo: ctx.repoFlag,
73
- visibility: ctx.forcedVisibility,
74
- description: "",
75
- configChanged: false,
76
- packageWrites: null,
77
- packageDiffs: [],
78
- };
79
- // ── A. gh installed ──────────────────────────────────────────────────────
80
- plan.ghInstalled = await isGhInstalled();
81
- if (!plan.ghInstalled) {
82
- if (dryRun) {
83
- log.warn(`gh CLI is not installed. Install with ${cyan("brew install gh")} (macOS) or via https://cli.github.com/ before running for real. The dry-run continues so you can review the plan.`);
84
- }
85
- else {
86
- log.error(`gh CLI is not installed. Install with ${cyan("brew install gh")} (macOS) or via https://cli.github.com/ and retry.`);
87
- process.exitCode = 1;
88
- return;
89
- }
90
- }
91
- else {
92
- log.ok(`gh CLI installed`);
93
- }
94
- // ── B. gh authenticated ──────────────────────────────────────────────────
95
- const auth = plan.ghInstalled
96
- ? await ghAuthStatus()
97
- : {
98
- installed: false,
99
- authenticated: false,
100
- username: null,
101
- hosts: [],
102
- detail: "gh CLI not installed",
103
- };
104
- plan.ghAuthenticated = auth.authenticated;
105
- if (!auth.authenticated) {
106
- if (dryRun) {
107
- log.warn(`gh is not authenticated. Run ${cyan("gh auth login")} before applying. The dry-run continues.`);
108
- }
109
- else {
110
- log.warn(`gh is not authenticated. Run ${cyan("gh auth login")} first.`);
111
- let runLogin = false;
112
- if (interactive) {
113
- runLogin = await askYesNo({
114
- message: "Run gh auth login now? (TTY is handed off to the child process)",
115
- nonInteractive: false,
116
- defaultValue: true,
117
- });
118
- }
119
- else if (explicitYes) {
120
- // --yes alone does not auto-spawn gh auth login (TTY required).
121
- log.info(dim(` · --yes alone does not run gh auth login. Re-run interactively to authenticate.`));
122
- }
123
- if (runLogin) {
124
- log.info(dim(` · spawning: gh auth login`));
125
- const code = await ghAuthLoginInteractive();
126
- if (code !== 0) {
127
- log.error(`gh auth login exited with code ${code}. Please retry.`);
128
- process.exitCode = code;
129
- return;
130
- }
131
- const auth2 = await ghAuthStatus();
132
- plan.ghAuthenticated = auth2.authenticated;
133
- if (!plan.ghAuthenticated) {
134
- log.error("gh auth login finished but the status is still unauthenticated. Inspect with 'gh auth status'.");
135
- process.exitCode = 1;
136
- return;
137
- }
138
- log.ok("gh authenticated");
139
- }
140
- else {
141
- log.info(dim(` · Cannot create a new GitHub repo without gh auth login. Exiting.`));
142
- process.exitCode = 1;
143
- return;
144
- }
145
- }
146
- }
147
- else {
148
- log.ok(`gh authenticated${auth.username !== null ? ` ${dim(`as ${auth.username}`)}` : ""}`);
149
- }
150
- // ── C. inspect current remotes ───────────────────────────────────────────
151
- const remotes = await gitRemoteList(cwd);
152
- const existingRemote = remotes.find((r) => r.name === ctx.remoteName) ?? null;
153
- const existingInfo = existingRemote !== null ? parseGitHubRemote(existingRemote.url) : null;
154
- log.info(`${dim("git remote")} ${cyan(ctx.remoteName)}${existingRemote === null
155
- ? ` ${dim("(none)")}`
156
- : ` ${existingRemote.url}${existingInfo?.isGithub ? ` ${dim(`(${existingInfo.owner}/${existingInfo.repo})`)}` : ` ${yellow("(not a GitHub URL)")}`}`}`);
157
- let path;
158
- if (ctx.connectTarget !== null) {
159
- path = "connect-existing";
160
- }
161
- else if (existingRemote !== null &&
162
- existingInfo !== null &&
163
- existingInfo.isGithub === true) {
164
- if (interactive) {
165
- const useThis = await askYesNo({
166
- message: `Use existing remote ${cyan(`${ctx.remoteName} = ${existingRemote.url}`)} (${existingInfo.owner}/${existingInfo.repo})?`,
167
- nonInteractive: false,
168
- defaultValue: true,
169
- });
170
- path = useThis ? "use-existing" : "create-new";
171
- }
172
- else {
173
- path = "use-existing";
174
- }
175
- }
176
- else if (existingRemote !== null && existingInfo !== null && !existingInfo.isGithub) {
177
- // existing non-github remote — propose new repo by default.
178
- if (interactive) {
179
- const overwrite = await askYesNo({
180
- message: `${ctx.remoteName} is not a GitHub URL. Create a new GitHub repo and overwrite ${ctx.remoteName}?`,
181
- nonInteractive: false,
182
- defaultValue: false,
183
- });
184
- path = overwrite ? "create-new" : "use-existing";
185
- }
186
- else {
187
- path = "use-existing";
188
- }
189
- }
190
- else {
191
- // no existing remote
192
- if (interactive) {
193
- const wantCreate = await askYesNo({
194
- message: "Create a new GitHub repo? (No → enter an existing GitHub URL to connect)",
195
- nonInteractive: false,
196
- defaultValue: true,
197
- });
198
- path = wantCreate ? "create-new" : "connect-existing";
199
- }
200
- else {
201
- path = ctx.ownerFlag.length > 0 && ctx.repoFlag.length > 0 ? "create-new" : "connect-existing";
202
- }
203
- }
204
- log.blank();
205
- log.info(bold(`Path: ${path}`));
206
- // ── package.json defaults (used by D and to render F preview) ────────────
207
- const pkg = await readPackageJson(cwd);
208
- if (pkg !== null) {
209
- log.info(` ${dim("package.json")} ${cyan(pkg.path)}`);
210
- }
211
- else if (path === "create-new") {
212
- log.info(` ${dim("package.json")} ${dim("(not found — repo name/description defaults will use folder name only)")}`);
213
- }
214
- // ── execute the chosen path ──────────────────────────────────────────────
215
- if (path === "use-existing") {
216
- if (existingInfo !== null && existingInfo.isGithub) {
217
- plan.remoteUsed = true;
218
- plan.owner = existingInfo.owner ?? "";
219
- plan.repo = existingInfo.repo ?? "";
220
- plan.remoteUrl = existingRemote.url;
221
- }
222
- else if (existingInfo !== null) {
223
- // user opted to keep a non-github remote — record best-effort
224
- plan.remoteUsed = true;
225
- plan.remoteUrl = existingRemote.url;
226
- }
227
- }
228
- else if (path === "create-new") {
229
- const owner = ctx.ownerFlag.length > 0
230
- ? ctx.ownerFlag
231
- : (await ghCurrentUser()) ??
232
- (interactive
233
- ? await askInput({
234
- message: "GitHub owner (user or org)",
235
- nonInteractive: false,
236
- required: true,
237
- })
238
- : "");
239
- if (owner.length === 0) {
240
- log.error(`Could not determine owner. Pass ${cyan("--owner <user>")} explicitly, or run ${cyan("gh auth login")} first.`);
241
- process.exitCode = 1;
242
- return;
243
- }
244
- const defaultRepo = ctx.repoFlag.length > 0
245
- ? ctx.repoFlag
246
- : typeof pkg?.data.name === "string" && pkg.data.name.length > 0
247
- ? pkg.data.name
248
- : basename(cwd);
249
- const repoName = interactive
250
- ? await askInput({
251
- message: "Repository name",
252
- nonInteractive: false,
253
- default: defaultRepo,
254
- required: true,
255
- })
256
- : defaultRepo;
257
- if (repoName.length === 0) {
258
- log.error("Repo name is empty.");
259
- process.exitCode = 1;
260
- return;
261
- }
262
- const defaultDesc = typeof pkg?.data.description === "string" ? pkg.data.description : "";
263
- const description = interactive
264
- ? await askInput({
265
- message: "Description (Enter to skip)",
266
- nonInteractive: false,
267
- default: defaultDesc,
268
- })
269
- : defaultDesc;
270
- let resolvedVisibility;
271
- if (ctx.forcedVisibility === "public" || ctx.forcedVisibility === "private") {
272
- resolvedVisibility = ctx.forcedVisibility;
273
- }
274
- else if (interactive) {
275
- resolvedVisibility = await pickVisibility();
276
- }
277
- else {
278
- resolvedVisibility = "private";
279
- }
280
- plan.owner = owner;
281
- plan.repo = repoName;
282
- plan.visibility = resolvedVisibility;
283
- plan.description = description;
284
- const argv = buildGhCreateRepoArgs({
285
- owner,
286
- repo: repoName,
287
- visibility: resolvedVisibility,
288
- source: cwd,
289
- remote: ctx.remoteName,
290
- description: description.length > 0 ? description : undefined,
291
- });
292
- plan.repoCreationCommand = `gh ${argv.join(" ")}`;
293
- plan.remoteUrl = `https://github.com/${owner}/${repoName}`;
294
- plan.repoCreated = true;
295
- plan.remoteAdded = existingRemote === null; // gh repo create adds remote when missing
296
- plan.remoteSetUrl = existingRemote !== null;
297
- }
298
- else {
299
- // connect-existing
300
- let target = ctx.connectTarget;
301
- if (target === null) {
302
- let ans = "";
303
- if (interactive) {
304
- ans = await askInput({
305
- message: "Existing GitHub URL or owner/repo slug",
306
- nonInteractive: false,
307
- required: true,
308
- });
309
- }
310
- else if (ctx.ownerFlag.length > 0 && ctx.repoFlag.length > 0) {
311
- ans = `${ctx.ownerFlag}/${ctx.repoFlag}`;
312
- }
313
- else {
314
- log.error(`Non-interactive mode (dry-run / --yes / non-TTY) requires ${cyan("--connect <owner/repo or url>")} or ${cyan("--owner <user> --repo <name>")}.`);
315
- log.info(dim(` example: vibeops github init --dry-run --connect goodtekxyz/vibeops`));
316
- process.exitCode = 1;
317
- return;
318
- }
319
- target = parseGitHubRemote(ans);
320
- }
321
- if (target === null || !target.isGithub || target.owner === null || target.repo === null) {
322
- log.error(`Not a valid GitHub URL or owner/repo slug. Examples: ${cyan("https://github.com/<owner>/<repo>")} or ${cyan("<owner>/<repo>")}`);
323
- process.exitCode = 1;
324
- return;
325
- }
326
- plan.owner = target.owner;
327
- plan.repo = target.repo;
328
- plan.remoteUrl = target.httpsUrl ?? target.url;
329
- if (existingRemote === null) {
330
- plan.remoteAdded = true;
331
- }
332
- else if (existingRemote.url !== plan.remoteUrl) {
333
- let allow = false;
334
- if (interactive) {
335
- allow = await askYesNo({
336
- message: `${ctx.remoteName} already exists (${existingRemote.url}). Update its URL to this one?`,
337
- nonInteractive: false,
338
- defaultValue: false,
339
- });
340
- }
341
- if (!allow) {
342
- log.info(`${dim("·")} Keeping existing ${ctx.remoteName} — not setting a new URL.`);
343
- plan.remoteUrl = existingRemote.url;
344
- }
345
- else {
346
- plan.remoteSetUrl = true;
347
- }
348
- }
349
- else {
350
- plan.remoteUsed = true;
351
- }
352
- }
353
- // ── F. package.json fields preview ───────────────────────────────────────
354
- if (plan.owner.length > 0 && plan.repo.length > 0) {
355
- if (ctx.packageUpdate && pkg !== null) {
356
- const patch = buildRepositoryFieldsPatch({ owner: plan.owner, repo: plan.repo });
357
- const before = {
358
- repositoryUrl: readRepositoryUrl(pkg.data),
359
- homepage: readHomepage(pkg.data),
360
- bugsUrl: readBugsUrl(pkg.data),
361
- };
362
- const fields = [
363
- { field: "repository.url", before: before.repositoryUrl, after: patch.repositoryUrl },
364
- { field: "homepage", before: before.homepage, after: patch.homepage },
365
- { field: "bugs.url", before: before.bugsUrl, after: patch.bugsUrl },
366
- ].filter((f) => f.before !== f.after);
367
- let allow = true;
368
- if (interactive && fields.length > 0) {
369
- const anyExisting = fields.some((f) => f.before.length > 0);
370
- if (anyExisting) {
371
- allow = await askYesNo({
372
- message: `Overwrite package.json repository / homepage / bugs? (${fields.length} field${fields.length === 1 ? "" : "s"} will change)`,
373
- nonInteractive: false,
374
- defaultValue: false,
375
- });
376
- }
377
- else {
378
- allow = await askYesNo({
379
- message: `Fill package.json repository / homepage / bugs? (${fields.length} new field${fields.length === 1 ? "" : "s"})`,
380
- nonInteractive: false,
381
- defaultValue: true,
382
- });
383
- }
384
- }
385
- else if (!interactive && fields.length > 0) {
386
- const anyExisting = fields.some((f) => f.before.length > 0);
387
- // default in non-interactive: write new fields, but never silently overwrite existing ones unless --yes.
388
- allow = !anyExisting || explicitYes;
389
- }
390
- if (allow && fields.length > 0) {
391
- plan.packageWrites = patch;
392
- plan.packageDiffs = fields;
393
- }
394
- else if (!allow && fields.length > 0) {
395
- if (interactive) {
396
- log.info(`${dim("·")} Skipping package.json update (user choice).`);
397
- }
398
- else {
399
- log.info(`${dim("·")} Skipping package.json update (preserving existing values — pass ${cyan("--yes")} or run interactively to overwrite).`);
400
- }
401
- }
402
- }
403
- else if (!ctx.packageUpdate) {
404
- log.info(`${dim("·")} --no-package-update — package.json will not be modified.`);
405
- }
406
- }
407
- // ── render plan ──────────────────────────────────────────────────────────
408
- log.blank();
409
- log.info(bold("Plan"));
410
- renderPlan(ctx, plan);
411
- if (dryRun) {
412
- log.blank();
413
- log.info(dim("dry-run — no commands executed, no files written."));
414
- log.info(bold("Next steps"));
415
- log.info(` 1) Drop ${cyan("--dry-run")} to apply this plan (gh / git / config / package.json mutation).`);
416
- log.info(` 2) After commit, run ${cyan(`git push -u ${ctx.remoteName} <branch>`)} manually — VibeOps does not auto-push.`);
417
- log.info(` 3) Verify with ${cyan("vibeops github status")}.`);
418
- return;
419
- }
420
- // ── execute ──────────────────────────────────────────────────────────────
421
- if (plan.repoCreated) {
422
- log.step(`gh repo create ${plan.owner}/${plan.repo}`);
423
- const res = await ghCreateRepo({
424
- owner: plan.owner,
425
- repo: plan.repo,
426
- visibility: plan.visibility === "" ? "private" : plan.visibility,
427
- source: cwd,
428
- remote: ctx.remoteName,
429
- description: plan.description.length > 0 ? plan.description : undefined,
430
- });
431
- if (!res.ok) {
432
- log.error(`gh repo create failed (exit ${res.exitCode ?? "?"}).`);
433
- if (typeof res.stderr === "string" && res.stderr.length > 0) {
434
- log.info(dim(res.stderr.trim()));
435
- }
436
- process.exitCode = res.exitCode ?? 1;
437
- return;
438
- }
439
- log.ok(`created ${plan.owner}/${plan.repo} ${dim(plan.visibility)}`);
440
- }
441
- else if (plan.remoteAdded && plan.remoteUrl !== null) {
442
- log.step(`git remote add ${ctx.remoteName} ${plan.remoteUrl}`);
443
- try {
444
- await gitRemoteAdd(cwd, ctx.remoteName, plan.remoteUrl);
445
- log.ok(`added remote ${ctx.remoteName}`);
446
- }
447
- catch (err) {
448
- log.error(`git remote add failed: ${err.message}`);
449
- process.exitCode = 1;
450
- return;
451
- }
452
- }
453
- else if (plan.remoteSetUrl && plan.remoteUrl !== null) {
454
- log.step(`git remote set-url ${ctx.remoteName} ${plan.remoteUrl}`);
455
- try {
456
- await gitRemoteSetUrl(cwd, ctx.remoteName, plan.remoteUrl);
457
- log.ok(`updated remote ${ctx.remoteName}`);
458
- }
459
- catch (err) {
460
- log.error(`git remote set-url failed: ${err.message}`);
461
- process.exitCode = 1;
462
- return;
463
- }
464
- }
465
- // ── .vibeops.json ───────────────────────────────────────────────────────
466
- const githubPatch = {
467
- enabled: true,
468
- mode: "gh-cli",
469
- owner: plan.owner,
470
- repo: plan.repo,
471
- remote: ctx.remoteName,
472
- visibility: plan.visibility === "" ? guessVisibilityFromExistingConfig(baseConfig.github) : plan.visibility,
473
- url: plan.remoteUrl ?? "",
474
- };
475
- const { merged, changed } = mergeGithubConfig(baseConfig, githubPatch);
476
- plan.configChanged = changed;
477
- if (changed) {
478
- await writeConfig(cwd, merged);
479
- log.ok(".vibeops.json updated (github section)");
480
- }
481
- else {
482
- log.info(dim(".vibeops.json unchanged"));
483
- }
484
- // ── package.json ────────────────────────────────────────────────────────
485
- if (plan.packageWrites !== null && plan.packageDiffs.length > 0) {
486
- const res = await updatePackageRepositoryFields({
487
- cwd,
488
- patch: { owner: plan.owner, repo: plan.repo },
489
- dryRun: false,
490
- });
491
- if (!res.ok) {
492
- log.warn(`package.json update failed: ${res.reason ?? "unknown"}`);
493
- }
494
- else if (res.written) {
495
- log.ok(`package.json updated ${dim(`(${res.diffs.length} field${res.diffs.length === 1 ? "" : "s"})`)}`);
496
- }
497
- else {
498
- log.info(dim(`package.json unchanged`));
499
- }
500
- }
501
- log.blank();
502
- log.info(bold("Done"));
503
- log.info(` · git remote ${ctx.remoteName} → ${cyan(plan.remoteUrl ?? "(unset)")}`);
504
- log.info(` · Verify with ${cyan("vibeops github status")}.`);
505
- log.info(` · VibeOps never auto-pushes. When ready, run ${cyan(`git push -u ${ctx.remoteName} <branch>`)} yourself.`);
506
- }
507
- function guessVisibilityFromExistingConfig(prev) {
508
- return prev?.visibility ?? "";
509
- }
510
- async function pickVisibility() {
511
- return await select({
512
- message: "Repository visibility?",
513
- choices: [
514
- { name: "private", value: "private" },
515
- { name: "public", value: "public" },
516
- ],
517
- default: "private",
518
- loop: false,
519
- pageSize: 2,
520
- });
521
- }
522
- function renderPlan(ctx, plan) {
523
- if (plan.repoCreated) {
524
- log.info(` ${green("+")} gh repo create ${cyan(`${plan.owner}/${plan.repo}`)} ${dim(`--${plan.visibility === "" ? "private" : plan.visibility} --source=${ctx.cwd} --remote=${ctx.remoteName}`)}`);
525
- if (plan.description.length > 0) {
526
- log.info(` ${dim("--description")} ${plan.description}`);
527
- }
528
- log.info(` ${dim("→")} This command also registers the ${ctx.remoteName} remote.`);
529
- }
530
- if (plan.remoteAdded && !plan.repoCreated && plan.remoteUrl !== null) {
531
- log.info(` ${green("+")} git remote add ${cyan(ctx.remoteName)} ${plan.remoteUrl}`);
532
- }
533
- if (plan.remoteSetUrl && plan.remoteUrl !== null) {
534
- log.info(` ${green("~")} git remote set-url ${cyan(ctx.remoteName)} ${plan.remoteUrl}`);
535
- }
536
- if (plan.remoteUsed && !plan.remoteAdded && !plan.remoteSetUrl && !plan.repoCreated) {
537
- log.info(` ${dim("·")} keep existing remote ${cyan(ctx.remoteName)} ${plan.remoteUrl ?? ""}`);
538
- }
539
- if (plan.packageWrites !== null && plan.packageDiffs.length > 0) {
540
- log.info(` ${green("~")} package.json fields:`);
541
- for (const f of plan.packageDiffs) {
542
- log.info(` ${dim(f.field.padEnd(16, " "))} ${dim(f.before.length === 0 ? "(empty)" : f.before)} → ${cyan(f.after)}`);
543
- }
544
- }
545
- else if (!ctx.packageUpdate) {
546
- log.info(` ${dim("·")} package.json untouched ${dim("(--no-package-update)")}`);
547
- }
548
- log.info(` ${green("~")} .vibeops.json github:`);
549
- log.info(` ${dim("owner".padEnd(16, " "))} ${cyan(plan.owner.length > 0 ? plan.owner : "(none)")}`);
550
- log.info(` ${dim("repo".padEnd(16, " "))} ${cyan(plan.repo.length > 0 ? plan.repo : "(none)")}`);
551
- log.info(` ${dim("remote".padEnd(16, " "))} ${cyan(ctx.remoteName)}`);
552
- log.info(` ${dim("visibility".padEnd(16, " "))} ${cyan(plan.visibility.length > 0 ? plan.visibility : "(unknown)")}`);
553
- log.info(` ${dim("url".padEnd(16, " "))} ${cyan(plan.remoteUrl ?? "(unset)")}`);
554
- }