@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,595 +0,0 @@
1
- import { resolve } from "node:path";
2
- import { notionProjectsTargetId, notionTasksTargetId, readConfig, } from "../lib/config.js";
3
- import { bold, cyan, dim, green, log, red, yellow } from "../lib/logger.js";
4
- import { loadNotionEnv, maskToken } from "../lib/notion-env.js";
5
- import { createNotionClient, notionApiError, } from "../lib/notion-client.js";
6
- import { PROJECTS_DB_PROPERTIES, TASKS_DB_PROPERTIES, validateDatabaseSchema, } from "../lib/notion-schema.js";
7
- import { resolveNotionDataSourceTarget, } from "../lib/notion-target.js";
8
- import { projectPaths } from "../lib/paths.js";
9
- export async function notionTestCommand(options = {}) {
10
- const cwd = resolve(options.cwd ?? process.cwd());
11
- const paths = projectPaths(cwd);
12
- const wantJson = options.json === true;
13
- const wantShape = options.debugShape === true;
14
- const report = {
15
- cwd,
16
- configPresent: false,
17
- notion: null,
18
- envSource: "none",
19
- hasToken: false,
20
- tokenMasked: null,
21
- checks: [],
22
- ok: false,
23
- };
24
- const config = await readConfig(paths.root);
25
- report.configPresent = config !== null;
26
- const cfgCheck = config
27
- ? {
28
- id: "config.present",
29
- label: "Load .vibeops.json",
30
- ok: true,
31
- status: "ok",
32
- }
33
- : {
34
- id: "config.present",
35
- label: "Load .vibeops.json",
36
- ok: false,
37
- status: "fail",
38
- detail: `${paths.config} is missing. Run \`vibeops init\` first.`,
39
- };
40
- report.checks.push(cfgCheck);
41
- if (config === null) {
42
- return finalize(report, wantJson);
43
- }
44
- const notion = config.notion ?? null;
45
- report.notion = notion;
46
- const enabledCheck = notion?.enabled
47
- ? {
48
- id: "config.notion.enabled",
49
- label: "notion.enabled = true",
50
- ok: true,
51
- status: "ok",
52
- }
53
- : {
54
- id: "config.notion.enabled",
55
- label: "notion.enabled = true",
56
- ok: false,
57
- status: "fail",
58
- detail: `Currently false (or missing). Enable it with \`vibeops notion init --enable\`.`,
59
- };
60
- report.checks.push(enabledCheck);
61
- const projectsInputId = notion === null ? "" : notionProjectsTargetId(notion);
62
- const tasksInputId = notion === null ? "" : notionTasksTargetId(notion);
63
- const projDbCheck = notion && projectsInputId.length > 0
64
- ? {
65
- id: "config.notion.projectsTarget",
66
- label: "notion.projectsTargetId/projectsDatabaseId configured",
67
- ok: true,
68
- status: "ok",
69
- detail: notion.projectsTargetId.length > 0
70
- ? `projectsTargetId=${notion.projectsTargetId}`
71
- : `projectsDatabaseId=${notion.projectsDatabaseId}`,
72
- }
73
- : {
74
- id: "config.notion.projectsTarget",
75
- label: "notion.projectsTargetId/projectsDatabaseId configured",
76
- ok: false,
77
- status: "fail",
78
- detail: "Empty. Run `vibeops notion init` to discover a data_source target, or set the fallback via `--projects-db <id>`.",
79
- };
80
- report.checks.push(projDbCheck);
81
- const tasksDbCheck = notion && tasksInputId.length > 0
82
- ? {
83
- id: "config.notion.tasksTarget",
84
- label: "notion.tasksTargetId/tasksDatabaseId configured",
85
- ok: true,
86
- status: "ok",
87
- detail: notion.tasksTargetId.length > 0
88
- ? `tasksTargetId=${notion.tasksTargetId}`
89
- : `tasksDatabaseId=${notion.tasksDatabaseId}`,
90
- }
91
- : {
92
- id: "config.notion.tasksTarget",
93
- label: "notion.tasksTargetId/tasksDatabaseId configured",
94
- ok: false,
95
- status: "fail",
96
- detail: "Empty. Run `vibeops notion init` to discover a data_source target, or set the fallback via `--tasks-db <id>`.",
97
- };
98
- report.checks.push(tasksDbCheck);
99
- const envInputs = await loadNotionEnv(cwd);
100
- report.envSource = envInputs.source;
101
- report.hasToken = envInputs.token !== null;
102
- report.tokenMasked = envInputs.token === null ? null : maskToken(envInputs.token);
103
- const tokenCheck = envInputs.token
104
- ? {
105
- id: "env.notion.token",
106
- label: `Load NOTION_TOKEN (source: ${envInputs.source})`,
107
- ok: true,
108
- status: "ok",
109
- detail: report.tokenMasked,
110
- }
111
- : {
112
- id: "env.notion.token",
113
- label: "Load NOTION_TOKEN",
114
- ok: false,
115
- status: "fail",
116
- detail: "Not found. Add `NOTION_TOKEN=secret_…` to .vibeops.env or export it via process.env.",
117
- };
118
- report.checks.push(tokenCheck);
119
- const canCallApi = config !== null &&
120
- notion !== null &&
121
- notion.enabled &&
122
- projectsInputId.length > 0 &&
123
- tasksInputId.length > 0 &&
124
- envInputs.token !== null;
125
- if (!canCallApi) {
126
- skipApi(report, "config / env not ready — skipping Notion API calls");
127
- return finalize(report, wantJson);
128
- }
129
- let client;
130
- try {
131
- client = await createNotionClient(envInputs.token);
132
- }
133
- catch (err) {
134
- const apiErr = notionApiError(err);
135
- report.checks.push({
136
- id: "notion.sdk.load",
137
- label: "Load @notionhq/client",
138
- ok: false,
139
- status: "fail",
140
- detail: apiErr.message,
141
- apiError: apiErr,
142
- });
143
- skipApi(report, "Failed to load @notionhq/client");
144
- return finalize(report, wantJson);
145
- }
146
- report.checks.push({
147
- id: "notion.sdk.load",
148
- label: "Load @notionhq/client",
149
- ok: true,
150
- status: "ok",
151
- });
152
- const usersMeOk = await runCheck({
153
- id: "notion.users.me",
154
- label: "Notion API auth (users.me)",
155
- run: async () => {
156
- const me = await client.usersMe();
157
- return { detail: `${me.type ?? "bot"} · ${me.id}` };
158
- },
159
- });
160
- report.checks.push(usersMeOk);
161
- if (!usersMeOk.ok) {
162
- skipApi(report, "users.me failed — skipping downstream checks", [
163
- "notion.projects.retrieve",
164
- "notion.projects.schema",
165
- "notion.tasks.retrieve",
166
- "notion.tasks.schema",
167
- ]);
168
- return finalize(report, wantJson);
169
- }
170
- if (wantShape) {
171
- report.debugShape = [];
172
- for (const kind of ["projects", "tasks"]) {
173
- const inputId = kind === "projects" ? projectsInputId : tasksInputId;
174
- try {
175
- const resolved = await resolveNotionDataSourceTarget(client, inputId, kind);
176
- if (resolved.ok) {
177
- const configuredContainer = kind === "projects" ? notion.projectsDatabaseId : notion.tasksDatabaseId;
178
- const configuredTarget = kind === "projects" ? notion.projectsTargetId : notion.tasksTargetId;
179
- const projectViolations = validateDatabaseSchema({
180
- db: "projects",
181
- required: PROJECTS_DB_PROPERTIES,
182
- retrieveResponse: resolved.properties,
183
- });
184
- const taskViolations = validateDatabaseSchema({
185
- db: "tasks",
186
- required: TASKS_DB_PROPERTIES,
187
- retrieveResponse: resolved.properties,
188
- });
189
- const schemaHint = projectViolations.length === 0
190
- ? "project"
191
- : taskViolations.length === 0
192
- ? "task"
193
- : "unknown";
194
- report.debugShape.push({
195
- kind,
196
- inputId,
197
- resolvedDataSourceId: resolved.resolvedId,
198
- source: debugSourceLabel({
199
- rawSource: resolved.source,
200
- configuredTarget,
201
- configuredContainer,
202
- inputId,
203
- }),
204
- hasProperties: true,
205
- propertiesKeysCount: Object.keys(resolved.properties).length,
206
- schemaHint,
207
- });
208
- continue;
209
- }
210
- }
211
- catch {
212
- // Keep going to the raw database-shape probe below.
213
- }
214
- try {
215
- const shape = await client.probeDatabaseShape(inputId);
216
- report.debugShape.push({ kind, inputId, shape });
217
- }
218
- catch (err) {
219
- const apiErr = notionApiError(err);
220
- report.debugShape.push({
221
- kind,
222
- inputId,
223
- error: {
224
- code: apiErr.code,
225
- ...(apiErr.status !== undefined ? { status: apiErr.status } : {}),
226
- message: apiErr.message,
227
- },
228
- });
229
- }
230
- }
231
- }
232
- await runResolveAndSchema(client, projectsInputId, "projects", report);
233
- await runResolveAndSchema(client, tasksInputId, "tasks", report);
234
- return finalize(report, wantJson);
235
- }
236
- function debugSourceLabel(inputs) {
237
- if (inputs.configuredTarget.length > 0 &&
238
- inputs.configuredContainer.length > 0 &&
239
- inputs.inputId === inputs.configuredTarget) {
240
- return "page-child-database";
241
- }
242
- if (inputs.rawSource === "input-data-source")
243
- return "direct-data-source";
244
- if (inputs.rawSource === "database-default-data-source")
245
- return "database-data-source";
246
- return inputs.rawSource;
247
- }
248
- /**
249
- * Shared resolve + schema-check pair — keeps `notion test` in lock-step with
250
- * `notion sync`. Both go through `resolveNotionDataSourceTarget` so the
251
- * `database → data_source` fallback path applies uniformly.
252
- *
253
- * Emits three checks per kind:
254
- * - `notion.{kind}.retrieve` — the `database` retrieve call (transport).
255
- * - `notion.{kind}.resolve` — the resolver result (input id → resolved id).
256
- * - `notion.{kind}.schema` — the property-schema validation.
257
- */
258
- async function runResolveAndSchema(client, inputId, kind, report) {
259
- const required = kind === "projects" ? PROJECTS_DB_PROPERTIES : TASKS_DB_PROPERTIES;
260
- const retrieveId = `notion.${kind}.retrieve`;
261
- const resolveId = `notion.${kind}.resolve`;
262
- const schemaId = `notion.${kind}.schema`;
263
- const labelDb = kind === "projects" ? "Projects DB" : "Tasks DB";
264
- let resolved;
265
- try {
266
- resolved = await resolveNotionDataSourceTarget(client, inputId, kind);
267
- }
268
- catch (err) {
269
- const apiErr = notionApiError(err);
270
- report.checks.push({
271
- id: retrieveId,
272
- label: `${labelDb} retrieve`,
273
- ok: false,
274
- status: "fail",
275
- detail: explainNotionError(apiErr),
276
- apiError: apiErr,
277
- });
278
- report.checks.push({
279
- id: resolveId,
280
- label: `${labelDb} resolve target (database → data_source)`,
281
- ok: false,
282
- status: "skip",
283
- detail: "Skipped because retrieve failed",
284
- });
285
- report.checks.push({
286
- id: schemaId,
287
- label: `${labelDb} required-property validation`,
288
- ok: false,
289
- status: "skip",
290
- detail: "Skipped because retrieve failed",
291
- });
292
- return;
293
- }
294
- // `notion.{kind}.retrieve` — pass when resolver got past at least one Notion call.
295
- if (resolved.ok) {
296
- report.checks.push({
297
- id: retrieveId,
298
- label: `${labelDb} retrieve`,
299
- ok: true,
300
- status: "ok",
301
- detail: `input id=${inputId} input object=${resolved.inputObject}`,
302
- });
303
- }
304
- else if (resolved.reason === "transport") {
305
- report.checks.push({
306
- id: retrieveId,
307
- label: `${labelDb} retrieve`,
308
- ok: false,
309
- status: "fail",
310
- detail: resolved.message,
311
- ...(resolved.apiError !== undefined ? { apiError: resolved.apiError } : {}),
312
- });
313
- report.checks.push({
314
- id: resolveId,
315
- label: `${labelDb} resolve target (database → data_source)`,
316
- ok: false,
317
- status: "skip",
318
- detail: "Skipped because retrieve failed",
319
- });
320
- report.checks.push({
321
- id: schemaId,
322
- label: `${labelDb} required-property validation`,
323
- ok: false,
324
- status: "skip",
325
- detail: "Skipped because retrieve failed",
326
- });
327
- return;
328
- }
329
- else {
330
- // resolver did the database retrieve OK but found no usable data_source.
331
- report.checks.push({
332
- id: retrieveId,
333
- label: `${labelDb} retrieve`,
334
- ok: true,
335
- status: "ok",
336
- detail: `input id=${inputId} input object=${resolved.partial?.inputObject ?? "(unknown)"}`,
337
- });
338
- }
339
- // `notion.{kind}.resolve`
340
- if (resolved.ok) {
341
- const detail = `input id=${resolved.inputId} input object=${resolved.inputObject} ` +
342
- `resolved id=${resolved.resolvedId} resolved object=${resolved.resolvedObject} ` +
343
- `source=${resolved.source}`;
344
- report.checks.push({
345
- id: resolveId,
346
- label: `${labelDb} resolve target (database → data_source)`,
347
- ok: true,
348
- status: "ok",
349
- detail,
350
- });
351
- }
352
- else {
353
- report.checks.push({
354
- id: resolveId,
355
- label: `${labelDb} resolve target (database → data_source)`,
356
- ok: false,
357
- status: "fail",
358
- detail: resolved.message,
359
- });
360
- report.checks.push({
361
- id: schemaId,
362
- label: `${labelDb} required-property validation`,
363
- ok: false,
364
- status: "skip",
365
- detail: "Skipped because target resolution failed",
366
- });
367
- return;
368
- }
369
- // `notion.{kind}.schema`
370
- const violations = validateDatabaseSchema({
371
- db: kind,
372
- required,
373
- retrieveResponse: resolved.properties,
374
- });
375
- report.checks.push({
376
- id: schemaId,
377
- label: `${labelDb} required-property validation`,
378
- ok: violations.length === 0,
379
- status: violations.length === 0 ? "ok" : "fail",
380
- ...(violations.length === 0
381
- ? { detail: `All ${required.length} properties exist with matching types` }
382
- : { detail: `${violations.length} violation(s)`, violations }),
383
- });
384
- }
385
- async function runCheck(inputs) {
386
- try {
387
- const r = await inputs.run();
388
- return {
389
- id: inputs.id,
390
- label: inputs.label,
391
- ok: true,
392
- status: "ok",
393
- ...(r.detail ? { detail: r.detail } : {}),
394
- };
395
- }
396
- catch (err) {
397
- const apiErr = notionApiError(err);
398
- return {
399
- id: inputs.id,
400
- label: inputs.label,
401
- ok: false,
402
- status: "fail",
403
- detail: explainNotionError(apiErr),
404
- apiError: apiErr,
405
- };
406
- }
407
- }
408
- function explainNotionError(err) {
409
- const tail = err.status ? ` (HTTP ${err.status})` : "";
410
- switch (err.code) {
411
- case "unauthorized":
412
- return `NOTION_TOKEN was rejected. The integration may be expired or the token incorrect.${tail}`;
413
- case "restricted_resource":
414
- return `The Notion DB is not shared with the integration. Open the DB page → ⋯ → Connections and add the integration.${tail}`;
415
- case "object_not_found":
416
- return `Database id not found. Verify projectsDatabaseId / tasksDatabaseId in .vibeops.json.${tail}`;
417
- case "validation_error":
418
- return `Request rejected (validation_error): ${err.message}${tail}`;
419
- case "rate_limited":
420
- return `Notion API rate limit. Retry shortly.${tail}`;
421
- case "request_timeout":
422
- case "ETIMEDOUT":
423
- return `Notion API 5s timeout. Check your network.${tail}`;
424
- default:
425
- return `${err.code}: ${err.message}${tail}`;
426
- }
427
- }
428
- function skipApi(report, reason, ids = [
429
- "notion.sdk.load",
430
- "notion.users.me",
431
- "notion.projects.retrieve",
432
- "notion.projects.schema",
433
- "notion.tasks.retrieve",
434
- "notion.tasks.schema",
435
- ]) {
436
- for (const id of ids) {
437
- if (report.checks.some((c) => c.id === id))
438
- continue;
439
- report.checks.push({
440
- id,
441
- label: humanLabel(id),
442
- ok: false,
443
- status: "skip",
444
- detail: reason,
445
- });
446
- }
447
- }
448
- function humanLabel(id) {
449
- switch (id) {
450
- case "notion.sdk.load": return "Load @notionhq/client";
451
- case "notion.users.me": return "Notion API auth (users.me)";
452
- case "notion.projects.retrieve": return "databases.retrieve(projectsDatabaseId)";
453
- case "notion.projects.schema": return "Projects DB required-property validation";
454
- case "notion.tasks.retrieve": return "databases.retrieve(tasksDatabaseId)";
455
- case "notion.tasks.schema": return "Tasks DB required-property validation";
456
- default: return id;
457
- }
458
- }
459
- function finalize(report, wantJson) {
460
- report.ok = report.checks.every((c) => c.status === "ok");
461
- if (wantJson) {
462
- const sanitized = sanitizeForJson(report);
463
- process.stdout.write(`${JSON.stringify(sanitized, null, 2)}\n`);
464
- process.exitCode = report.ok ? 0 : 1;
465
- return;
466
- }
467
- log.info(bold("vibeops notion test"));
468
- log.info(` ${dim("cwd")} ${report.cwd}`);
469
- log.blank();
470
- if (report.debugShape !== undefined && report.debugShape.length > 0) {
471
- for (const probe of report.debugShape) {
472
- const kindLabel = probe.kind === "projects" ? "Projects DB shape" : "Tasks DB shape";
473
- log.info(` ${bold(kindLabel)} ${dim(`input id=${probe.inputId}`)}`);
474
- if (probe.error !== undefined) {
475
- log.info(` ${dim("retrieve failed:")} ${red(probe.error.code)}${probe.error.status !== undefined ? ` (HTTP ${probe.error.status})` : ""} — ${probe.error.message}`);
476
- continue;
477
- }
478
- if (probe.resolvedDataSourceId !== undefined) {
479
- log.info(` ${dim("selected input id ")} ${cyan(probe.inputId)}`);
480
- log.info(` ${dim("resolved source ")} ${probe.source ?? "(unknown)"}`);
481
- log.info(` ${dim("resolved data src ")} ${cyan(probe.resolvedDataSourceId)}`);
482
- log.info(` ${dim("has properties ")} ${probe.hasProperties ? green("yes") : red("no")}`);
483
- log.info(` ${dim("property keys ")} ${String(probe.propertiesKeysCount ?? 0)}`);
484
- log.info(` ${dim("schema hint ")} ${probe.schemaHint ?? "unknown"}`);
485
- continue;
486
- }
487
- const s = probe.shape;
488
- log.info(` ${dim("object ")} ${s.object}`);
489
- log.info(` ${dim("id ")} ${cyan(s.id)}`);
490
- if (s.title !== undefined) {
491
- log.info(` ${dim("title ")} ${s.title}`);
492
- }
493
- log.info(` ${dim("has properties ")} ${s.hasProperties ? green("yes") : red("no")}${s.hasProperties ? ` ${dim(`(len=${s.propertiesKeysLength})`)}` : ""}`);
494
- log.info(` ${dim("data_sources ")} ${s.hasDataSources ? "" : red("not present")}${s.hasDataSources ? `${cyan(`${s.dataSourcesLength}`)}${s.dataSourcesField !== undefined && s.dataSourcesField !== "data_sources" ? ` ${yellow(`(via '${s.dataSourcesField}')`)}` : ""}` : ""}`);
495
- for (const ref of s.dataSources) {
496
- log.info(` ${dim("-")} id=${cyan(ref.id)}${ref.name !== undefined ? ` name=${ref.name}` : ""}`);
497
- }
498
- log.info(` ${dim("top-level keys ")} ${s.topLevelKeys.join(", ")}`);
499
- }
500
- log.blank();
501
- }
502
- for (const c of report.checks) {
503
- const tag = c.status === "ok"
504
- ? green("✓")
505
- : c.status === "skip"
506
- ? yellow("·")
507
- : red("✗");
508
- log.info(` ${tag} ${c.label}`);
509
- if (c.detail)
510
- log.info(` ${dim(c.detail)}`);
511
- if (c.violations && c.violations.length > 0) {
512
- for (const v of c.violations) {
513
- if (v.kind === "status-options-missing" ||
514
- v.kind === "status-options-unreadable") {
515
- const tag2 = red(v.kind === "status-options-missing"
516
- ? "status-options-missing"
517
- : "status-options-unreadable");
518
- log.info(` · ${tag2} ${cyan(v.property)}`);
519
- if (v.kind === "status-options-missing" &&
520
- v.missingOptions !== undefined &&
521
- v.missingOptions.length > 0) {
522
- log.info(` ${dim("missing")} ${v.missingOptions.join(", ")}`);
523
- }
524
- if (v.requiredOptions !== undefined && v.requiredOptions.length > 0) {
525
- log.info(` ${dim("Add these options in Notion")}: Status property → Edit options → ${v.requiredOptions.join(", ")}`);
526
- }
527
- if (v.kind === "status-options-missing" &&
528
- v.foundOptions !== undefined &&
529
- v.foundOptions.length > 0) {
530
- log.info(` ${dim("found in Notion")}: ${v.foundOptions.join(", ")}`);
531
- }
532
- continue;
533
- }
534
- const tag2 = v.kind === "missing"
535
- ? red("missing")
536
- : v.kind === "type-mismatch"
537
- ? red("type-mismatch")
538
- : v.kind === "missing-properties"
539
- ? red("missing-properties")
540
- : red(v.kind);
541
- const detail = v.kind === "missing"
542
- ? `expected types: ${v.allowedTypes.join(" | ")}`
543
- : v.kind === "type-mismatch"
544
- ? `expected ${v.allowedTypes.join(" | ")} but got ${v.actualType ?? "?"}`
545
- : v.description;
546
- log.info(` · ${tag2} ${cyan(v.property)} — ${dim(detail)}`);
547
- }
548
- }
549
- }
550
- log.blank();
551
- if (report.ok) {
552
- log.ok("Notion connection OK — ready for sync.");
553
- process.exitCode = 0;
554
- }
555
- else {
556
- const failed = report.checks.filter((c) => c.status === "fail").length;
557
- const skipped = report.checks.filter((c) => c.status === "skip").length;
558
- log.error(`Notion connection failed — ${failed} fail${skipped > 0 ? `, ${skipped} skipped` : ""}.`);
559
- log.info(dim(" Security: NOTION_TOKEN values shown above are masked. The real value lives in .vibeops.env."));
560
- process.exitCode = 1;
561
- }
562
- }
563
- function sanitizeForJson(report) {
564
- return {
565
- cwd: report.cwd,
566
- configPresent: report.configPresent,
567
- notion: report.notion
568
- ? {
569
- enabled: report.notion.enabled,
570
- projectsTargetId: report.notion.projectsTargetId,
571
- tasksTargetId: report.notion.tasksTargetId,
572
- projectsDatabaseId: report.notion.projectsDatabaseId,
573
- tasksDatabaseId: report.notion.tasksDatabaseId,
574
- }
575
- : null,
576
- env: {
577
- source: report.envSource,
578
- hasToken: report.hasToken,
579
- tokenMasked: report.tokenMasked,
580
- },
581
- ok: report.ok,
582
- checks: report.checks.map((c) => ({
583
- id: c.id,
584
- label: c.label,
585
- status: c.status,
586
- ok: c.ok,
587
- ...(c.detail ? { detail: c.detail } : {}),
588
- ...(c.violations ? { violations: c.violations } : {}),
589
- ...(c.apiError ? { apiError: c.apiError } : {}),
590
- })),
591
- ...(report.debugShape !== undefined
592
- ? { debugShape: report.debugShape }
593
- : {}),
594
- };
595
- }