@gh-symphony/cli 0.0.22 → 0.1.2

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 (36) hide show
  1. package/README.md +72 -77
  2. package/dist/{chunk-IWFX2FMA.js → chunk-6I753NYO.js} +4 -1
  3. package/dist/{workflow-L3KT6HB7.js → chunk-B4ZJMAZL.js} +27 -19
  4. package/dist/{chunk-2TSM3INR.js → chunk-DLZAJXZL.js} +575 -12
  5. package/dist/chunk-GHVDABFO.js +235 -0
  6. package/dist/{chunk-EEQQWTXS.js → chunk-GPRCOJDJ.js} +158 -75
  7. package/dist/{chunk-36KYEDEO.js → chunk-MVRF7BES.js} +1 -10
  8. package/dist/{chunk-2UW7NQLX.js → chunk-VFHMHHZW.js} +1 -1
  9. package/dist/{chunk-HMLBBZNY.js → chunk-WM2B6BJ7.js} +16 -71
  10. package/dist/{chunk-QIRE2VXS.js → chunk-WOVNN5NW.js} +16 -17
  11. package/dist/{chunk-C67H3OUL.js → chunk-Z3NZOPLZ.js} +0 -81
  12. package/dist/{config-cmd-Z3A7V6NC.js → config-cmd-2ADPUYWA.js} +1 -1
  13. package/dist/{doctor-EJUMPBMW.js → doctor-EEPNFCGF.js} +464 -40
  14. package/dist/index.js +340 -294
  15. package/dist/{chunk-PUDXVBSN.js → repo-RX4OK7XH.js} +5944 -3001
  16. package/dist/{setup-TZJSM3QV.js → setup-XNHHRBGU.js} +57 -92
  17. package/dist/{upgrade-O33S2SJK.js → upgrade-NS53EO2B.js} +2 -2
  18. package/dist/{version-CW54Q7BK.js → version-2RHFZ5CI.js} +1 -1
  19. package/dist/worker-entry.js +10 -5
  20. package/dist/workflow-26QNZZWH.js +22 -0
  21. package/package.json +4 -4
  22. package/dist/chunk-DDL4BWSL.js +0 -146
  23. package/dist/chunk-DFLXHNYQ.js +0 -482
  24. package/dist/chunk-E7HYEEZD.js +0 -1318
  25. package/dist/chunk-GDE6FYN4.js +0 -26
  26. package/dist/chunk-GSX2FV3M.js +0 -103
  27. package/dist/chunk-ZHOKYUO3.js +0 -1047
  28. package/dist/init-54HMKNYI.js +0 -38
  29. package/dist/logs-GTZ4U5JE.js +0 -188
  30. package/dist/project-RMYMZSFV.js +0 -25
  31. package/dist/recover-LTLKMTRX.js +0 -133
  32. package/dist/repo-WI7GF6XQ.js +0 -749
  33. package/dist/run-IHN3ZL35.js +0 -122
  34. package/dist/start-RTAHQMR2.js +0 -19
  35. package/dist/status-F4D52OVK.js +0 -12
  36. package/dist/stop-MDKMJPVR.js +0 -10
@@ -1,4 +1,18 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ parseIssueReference,
4
+ readGitHubProjectBinding,
5
+ renderIssueWorkflowPreview
6
+ } from "./chunk-B4ZJMAZL.js";
7
+ import "./chunk-WM2B6BJ7.js";
8
+ import {
9
+ fetchGithubProjectIssueByRepositoryAndNumber,
10
+ fetchGithubProjectIssues,
11
+ inspectManagedProjectSelection
12
+ } from "./chunk-DLZAJXZL.js";
13
+ import {
14
+ resolveRuntimeRoot
15
+ } from "./chunk-6I753NYO.js";
2
16
  import {
3
17
  GitHubApiError,
4
18
  REQUIRED_GH_SCOPES,
@@ -6,27 +20,22 @@ import {
6
20
  checkGhInstalled,
7
21
  checkGhScopes,
8
22
  createClient,
23
+ findLinkedRepository,
9
24
  getEnvGitHubToken,
10
25
  getGhToken,
11
26
  getProjectDetail,
12
27
  runGhAuthLogin,
13
28
  runGhAuthRefresh,
14
29
  validateGitHubToken
15
- } from "./chunk-C67H3OUL.js";
30
+ } from "./chunk-Z3NZOPLZ.js";
16
31
  import {
17
32
  isClaudeRuntimeCommand,
18
33
  parseWorkflowMarkdown,
19
34
  resolveClaudeCommandBinary,
20
35
  resolveRuntimeCommandBinary,
21
36
  runClaudePreflight
22
- } from "./chunk-EEQQWTXS.js";
23
- import {
24
- resolveRuntimeRoot
25
- } from "./chunk-IWFX2FMA.js";
26
- import {
27
- inspectManagedProjectSelection
28
- } from "./chunk-DDL4BWSL.js";
29
- import "./chunk-QIRE2VXS.js";
37
+ } from "./chunk-GPRCOJDJ.js";
38
+ import "./chunk-WOVNN5NW.js";
30
39
 
31
40
  // src/commands/doctor.ts
32
41
  import { constants } from "fs";
@@ -43,6 +52,8 @@ var DEFAULT_DEPENDENCIES = {
43
52
  inspectManagedProjectSelection,
44
53
  createClient,
45
54
  getProjectDetail,
55
+ fetchProjectIssues: fetchGithubProjectIssues,
56
+ fetchProjectIssue: fetchGithubProjectIssueByRepositoryAndNumber,
46
57
  readFile,
47
58
  access,
48
59
  mkdir,
@@ -64,8 +75,9 @@ var DEFAULT_DEPENDENCIES = {
64
75
  };
65
76
  var MINIMUM_NODE_MAJOR = 24;
66
77
  var MINIMUM_NODE_VERSION = `v${MINIMUM_NODE_MAJOR}.0.0`;
78
+ var DOCTOR_USAGE = "Usage: gh-symphony doctor [--project-id <project-id>] [--fix] [--smoke] [--issue <owner/repo#number>]";
67
79
  function parseDoctorArgs(args) {
68
- const parsed = { fix: false };
80
+ const parsed = { fix: false, smoke: false };
69
81
  for (let i = 0; i < args.length; i += 1) {
70
82
  const arg = args[i];
71
83
  if (arg === "--project" || arg === "--project-id") {
@@ -82,11 +94,28 @@ function parseDoctorArgs(args) {
82
94
  parsed.fix = true;
83
95
  continue;
84
96
  }
97
+ if (arg === "--smoke") {
98
+ parsed.smoke = true;
99
+ continue;
100
+ }
101
+ if (arg === "--issue") {
102
+ const value = args[i + 1];
103
+ if (!value || value.startsWith("-")) {
104
+ parsed.error = "Option '--issue' argument missing";
105
+ return parsed;
106
+ }
107
+ parsed.issue = value;
108
+ i += 1;
109
+ continue;
110
+ }
85
111
  if (arg?.startsWith("-")) {
86
112
  parsed.error = `Unknown option '${arg}'`;
87
113
  return parsed;
88
114
  }
89
115
  }
116
+ if (parsed.issue && !parsed.smoke) {
117
+ parsed.error = "Option '--issue' requires '--smoke'";
118
+ }
90
119
  return parsed;
91
120
  }
92
121
  function passCheck(id, title, summary, details) {
@@ -310,7 +339,7 @@ async function checkWorkflow(repoRoot, deps) {
310
339
  reason: "missing",
311
340
  workflowPath,
312
341
  summary: "WORKFLOW.md was not found in the repository root.",
313
- remediation: "Run 'gh-symphony init' in this repository or add a valid WORKFLOW.md at the repo root."
342
+ remediation: "Run 'gh-symphony workflow init' in this repository or add a valid WORKFLOW.md at the repo root."
314
343
  };
315
344
  }
316
345
  try {
@@ -319,7 +348,8 @@ async function checkWorkflow(repoRoot, deps) {
319
348
  status: "pass",
320
349
  command: parsed.agentCommand,
321
350
  workflowPath,
322
- format: parsed.format
351
+ format: parsed.format,
352
+ workflow: parsed
323
353
  };
324
354
  } catch (error) {
325
355
  return {
@@ -327,20 +357,394 @@ async function checkWorkflow(repoRoot, deps) {
327
357
  reason: "invalid",
328
358
  workflowPath,
329
359
  summary: "WORKFLOW.md could not be parsed.",
330
- remediation: "Fix the WORKFLOW.md front matter or re-run 'gh-symphony init' to regenerate it.",
360
+ remediation: "Fix the WORKFLOW.md front matter or re-run 'gh-symphony workflow init' to regenerate it.",
331
361
  error: error instanceof Error ? error.message : "Unknown workflow parse error."
332
362
  };
333
363
  }
334
364
  }
365
+ function buildGithubTrackerConfig(input) {
366
+ const settings = input.projectConfig.projectConfig.tracker.settings;
367
+ return {
368
+ projectId: input.bindingId,
369
+ token: input.token,
370
+ apiUrl: input.projectConfig.projectConfig.tracker.apiUrl,
371
+ lifecycle: input.workflow.lifecycle,
372
+ assignedOnly: settings?.assignedOnly === true,
373
+ priorityFieldName: typeof settings?.priorityFieldName === "string" ? settings.priorityFieldName : void 0,
374
+ timeoutMs: typeof settings?.timeoutMs === "number" ? settings.timeoutMs : void 0
375
+ };
376
+ }
377
+ function isActiveSmokeIssue(issue, workflow) {
378
+ const normalized = issue.state.trim().toLowerCase();
379
+ return workflow.lifecycle.activeStates.some(
380
+ (state) => state.trim().toLowerCase() === normalized
381
+ );
382
+ }
383
+ function selectRepresentativeIssue(issues, workflow) {
384
+ const activeIssues = issues.filter(
385
+ (issue) => isActiveSmokeIssue(issue, workflow)
386
+ );
387
+ activeIssues.sort((a, b) => {
388
+ const left = a.updatedAt ?? a.createdAt ?? "";
389
+ const right = b.updatedAt ?? b.createdAt ?? "";
390
+ return right.localeCompare(left);
391
+ });
392
+ return activeIssues[0] ?? null;
393
+ }
394
+ function isRepositoryConfigured(projectConfig, owner, name) {
395
+ const repositories = [projectConfig.projectConfig.repository].filter(
396
+ (repository) => Boolean(repository?.owner && repository.name)
397
+ );
398
+ const normalizedOwner = owner.trim().toLowerCase();
399
+ const normalizedName = name.trim().toLowerCase();
400
+ return repositories.some(
401
+ (repo) => repo.owner.trim().toLowerCase() === normalizedOwner && repo.name.trim().toLowerCase() === normalizedName
402
+ );
403
+ }
404
+ function isHookPathLike(command) {
405
+ const trimmed = command.trim();
406
+ return trimmed.length > 0 && !trimmed.includes("\n") && !/\s/.test(trimmed) && (trimmed.startsWith("/") || trimmed.startsWith("./") || trimmed.startsWith("../") || trimmed.includes("/") || trimmed.includes("\\"));
407
+ }
408
+ async function buildHookChecks(repoRoot, workflow, deps) {
409
+ const hooks = [
410
+ ["after_create", workflow.hooks.afterCreate],
411
+ ["before_run", workflow.hooks.beforeRun],
412
+ ["after_run", workflow.hooks.afterRun],
413
+ ["before_remove", workflow.hooks.beforeRemove]
414
+ ];
415
+ const configured = hooks.filter(([, command]) => command);
416
+ if (configured.length === 0) {
417
+ return [
418
+ passCheck(
419
+ "workflow_hooks",
420
+ "Workflow hook paths",
421
+ "No WORKFLOW.md hooks are configured.",
422
+ { configured: 0 }
423
+ )
424
+ ];
425
+ }
426
+ const unresolved = [];
427
+ const checked = [];
428
+ let inline = 0;
429
+ for (const [hook, command] of configured) {
430
+ if (!command || !isHookPathLike(command)) {
431
+ inline += 1;
432
+ continue;
433
+ }
434
+ const path = isAbsolute(command) ? command : resolve(repoRoot, command);
435
+ try {
436
+ await deps.access(path, constants.F_OK);
437
+ checked.push({ hook, command, path });
438
+ } catch {
439
+ unresolved.push({ hook, command, path });
440
+ }
441
+ }
442
+ if (unresolved.length > 0) {
443
+ return [
444
+ failCheck(
445
+ "workflow_hooks",
446
+ "Workflow hook paths",
447
+ `Unresolved WORKFLOW.md hook path${unresolved.length === 1 ? "" : "s"}: ${unresolved.map((entry) => `${entry.hook}=${entry.command}`).join(", ")}.`,
448
+ "Create the referenced hook script(s), fix the hook path(s), or replace them with inline commands.",
449
+ { configured: configured.length, pathsChecked: checked.length, inline, unresolved, checked }
450
+ )
451
+ ];
452
+ }
453
+ const pathSummary = checked.length === 0 ? "No hook paths required filesystem validation." : `Resolved ${checked.length} WORKFLOW.md hook path${checked.length === 1 ? "" : "s"}.`;
454
+ const inlineSummary = inline === 0 ? "" : ` Treated ${inline} hook${inline === 1 ? "" : "s"} as inline command${inline === 1 ? "" : "s"}.`;
455
+ return [
456
+ passCheck(
457
+ "workflow_hooks",
458
+ "Workflow hook paths",
459
+ `${pathSummary}${inlineSummary}`,
460
+ { configured: configured.length, pathsChecked: checked.length, inline, checked }
461
+ )
462
+ ];
463
+ }
464
+ function formatSmokeError(error) {
465
+ return error instanceof Error ? error.message : String(error);
466
+ }
467
+ async function buildDoctorSmokeChecks(input) {
468
+ const checks = [];
469
+ if (input.selection.kind !== "resolved") {
470
+ checks.push(
471
+ failCheck(
472
+ "smoke_issue",
473
+ "Smoke target issue",
474
+ "Smoke check could not choose an issue because managed project selection failed.",
475
+ "Fix the managed project selection check first, then re-run 'gh-symphony doctor --smoke'.",
476
+ { blockedBy: "managed_project" }
477
+ )
478
+ );
479
+ return checks;
480
+ }
481
+ if (!input.auth) {
482
+ checks.push(
483
+ failCheck(
484
+ "smoke_issue",
485
+ "Smoke target issue",
486
+ "Smoke check could not read live issues because GitHub authentication failed.",
487
+ "Fix GitHub authentication first, then re-run 'gh-symphony doctor --smoke'.",
488
+ { blockedBy: "gh_authentication" }
489
+ )
490
+ );
491
+ return checks;
492
+ }
493
+ if (input.workflow.status !== "pass") {
494
+ checks.push(
495
+ failCheck(
496
+ "smoke_issue",
497
+ "Smoke target issue",
498
+ "Smoke check could not read a live issue because WORKFLOW.md is missing or invalid.",
499
+ "Fix WORKFLOW.md first, then re-run 'gh-symphony doctor --smoke'.",
500
+ { blockedBy: "workflow_file" }
501
+ )
502
+ );
503
+ return checks;
504
+ }
505
+ if (!input.projectBindingId || !input.projectDetail) {
506
+ checks.push(
507
+ failCheck(
508
+ "smoke_issue",
509
+ "Smoke target issue",
510
+ "Smoke check could not read live issues because the GitHub Project binding did not resolve.",
511
+ "Fix the GitHub project resolution check first, then re-run 'gh-symphony doctor --smoke'.",
512
+ { blockedBy: "github_project_resolution" }
513
+ )
514
+ );
515
+ return checks;
516
+ }
517
+ const trackerConfig = buildGithubTrackerConfig({
518
+ projectConfig: input.selection,
519
+ bindingId: input.projectBindingId,
520
+ token: input.auth.token,
521
+ workflow: input.workflow.workflow
522
+ });
523
+ let issue = null;
524
+ if (input.parsedArgs.issue) {
525
+ let issueRef;
526
+ try {
527
+ issueRef = parseIssueReference(input.parsedArgs.issue);
528
+ } catch (error) {
529
+ checks.push(
530
+ failCheck(
531
+ "smoke_issue",
532
+ "Smoke target issue",
533
+ `Smoke check issue reference is invalid: ${input.parsedArgs.issue}.`,
534
+ "Use the expected '--issue owner/repo#number' format and re-run the smoke check.",
535
+ {
536
+ issue: input.parsedArgs.issue,
537
+ expectedFormat: "owner/repo#number",
538
+ error: formatSmokeError(error)
539
+ }
540
+ )
541
+ );
542
+ return checks;
543
+ }
544
+ if (!findLinkedRepository(input.projectDetail, issueRef.owner, issueRef.name)) {
545
+ checks.push(
546
+ failCheck(
547
+ "project_repository_link",
548
+ "Project repository link",
549
+ `Repository ${issueRef.owner}/${issueRef.name} is not linked to GitHub Project "${input.projectDetail.title}".`,
550
+ "Run 'gh-symphony setup' from inside the target repository, or run 'gh-symphony workflow init' followed by 'gh-symphony repo init'.",
551
+ {
552
+ repository: `${issueRef.owner}/${issueRef.name}`,
553
+ projectTitle: input.projectDetail.title
554
+ }
555
+ )
556
+ );
557
+ } else if (!isRepositoryConfigured(input.selection, issueRef.owner, issueRef.name)) {
558
+ checks.push(
559
+ failCheck(
560
+ "project_repository_link",
561
+ "Project repository link",
562
+ `Repository ${issueRef.owner}/${issueRef.name} is linked to the GitHub Project but is not configured locally.`,
563
+ "Run 'gh-symphony repo init' from the target repository before running start.",
564
+ { repository: `${issueRef.owner}/${issueRef.name}` }
565
+ )
566
+ );
567
+ } else {
568
+ checks.push(
569
+ passCheck(
570
+ "project_repository_link",
571
+ "Project repository link",
572
+ `Repository ${issueRef.owner}/${issueRef.name} is linked to the GitHub Project and configured locally.`,
573
+ { repository: `${issueRef.owner}/${issueRef.name}` }
574
+ )
575
+ );
576
+ }
577
+ try {
578
+ issue = await input.deps.fetchProjectIssue(
579
+ trackerConfig,
580
+ { owner: issueRef.owner, name: issueRef.name },
581
+ issueRef.number
582
+ );
583
+ } catch (error) {
584
+ checks.push(
585
+ failCheck(
586
+ "smoke_issue",
587
+ "Smoke target issue",
588
+ `Smoke check could not read issue ${issueRef.identifier}.`,
589
+ "Confirm GitHub token scopes, project visibility, and network access, then re-run the smoke check.",
590
+ { issue: issueRef.identifier, error: formatSmokeError(error) }
591
+ )
592
+ );
593
+ return checks;
594
+ }
595
+ if (!issue) {
596
+ checks.push(
597
+ failCheck(
598
+ "smoke_issue",
599
+ "Smoke target issue",
600
+ `Issue ${issueRef.identifier} is not in the configured GitHub Project or is not readable.`,
601
+ "Add the issue to the project, confirm the token can read it, and re-run the smoke check.",
602
+ { issue: issueRef.identifier }
603
+ )
604
+ );
605
+ return checks;
606
+ }
607
+ } else {
608
+ let issues;
609
+ try {
610
+ issues = await input.deps.fetchProjectIssues(trackerConfig);
611
+ } catch (error) {
612
+ checks.push(
613
+ failCheck(
614
+ "smoke_issue",
615
+ "Smoke target issue",
616
+ `Smoke check could not read live issues from GitHub Project "${input.projectDetail.title}".`,
617
+ "Confirm GitHub token scopes, project visibility, and network access, then re-run the smoke check.",
618
+ {
619
+ projectTitle: input.projectDetail.title,
620
+ error: formatSmokeError(error)
621
+ }
622
+ )
623
+ );
624
+ return checks;
625
+ }
626
+ issue = selectRepresentativeIssue(issues, input.workflow.workflow);
627
+ if (!issue) {
628
+ checks.push(
629
+ failCheck(
630
+ "smoke_issue",
631
+ "Smoke target issue",
632
+ `No active live issue was found in GitHub Project "${input.projectDetail.title}".`,
633
+ `Move one issue into an active state (${input.workflow.workflow.lifecycle.activeStates.join(", ")}) or re-run with '--issue owner/repo#number'.`,
634
+ {
635
+ projectTitle: input.projectDetail.title,
636
+ activeStates: input.workflow.workflow.lifecycle.activeStates
637
+ }
638
+ )
639
+ );
640
+ return checks;
641
+ }
642
+ }
643
+ const repositoryName = `${issue.repository.owner}/${issue.repository.name}`;
644
+ if (!checks.some((check) => check.id === "project_repository_link")) {
645
+ if (!findLinkedRepository(
646
+ input.projectDetail,
647
+ issue.repository.owner,
648
+ issue.repository.name
649
+ )) {
650
+ checks.push(
651
+ failCheck(
652
+ "project_repository_link",
653
+ "Project repository link",
654
+ `Repository ${repositoryName} is not linked to GitHub Project "${input.projectDetail.title}".`,
655
+ "Run 'gh-symphony setup' from inside the target repository, or run 'gh-symphony workflow init' followed by 'gh-symphony repo init'.",
656
+ {
657
+ repository: repositoryName,
658
+ projectTitle: input.projectDetail.title
659
+ }
660
+ )
661
+ );
662
+ } else if (!isRepositoryConfigured(
663
+ input.selection,
664
+ issue.repository.owner,
665
+ issue.repository.name
666
+ )) {
667
+ checks.push(
668
+ failCheck(
669
+ "project_repository_link",
670
+ "Project repository link",
671
+ `Repository ${repositoryName} is linked to the GitHub Project but is not configured locally.`,
672
+ "Run 'gh-symphony repo init' from the target repository before running start.",
673
+ { repository: repositoryName }
674
+ )
675
+ );
676
+ } else {
677
+ checks.push(
678
+ passCheck(
679
+ "project_repository_link",
680
+ "Project repository link",
681
+ `Repository ${repositoryName} is linked to the GitHub Project and configured locally.`,
682
+ { repository: repositoryName }
683
+ )
684
+ );
685
+ }
686
+ }
687
+ checks.push(
688
+ passCheck(
689
+ "smoke_issue",
690
+ "Smoke target issue",
691
+ `Using live issue ${issue.identifier} (${issue.state}).`,
692
+ {
693
+ issue: issue.identifier,
694
+ state: issue.state,
695
+ source: input.parsedArgs.issue ? "explicit" : "auto"
696
+ }
697
+ )
698
+ );
699
+ try {
700
+ const renderedPrompt = renderIssueWorkflowPreview({
701
+ workflow: input.workflow.workflow,
702
+ issue,
703
+ attempt: null
704
+ });
705
+ checks.push(
706
+ passCheck(
707
+ "workflow_prompt_render",
708
+ "Workflow prompt render",
709
+ `WORKFLOW.md rendered successfully for ${issue.identifier}.`,
710
+ {
711
+ issue: issue.identifier,
712
+ promptLength: renderedPrompt.length,
713
+ workflowPath: input.workflow.workflowPath
714
+ }
715
+ )
716
+ );
717
+ } catch (error) {
718
+ checks.push(
719
+ failCheck(
720
+ "workflow_prompt_render",
721
+ "Workflow prompt render",
722
+ `WORKFLOW.md failed to render for ${issue.identifier}.`,
723
+ "Fix the prompt template variables or run 'gh-symphony workflow preview --issue ...' for a detailed preview error.",
724
+ {
725
+ issue: issue.identifier,
726
+ error: error instanceof Error ? error.message : String(error)
727
+ }
728
+ )
729
+ );
730
+ }
731
+ checks.push(
732
+ ...await buildHookChecks(
733
+ input.repoRoot,
734
+ input.workflow.workflow,
735
+ input.deps
736
+ )
737
+ );
738
+ return checks;
739
+ }
335
740
  async function runDoctorDiagnostics(options, args, dependencies = {}) {
336
741
  const deps = { ...DEFAULT_DEPENDENCIES, ...dependencies };
337
742
  const parsedArgs = parseDoctorArgs(args);
338
743
  if (parsedArgs.error) {
339
- throw new Error(
340
- `${parsedArgs.error}
341
- Usage: gh-symphony doctor [--project-id <project-id>] [--fix]`
342
- );
744
+ throw new Error(`${parsedArgs.error}
745
+ ${DOCTOR_USAGE}`);
343
746
  }
747
+ const repoRoot = process.cwd();
344
748
  const checks = [];
345
749
  let auth = null;
346
750
  let tokenError = null;
@@ -349,6 +753,8 @@ Usage: gh-symphony doctor [--project-id <project-id>] [--fix]`
349
753
  let envTokenError = null;
350
754
  let resolvedProjectId = null;
351
755
  let resolvedProjectConfig = null;
756
+ let resolvedGithubProjectDetail = null;
757
+ let resolvedGithubProjectBindingId = null;
352
758
  const envToken = deps.getEnvGitHubToken();
353
759
  const currentNodeVersion = deps.processVersion;
354
760
  const currentNodeMajor = parseMajorNodeVersion(currentNodeVersion);
@@ -529,7 +935,7 @@ Usage: gh-symphony doctor [--project-id <project-id>] [--fix]`
529
935
  "managed_project",
530
936
  "Managed project selection",
531
937
  resolvedProjectConfig.message,
532
- "Run 'gh-symphony project add' to register a project, or select one with 'gh-symphony project switch' / '--project-id'.",
938
+ "Run 'gh-symphony repo init' from the target repository.",
533
939
  {
534
940
  reason: resolvedProjectConfig.kind,
535
941
  ...resolvedProjectConfig.projectId ? { projectId: resolvedProjectConfig.projectId } : {}
@@ -550,33 +956,38 @@ Usage: gh-symphony doctor [--project-id <project-id>] [--fix]`
550
956
  } : void 0
551
957
  )
552
958
  );
553
- } else if (resolvedProjectConfig.kind === "resolved" && !resolvedProjectConfig.projectConfig.tracker.bindingId) {
959
+ } else if (resolvedProjectConfig.kind === "resolved" && !readGitHubProjectBinding(resolvedProjectConfig.projectConfig)) {
554
960
  checks.push(
555
961
  failCheck(
556
962
  "github_project_resolution",
557
963
  "GitHub project resolution",
558
964
  `Managed project "${resolvedProjectConfig.projectId}" is not bound to a GitHub Project.`,
559
- "Re-run 'gh-symphony project add' and select a valid GitHub Project binding, then run the doctor command again.",
965
+ "Run 'gh-symphony workflow init' to select a valid GitHub Project binding, then run 'gh-symphony repo init' again.",
560
966
  {
561
967
  reason: "missing_binding",
562
968
  projectId: resolvedProjectConfig.projectId
563
969
  }
564
970
  )
565
971
  );
566
- } else if (auth && resolvedProjectConfig.kind === "resolved" && resolvedProjectConfig.projectConfig.tracker.bindingId) {
972
+ } else if (auth && resolvedProjectConfig.kind === "resolved" && readGitHubProjectBinding(resolvedProjectConfig.projectConfig)) {
567
973
  try {
568
- const client = deps.createClient(auth.token);
569
- const detail = await deps.getProjectDetail(
570
- client,
571
- resolvedProjectConfig.projectConfig.tracker.bindingId
974
+ const bindingId = readGitHubProjectBinding(
975
+ resolvedProjectConfig.projectConfig
572
976
  );
977
+ if (!bindingId) {
978
+ throw new Error("Managed project is not bound to a GitHub Project.");
979
+ }
980
+ resolvedGithubProjectBindingId = bindingId;
981
+ const client = deps.createClient(auth.token);
982
+ const detail = await deps.getProjectDetail(client, bindingId);
983
+ resolvedGithubProjectDetail = detail;
573
984
  checks.push(
574
985
  passCheck(
575
986
  "github_project_resolution",
576
987
  "GitHub project resolution",
577
988
  `Resolved GitHub Project "${detail.title}".`,
578
989
  {
579
- bindingId: resolvedProjectConfig.projectConfig.tracker.bindingId,
990
+ bindingId: resolvedGithubProjectBindingId,
580
991
  url: detail.url
581
992
  }
582
993
  )
@@ -587,11 +998,11 @@ Usage: gh-symphony doctor [--project-id <project-id>] [--fix]`
587
998
  failCheck(
588
999
  "github_project_resolution",
589
1000
  "GitHub project resolution",
590
- `Failed to resolve configured project binding '${resolvedProjectConfig.projectConfig.tracker.bindingId}'.`,
591
- "Re-run 'gh-symphony project add' and select a valid GitHub Project, then run the doctor command again.",
1001
+ `Failed to resolve configured project binding '${resolvedGithubProjectBindingId}'.`,
1002
+ "Run 'gh-symphony workflow init' to select a valid GitHub Project, then run 'gh-symphony repo init' again.",
592
1003
  {
593
1004
  reason: "api_error",
594
- bindingId: resolvedProjectConfig.projectConfig.tracker.bindingId,
1005
+ bindingId: resolvedGithubProjectBindingId,
595
1006
  error: message
596
1007
  }
597
1008
  )
@@ -733,6 +1144,21 @@ Usage: gh-symphony doctor [--project-id <project-id>] [--fix]`
733
1144
  )
734
1145
  );
735
1146
  }
1147
+ if (parsedArgs.smoke) {
1148
+ checks.push(
1149
+ ...await buildDoctorSmokeChecks({
1150
+ auth,
1151
+ selection: resolvedProjectConfig,
1152
+ workflow,
1153
+ projectDetail: resolvedGithubProjectDetail,
1154
+ projectBindingId: resolvedGithubProjectBindingId,
1155
+ repoRoot,
1156
+ options,
1157
+ parsedArgs,
1158
+ deps
1159
+ })
1160
+ );
1161
+ }
736
1162
  return {
737
1163
  ok: checks.every((check) => check.status !== "fail"),
738
1164
  checkedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -963,9 +1389,9 @@ async function runDoctorFixes(report, deps, options) {
963
1389
  if (check.details?.reason === "multiple_projects_require_selection") {
964
1390
  steps.push(
965
1391
  runCliRemediation(
966
- "Managed project selection",
1392
+ "Repository runtime setup",
967
1393
  check.id,
968
- ["project", "switch"],
1394
+ ["repo", "init"],
969
1395
  deps,
970
1396
  options,
971
1397
  interactive,
@@ -976,9 +1402,9 @@ async function runDoctorFixes(report, deps, options) {
976
1402
  }
977
1403
  steps.push(
978
1404
  runCliRemediation(
979
- "Managed project setup",
1405
+ "Repository runtime setup",
980
1406
  check.id,
981
- ["project", "add"],
1407
+ ["repo", "init"],
982
1408
  deps,
983
1409
  options,
984
1410
  interactive,
@@ -1005,7 +1431,7 @@ async function runDoctorFixes(report, deps, options) {
1005
1431
  runCliRemediation(
1006
1432
  "GitHub project binding setup",
1007
1433
  check.id,
1008
- ["project", "add"],
1434
+ ["setup"],
1009
1435
  deps,
1010
1436
  options,
1011
1437
  interactive,
@@ -1021,7 +1447,7 @@ async function runDoctorFixes(report, deps, options) {
1021
1447
  check.title,
1022
1448
  "manual",
1023
1449
  check.remediation ?? "Resolve the GitHub Project binding manually.",
1024
- formatGhSymphonyCommand(["project", "add"], deps, options),
1450
+ formatGhSymphonyCommand(["setup"], deps, options),
1025
1451
  check.details
1026
1452
  )
1027
1453
  );
@@ -1051,7 +1477,7 @@ async function runDoctorFixes(report, deps, options) {
1051
1477
  runCliRemediation(
1052
1478
  title,
1053
1479
  check.id,
1054
- ["init"],
1480
+ ["workflow", "init"],
1055
1481
  deps,
1056
1482
  options,
1057
1483
  interactive,
@@ -1142,10 +1568,8 @@ async function runDoctorCommand(args, options, dependencies = {}) {
1142
1568
  const deps = { ...DEFAULT_DEPENDENCIES, ...dependencies };
1143
1569
  const parsedArgs = parseDoctorArgs(args);
1144
1570
  if (parsedArgs.error) {
1145
- throw new Error(
1146
- `${parsedArgs.error}
1147
- Usage: gh-symphony doctor [--project-id <project-id>] [--fix]`
1148
- );
1571
+ throw new Error(`${parsedArgs.error}
1572
+ ${DOCTOR_USAGE}`);
1149
1573
  }
1150
1574
  const initialReport = await runDoctorDiagnostics(options, args, deps);
1151
1575
  if (parsedArgs.fix) {