@jonit-dev/night-watch-cli 1.8.12-beta.9 → 1.8.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -290,13 +290,14 @@ var init_job_registry = __esm({
290
290
  {
291
291
  id: "audit",
292
292
  name: "Auditor",
293
- description: "Performs code audits and creates issues for findings",
293
+ description: "Performs consolidated architecture and code quality audits",
294
294
  cliCommand: "audit",
295
295
  logName: "audit",
296
296
  lockSuffix: "-audit.lock",
297
297
  queuePriority: 10,
298
298
  envPrefix: "NW_AUDIT",
299
299
  extraFields: [
300
+ { name: "createIssues", type: "boolean", defaultValue: false },
300
301
  {
301
302
  name: "targetColumn",
302
303
  type: "enum",
@@ -305,9 +306,10 @@ var init_job_registry = __esm({
305
306
  }
306
307
  ],
307
308
  defaultConfig: {
308
- enabled: true,
309
+ enabled: false,
309
310
  schedule: "50 3 * * 1",
310
311
  maxRuntime: 1800,
312
+ createIssues: false,
311
313
  targetColumn: "Draft"
312
314
  }
313
315
  },
@@ -390,7 +392,7 @@ function resolveProviderBucketKey(provider, providerEnv) {
390
392
  return `claude-proxy:${baseUrl}`;
391
393
  }
392
394
  }
393
- var DEFAULT_DEFAULT_BRANCH, DEFAULT_PRD_DIR, DEFAULT_SUMMARY_WINDOW_HOURS, DEFAULT_MAX_RUNTIME, DEFAULT_REVIEWER_MAX_RUNTIME, DEFAULT_CRON_SCHEDULE, DEFAULT_REVIEWER_SCHEDULE, DEFAULT_CRON_SCHEDULE_OFFSET, DEFAULT_MAX_RETRIES, DEFAULT_REVIEWER_MAX_RETRIES, DEFAULT_REVIEWER_RETRY_DELAY, DEFAULT_REVIEWER_MAX_PRS_PER_RUN, DEFAULT_FEEDBACK, DEFAULT_BRANCH_PREFIX, DEFAULT_BRANCH_PATTERNS, DEFAULT_MIN_REVIEW_SCORE, DEFAULT_MAX_LOG_SIZE, DEFAULT_PROVIDER, DEFAULT_EXECUTOR_ENABLED, DEFAULT_REVIEWER_ENABLED, DEFAULT_PROVIDER_ENV, DEFAULT_FALLBACK_ON_RATE_LIMIT, DEFAULT_CLAUDE_MODEL, DEFAULT_PRIMARY_FALLBACK_MODEL, DEFAULT_SECONDARY_FALLBACK_MODEL, VALID_CLAUDE_MODELS, CLAUDE_MODEL_IDS, DEFAULT_NOTIFICATIONS, DEFAULT_PRD_PRIORITY, DEFAULT_SLICER_SCHEDULE, DEFAULT_SLICER_MAX_RUNTIME, DEFAULT_ROADMAP_SCANNER, DEFAULT_TEMPLATES_DIR, DEFAULT_BOARD_PROVIDER, DEFAULT_LOCAL_BOARD_INFO, DEFAULT_AUTO_MERGE, DEFAULT_AUTO_MERGE_METHOD, VALID_MERGE_METHODS, DEFAULT_QA_ENABLED, DEFAULT_QA_SCHEDULE, DEFAULT_QA_MAX_RUNTIME, DEFAULT_QA_ARTIFACTS, DEFAULT_QA_SKIP_LABEL, DEFAULT_QA_AUTO_INSTALL_PLAYWRIGHT, DEFAULT_QA_VALIDATED_LABEL, DEFAULT_QA, QA_LOG_NAME, DEFAULT_AUDIT_ENABLED, DEFAULT_AUDIT_SCHEDULE, DEFAULT_AUDIT_MAX_RUNTIME, DEFAULT_AUDIT_TARGET_COLUMN, DEFAULT_AUDIT, DEFAULT_ANALYTICS_ENABLED, DEFAULT_ANALYTICS_SCHEDULE, DEFAULT_ANALYTICS_MAX_RUNTIME, DEFAULT_ANALYTICS_LOOKBACK_DAYS, DEFAULT_ANALYTICS_TARGET_COLUMN, DEFAULT_ANALYTICS_PROMPT, DEFAULT_ANALYTICS, DEFAULT_PR_RESOLVER_ENABLED, DEFAULT_PR_RESOLVER_SCHEDULE, DEFAULT_PR_RESOLVER_MAX_RUNTIME, DEFAULT_PR_RESOLVER_MAX_PRS_PER_RUN, DEFAULT_PR_RESOLVER_PER_PR_TIMEOUT, DEFAULT_PR_RESOLVER_AI_CONFLICT_RESOLUTION, DEFAULT_PR_RESOLVER_AI_REVIEW_RESOLUTION, DEFAULT_PR_RESOLVER_READY_LABEL, DEFAULT_PR_RESOLVER, DEFAULT_MERGER_ENABLED, DEFAULT_MERGER_SCHEDULE, DEFAULT_MERGER_MAX_RUNTIME, DEFAULT_MERGER_MERGE_METHOD, DEFAULT_MERGER_MIN_REVIEW_SCORE, DEFAULT_MERGER_REBASE_BEFORE_MERGE, DEFAULT_MERGER_MAX_PRS_PER_RUN, DEFAULT_MERGER, MERGER_LOG_NAME, AUDIT_LOG_NAME, PLANNER_LOG_NAME, ANALYTICS_LOG_NAME, PR_RESOLVER_LOG_NAME, VALID_PROVIDERS, VALID_JOB_TYPES, DEFAULT_JOB_PROVIDERS, DEFAULT_PROVIDER_SCHEDULE_OVERRIDES, DEFAULT_WEBHOOK_TRIGGER_SECRET_ENV, DEFAULT_WEBHOOK_TRIGGER_MAX_SKEW_SECONDS, DEFAULT_WEBHOOK_TRIGGERS, BUILT_IN_PRESETS, BUILT_IN_PRESET_IDS, PROVIDER_COMMANDS, CONFIG_FILE_NAME, LOCK_FILE_PREFIX, LOG_DIR, CLAIM_FILE_EXTENSION, EXECUTOR_LOG_NAME, REVIEWER_LOG_NAME, EXECUTOR_LOG_FILE, REVIEWER_LOG_FILE, LOG_FILE_NAMES, GLOBAL_CONFIG_DIR, REGISTRY_FILE_NAME, HISTORY_FILE_NAME, PRD_STATES_FILE_NAME, STATE_DB_FILE_NAME, GLOBAL_NOTIFICATIONS_FILE_NAME, MAX_HISTORY_RECORDS_PER_PRD, DEFAULT_QUEUE_ENABLED, DEFAULT_QUEUE_MODE, DEFAULT_QUEUE_MAX_CONCURRENCY, DEFAULT_QUEUE_MAX_WAIT_TIME, DEFAULT_QUEUE_PRIORITY, DEFAULT_QUEUE, DEFAULT_SCHEDULING_PRIORITY;
395
+ var DEFAULT_DEFAULT_BRANCH, DEFAULT_PRD_DIR, DEFAULT_SUMMARY_WINDOW_HOURS, DEFAULT_MAX_RUNTIME, DEFAULT_REVIEWER_MAX_RUNTIME, DEFAULT_CRON_SCHEDULE, DEFAULT_REVIEWER_SCHEDULE, DEFAULT_CRON_SCHEDULE_OFFSET, DEFAULT_MAX_RETRIES, DEFAULT_REVIEWER_MAX_RETRIES, DEFAULT_REVIEWER_RETRY_DELAY, DEFAULT_REVIEWER_MAX_PRS_PER_RUN, DEFAULT_FEEDBACK, DEFAULT_BRANCH_PREFIX, DEFAULT_BRANCH_PATTERNS, DEFAULT_MIN_REVIEW_SCORE, DEFAULT_MAX_LOG_SIZE, DEFAULT_PROVIDER, DEFAULT_EXECUTOR_ENABLED, DEFAULT_REVIEWER_ENABLED, DEFAULT_PROVIDER_ENV, DEFAULT_FALLBACK_ON_RATE_LIMIT, DEFAULT_CLAUDE_MODEL, DEFAULT_PRIMARY_FALLBACK_MODEL, DEFAULT_SECONDARY_FALLBACK_MODEL, VALID_CLAUDE_MODELS, CLAUDE_MODEL_IDS, DEFAULT_NOTIFICATIONS, DEFAULT_PRD_PRIORITY, DEFAULT_SLICER_SCHEDULE, DEFAULT_SLICER_MAX_RUNTIME, DEFAULT_ROADMAP_SCANNER, DEFAULT_TEMPLATES_DIR, DEFAULT_BOARD_PROVIDER, DEFAULT_LOCAL_BOARD_INFO, DEFAULT_AUTO_MERGE, DEFAULT_AUTO_MERGE_METHOD, VALID_MERGE_METHODS, DEFAULT_QA_ENABLED, DEFAULT_QA_SCHEDULE, DEFAULT_QA_MAX_RUNTIME, DEFAULT_QA_ARTIFACTS, DEFAULT_QA_SKIP_LABEL, DEFAULT_QA_AUTO_INSTALL_PLAYWRIGHT, DEFAULT_QA_VALIDATED_LABEL, DEFAULT_QA, QA_LOG_NAME, DEFAULT_AUDIT_ENABLED, DEFAULT_AUDIT_SCHEDULE, DEFAULT_AUDIT_MAX_RUNTIME, DEFAULT_AUDIT_CREATE_ISSUES, DEFAULT_AUDIT_TARGET_COLUMN, DEFAULT_AUDIT, DEFAULT_ANALYTICS_ENABLED, DEFAULT_ANALYTICS_SCHEDULE, DEFAULT_ANALYTICS_MAX_RUNTIME, DEFAULT_ANALYTICS_LOOKBACK_DAYS, DEFAULT_ANALYTICS_TARGET_COLUMN, DEFAULT_ANALYTICS_PROMPT, DEFAULT_ANALYTICS, DEFAULT_PR_RESOLVER_ENABLED, DEFAULT_PR_RESOLVER_SCHEDULE, DEFAULT_PR_RESOLVER_MAX_RUNTIME, DEFAULT_PR_RESOLVER_MAX_PRS_PER_RUN, DEFAULT_PR_RESOLVER_PER_PR_TIMEOUT, DEFAULT_PR_RESOLVER_AI_CONFLICT_RESOLUTION, DEFAULT_PR_RESOLVER_AI_REVIEW_RESOLUTION, DEFAULT_PR_RESOLVER_READY_LABEL, DEFAULT_PR_RESOLVER, DEFAULT_MERGER_ENABLED, DEFAULT_MERGER_SCHEDULE, DEFAULT_MERGER_MAX_RUNTIME, DEFAULT_MERGER_MERGE_METHOD, DEFAULT_MERGER_MIN_REVIEW_SCORE, DEFAULT_MERGER_REBASE_BEFORE_MERGE, DEFAULT_MERGER_MAX_PRS_PER_RUN, DEFAULT_MERGER, MERGER_LOG_NAME, AUDIT_LOG_NAME, PLANNER_LOG_NAME, ANALYTICS_LOG_NAME, PR_RESOLVER_LOG_NAME, VALID_PROVIDERS, VALID_JOB_TYPES, DEFAULT_JOB_PROVIDERS, DEFAULT_PROVIDER_SCHEDULE_OVERRIDES, DEFAULT_WEBHOOK_TRIGGER_SECRET_ENV, DEFAULT_WEBHOOK_TRIGGER_MAX_SKEW_SECONDS, DEFAULT_WEBHOOK_TRIGGERS, BUILT_IN_PRESETS, BUILT_IN_PRESET_IDS, PROVIDER_COMMANDS, CONFIG_FILE_NAME, LOCK_FILE_PREFIX, LOG_DIR, CLAIM_FILE_EXTENSION, EXECUTOR_LOG_NAME, REVIEWER_LOG_NAME, EXECUTOR_LOG_FILE, REVIEWER_LOG_FILE, LOG_FILE_NAMES, GLOBAL_CONFIG_DIR, REGISTRY_FILE_NAME, HISTORY_FILE_NAME, PRD_STATES_FILE_NAME, STATE_DB_FILE_NAME, GLOBAL_NOTIFICATIONS_FILE_NAME, MAX_HISTORY_RECORDS_PER_PRD, DEFAULT_QUEUE_ENABLED, DEFAULT_QUEUE_MODE, DEFAULT_QUEUE_MAX_CONCURRENCY, DEFAULT_QUEUE_MAX_WAIT_TIME, DEFAULT_QUEUE_PRIORITY, DEFAULT_QUEUE, DEFAULT_SCHEDULING_PRIORITY;
394
396
  var init_constants = __esm({
395
397
  "../core/dist/constants.js"() {
396
398
  "use strict";
@@ -471,14 +473,16 @@ var init_constants = __esm({
471
473
  validatedLabel: DEFAULT_QA_VALIDATED_LABEL
472
474
  };
473
475
  QA_LOG_NAME = "night-watch-qa";
474
- DEFAULT_AUDIT_ENABLED = true;
476
+ DEFAULT_AUDIT_ENABLED = false;
475
477
  DEFAULT_AUDIT_SCHEDULE = "50 3 * * 1";
476
478
  DEFAULT_AUDIT_MAX_RUNTIME = 1800;
479
+ DEFAULT_AUDIT_CREATE_ISSUES = false;
477
480
  DEFAULT_AUDIT_TARGET_COLUMN = "Draft";
478
481
  DEFAULT_AUDIT = {
479
482
  enabled: DEFAULT_AUDIT_ENABLED,
480
483
  schedule: DEFAULT_AUDIT_SCHEDULE,
481
484
  maxRuntime: DEFAULT_AUDIT_MAX_RUNTIME,
485
+ createIssues: DEFAULT_AUDIT_CREATE_ISSUES,
482
486
  targetColumn: DEFAULT_AUDIT_TARGET_COLUMN
483
487
  };
484
488
  DEFAULT_ANALYTICS_ENABLED = false;
@@ -816,6 +820,9 @@ function normalizeConfig(rawConfig) {
816
820
  if (typeof rawBoardProvider.projectNumber === "number") {
817
821
  bp.projectNumber = rawBoardProvider.projectNumber;
818
822
  }
823
+ if (typeof rawBoardProvider.projectTitle === "string") {
824
+ bp.projectTitle = rawBoardProvider.projectTitle;
825
+ }
819
826
  if (typeof rawBoardProvider.repo === "string") {
820
827
  bp.repo = rawBoardProvider.repo;
821
828
  }
@@ -867,7 +874,11 @@ function normalizeConfig(rawConfig) {
867
874
  continue;
868
875
  const rawJob = readObject2(rawConfig[jobId]);
869
876
  if (rawJob) {
870
- normalized[jobId] = normalizeJobConfig(rawJob, jobDef);
877
+ const normalizedJob = normalizeJobConfig(rawJob, jobDef);
878
+ if (jobId === "audit" && rawJob.createIssues === void 0 && rawJob.targetColumn !== void 0) {
879
+ normalizedJob.createIssues = true;
880
+ }
881
+ normalized[jobId] = normalizedJob;
871
882
  }
872
883
  }
873
884
  const prResolverDef = getJobDef("pr-resolver");
@@ -2926,12 +2937,18 @@ var init_github_projects_base = __esm({
2926
2937
  if (this.cachedOwner && this.cachedRepositoryId)
2927
2938
  return this.cachedOwner;
2928
2939
  const { owner, name } = await this.getRepoParts();
2929
- const data = await graphql(`query ResolveRepoOwner($owner: String!, $name: String!) {
2930
- repository(owner: $owner, name: $name) {
2931
- id
2932
- owner { __typename id login }
2940
+ const data = await graphql(`
2941
+ query ResolveRepoOwner($owner: String!, $name: String!) {
2942
+ repository(owner: $owner, name: $name) {
2943
+ id
2944
+ owner {
2945
+ __typename
2946
+ id
2947
+ login
2948
+ }
2949
+ }
2933
2950
  }
2934
- }`, { owner, name }, this.cwd);
2951
+ `, { owner, name }, this.cwd);
2935
2952
  if (!data.repository)
2936
2953
  throw new Error(`Repository ${owner}/${name} not found.`);
2937
2954
  const ownerNode = data.repository.owner;
@@ -2967,24 +2984,49 @@ var init_github_projects_base = __esm({
2967
2984
  }
2968
2985
  return null;
2969
2986
  }
2987
+ assertProjectUsable(projectNode) {
2988
+ if (projectNode.closed === true) {
2989
+ throw new Error(`Configured GitHub Project #${projectNode.number} is closed: "${projectNode.title}". Update boardProvider.projectNumber to an open Night Watch board or run \`night-watch board setup\`.`);
2990
+ }
2991
+ const expectedTitle = this.config.projectTitle?.trim();
2992
+ if (expectedTitle && projectNode.title !== expectedTitle) {
2993
+ throw new Error(`Configured GitHub Project #${projectNode.number} title mismatch. Expected "${expectedTitle}", got "${projectNode.title}". Update boardProvider.projectNumber/projectTitle or run \`night-watch board setup\`.`);
2994
+ }
2995
+ }
2970
2996
  /** Try user query first, fall back to org query. */
2971
2997
  async fetchProjectNode(login, projectNumber) {
2972
2998
  try {
2973
- const userData = await graphql(`query GetProject($login: String!, $number: Int!) {
2974
- user(login: $login) {
2975
- projectV2(number: $number) { id number title url }
2999
+ const userData = await graphql(`
3000
+ query GetProject($login: String!, $number: Int!) {
3001
+ user(login: $login) {
3002
+ projectV2(number: $number) {
3003
+ id
3004
+ number
3005
+ title
3006
+ url
3007
+ closed
3008
+ }
3009
+ }
2976
3010
  }
2977
- }`, { login, number: projectNumber }, this.cwd);
3011
+ `, { login, number: projectNumber }, this.cwd);
2978
3012
  if (userData.user?.projectV2)
2979
3013
  return userData.user.projectV2;
2980
3014
  } catch {
2981
3015
  }
2982
3016
  try {
2983
- const orgData = await graphql(`query GetOrgProject($login: String!, $number: Int!) {
2984
- organization(login: $login) {
2985
- projectV2(number: $number) { id number title url }
3017
+ const orgData = await graphql(`
3018
+ query GetOrgProject($login: String!, $number: Int!) {
3019
+ organization(login: $login) {
3020
+ projectV2(number: $number) {
3021
+ id
3022
+ number
3023
+ title
3024
+ url
3025
+ closed
3026
+ }
3027
+ }
2986
3028
  }
2987
- }`, { login, number: projectNumber }, this.cwd);
3029
+ `, { login, number: projectNumber }, this.cwd);
2988
3030
  if (orgData.organization?.projectV2)
2989
3031
  return orgData.organization.projectV2;
2990
3032
  } catch {
@@ -2993,13 +3035,21 @@ var init_github_projects_base = __esm({
2993
3035
  }
2994
3036
  async ensureProjectCache() {
2995
3037
  if (this.cachedProjectId !== null && this.cachedFieldId !== null && this.cachedOptionIds.size > 0) {
2996
- return { projectId: this.cachedProjectId, fieldId: this.cachedFieldId, optionIds: this.cachedOptionIds };
3038
+ return {
3039
+ projectId: this.cachedProjectId,
3040
+ fieldId: this.cachedFieldId,
3041
+ optionIds: this.cachedOptionIds
3042
+ };
2997
3043
  }
2998
3044
  if (this.cachedProjectId !== null) {
2999
3045
  const statusField2 = await this.fetchStatusField(this.cachedProjectId);
3000
3046
  this.cachedFieldId = statusField2.fieldId;
3001
3047
  this.cachedOptionIds = statusField2.optionIds;
3002
- return { projectId: this.cachedProjectId, fieldId: this.cachedFieldId, optionIds: this.cachedOptionIds };
3048
+ return {
3049
+ projectId: this.cachedProjectId,
3050
+ fieldId: this.cachedFieldId,
3051
+ optionIds: this.cachedOptionIds
3052
+ };
3003
3053
  }
3004
3054
  const projectNumber = this.config.projectNumber;
3005
3055
  if (!projectNumber) {
@@ -3009,28 +3059,38 @@ var init_github_projects_base = __esm({
3009
3059
  if (!projectNode) {
3010
3060
  throw new Error(`GitHub Project #${projectNumber} not found for repository owner "${await this.getRepoOwnerLogin()}".`);
3011
3061
  }
3062
+ this.assertProjectUsable(projectNode);
3012
3063
  this.cachedProjectId = projectNode.id;
3013
3064
  const statusField = await this.fetchStatusField(projectNode.id);
3014
3065
  this.cachedFieldId = statusField.fieldId;
3015
3066
  this.cachedOptionIds = statusField.optionIds;
3016
- return { projectId: this.cachedProjectId, fieldId: this.cachedFieldId, optionIds: this.cachedOptionIds };
3067
+ return {
3068
+ projectId: this.cachedProjectId,
3069
+ fieldId: this.cachedFieldId,
3070
+ optionIds: this.cachedOptionIds
3071
+ };
3017
3072
  }
3018
3073
  // -------------------------------------------------------------------------
3019
3074
  // Status field management
3020
3075
  // -------------------------------------------------------------------------
3021
3076
  async fetchStatusField(projectId) {
3022
- const fieldData = await graphql(`query GetStatusField($projectId: ID!) {
3023
- node(id: $projectId) {
3024
- ... on ProjectV2 {
3025
- field(name: "Status") {
3026
- ... on ProjectV2SingleSelectField {
3027
- id
3028
- options { id name }
3077
+ const fieldData = await graphql(`
3078
+ query GetStatusField($projectId: ID!) {
3079
+ node(id: $projectId) {
3080
+ ... on ProjectV2 {
3081
+ field(name: "Status") {
3082
+ ... on ProjectV2SingleSelectField {
3083
+ id
3084
+ options {
3085
+ id
3086
+ name
3087
+ }
3088
+ }
3029
3089
  }
3030
3090
  }
3031
3091
  }
3032
3092
  }
3033
- }`, { projectId }, this.cwd);
3093
+ `, { projectId }, this.cwd);
3034
3094
  const field = fieldData.node?.field;
3035
3095
  if (!field) {
3036
3096
  throw new Error(`Status field not found on project ${projectId}. Run \`night-watch board setup\` to create it.`);
@@ -3038,18 +3098,23 @@ var init_github_projects_base = __esm({
3038
3098
  return { fieldId: field.id, optionIds: new Map(field.options.map((o) => [o.name, o.id])) };
3039
3099
  }
3040
3100
  async ensureStatusColumns(projectId) {
3041
- const fieldData = await graphql(`query GetStatusField($projectId: ID!) {
3042
- node(id: $projectId) {
3043
- ... on ProjectV2 {
3044
- field(name: "Status") {
3045
- ... on ProjectV2SingleSelectField {
3046
- id
3047
- options { id name }
3101
+ const fieldData = await graphql(`
3102
+ query GetStatusField($projectId: ID!) {
3103
+ node(id: $projectId) {
3104
+ ... on ProjectV2 {
3105
+ field(name: "Status") {
3106
+ ... on ProjectV2SingleSelectField {
3107
+ id
3108
+ options {
3109
+ id
3110
+ name
3111
+ }
3112
+ }
3048
3113
  }
3049
3114
  }
3050
3115
  }
3051
3116
  }
3052
- }`, { projectId }, this.cwd);
3117
+ `, { projectId }, this.cwd);
3053
3118
  const field = fieldData.node?.field;
3054
3119
  if (!field)
3055
3120
  return;
@@ -3057,31 +3122,47 @@ var init_github_projects_base = __esm({
3057
3122
  const required = ["Draft", "Ready", "In Progress", "Review", "Done"];
3058
3123
  if (required.every((n) => existing.has(n)))
3059
3124
  return;
3060
- await graphql(`mutation UpdateField($fieldId: ID!) {
3061
- updateProjectV2Field(input: {
3062
- fieldId: $fieldId
3063
- singleSelectOptions: [
3064
- { name: "Draft", color: GRAY, description: "" }
3065
- { name: "Ready", color: BLUE, description: "" }
3066
- { name: "In Progress", color: YELLOW, description: "" }
3067
- { name: "Review", color: ORANGE, description: "" }
3068
- { name: "Done", color: GREEN, description: "" }
3069
- ]
3070
- }) {
3071
- projectV2Field {
3072
- ... on ProjectV2SingleSelectField { id options { id name } }
3125
+ await graphql(`
3126
+ mutation UpdateField($fieldId: ID!) {
3127
+ updateProjectV2Field(
3128
+ input: {
3129
+ fieldId: $fieldId
3130
+ singleSelectOptions: [
3131
+ { name: "Draft", color: GRAY, description: "" }
3132
+ { name: "Ready", color: BLUE, description: "" }
3133
+ { name: "In Progress", color: YELLOW, description: "" }
3134
+ { name: "Review", color: ORANGE, description: "" }
3135
+ { name: "Done", color: GREEN, description: "" }
3136
+ ]
3137
+ }
3138
+ ) {
3139
+ projectV2Field {
3140
+ ... on ProjectV2SingleSelectField {
3141
+ id
3142
+ options {
3143
+ id
3144
+ name
3145
+ }
3146
+ }
3147
+ }
3073
3148
  }
3074
3149
  }
3075
- }`, { fieldId: field.id }, this.cwd);
3150
+ `, { fieldId: field.id }, this.cwd);
3076
3151
  }
3077
3152
  async linkProjectToRepository(projectId) {
3078
3153
  const repositoryId = await this.getRepositoryNodeId();
3079
3154
  try {
3080
- await graphql(`mutation LinkProjectToRepository($projectId: ID!, $repositoryId: ID!) {
3081
- linkProjectV2ToRepository(input: { projectId: $projectId, repositoryId: $repositoryId }) {
3082
- repository { id }
3155
+ await graphql(`
3156
+ mutation LinkProjectToRepository($projectId: ID!, $repositoryId: ID!) {
3157
+ linkProjectV2ToRepository(
3158
+ input: { projectId: $projectId, repositoryId: $repositoryId }
3159
+ ) {
3160
+ repository {
3161
+ id
3162
+ }
3163
+ }
3083
3164
  }
3084
- }`, { projectId, repositoryId }, this.cwd);
3165
+ `, { projectId, repositoryId }, this.cwd);
3085
3166
  } catch (err) {
3086
3167
  const message = err instanceof Error ? err.message : String(err);
3087
3168
  if (message.toLowerCase().includes("already") && message.toLowerCase().includes("project"))
@@ -3162,16 +3243,22 @@ var init_github_projects_base = __esm({
3162
3243
  };
3163
3244
  }
3164
3245
  async setItemStatus(projectId, itemId, fieldId, optionId) {
3165
- await graphql(`mutation UpdateItemField($projectId: ID!, $itemId: ID!, $fieldId: ID!, $optionId: String!) {
3166
- updateProjectV2ItemFieldValue(input: {
3167
- projectId: $projectId
3168
- itemId: $itemId
3169
- fieldId: $fieldId
3170
- value: { singleSelectOptionId: $optionId }
3171
- }) {
3172
- projectV2Item { id }
3246
+ await graphql(`
3247
+ mutation UpdateItemField($projectId: ID!, $itemId: ID!, $fieldId: ID!, $optionId: String!) {
3248
+ updateProjectV2ItemFieldValue(
3249
+ input: {
3250
+ projectId: $projectId
3251
+ itemId: $itemId
3252
+ fieldId: $fieldId
3253
+ value: { singleSelectOptionId: $optionId }
3254
+ }
3255
+ ) {
3256
+ projectV2Item {
3257
+ id
3258
+ }
3259
+ }
3173
3260
  }
3174
- }`, { projectId, itemId, fieldId, optionId }, this.cwd);
3261
+ `, { projectId, itemId, fieldId, optionId }, this.cwd);
3175
3262
  }
3176
3263
  // -------------------------------------------------------------------------
3177
3264
  // Project listing
@@ -3179,19 +3266,39 @@ var init_github_projects_base = __esm({
3179
3266
  async findExistingProject(owner, title) {
3180
3267
  try {
3181
3268
  if (owner.type === "User") {
3182
- const data2 = await graphql(`query ListUserProjects($login: String!) {
3183
- user(login: $login) {
3184
- projectsV2(first: 50) { nodes { id number title url } }
3269
+ const data2 = await graphql(`
3270
+ query ListUserProjects($login: String!) {
3271
+ user(login: $login) {
3272
+ projectsV2(first: 50) {
3273
+ nodes {
3274
+ id
3275
+ number
3276
+ title
3277
+ url
3278
+ closed
3279
+ }
3280
+ }
3281
+ }
3185
3282
  }
3186
- }`, { login: owner.login }, this.cwd);
3187
- return data2.user?.projectsV2.nodes.find((p) => p.title === title) ?? null;
3283
+ `, { login: owner.login }, this.cwd);
3284
+ return data2.user?.projectsV2.nodes.find((p) => p.title === title && p.closed !== true) ?? null;
3188
3285
  }
3189
- const data = await graphql(`query ListOrgProjects($login: String!) {
3190
- organization(login: $login) {
3191
- projectsV2(first: 50) { nodes { id number title url } }
3286
+ const data = await graphql(`
3287
+ query ListOrgProjects($login: String!) {
3288
+ organization(login: $login) {
3289
+ projectsV2(first: 50) {
3290
+ nodes {
3291
+ id
3292
+ number
3293
+ title
3294
+ url
3295
+ closed
3296
+ }
3297
+ }
3298
+ }
3192
3299
  }
3193
- }`, { login: owner.login }, this.cwd);
3194
- return data.organization?.projectsV2.nodes.find((p) => p.title === title) ?? null;
3300
+ `, { login: owner.login }, this.cwd);
3301
+ return data.organization?.projectsV2.nodes.find((p) => p.title === title && p.closed !== true) ?? null;
3195
3302
  } catch {
3196
3303
  return null;
3197
3304
  }
@@ -3224,7 +3331,16 @@ var init_github_projects = __esm({
3224
3331
  async createRepositoryIssue(repo, input) {
3225
3332
  const requestedLabels = input.labels ?? [];
3226
3333
  const buildIssueArgs = (labels) => {
3227
- const args = ["issue", "create", "--title", input.title, "--body", input.body, "--repo", repo];
3334
+ const args = [
3335
+ "issue",
3336
+ "create",
3337
+ "--title",
3338
+ input.title,
3339
+ "--body",
3340
+ input.body,
3341
+ "--repo",
3342
+ repo
3343
+ ];
3228
3344
  if (labels.length > 0) {
3229
3345
  args.push("--label", labels.join(","));
3230
3346
  }
@@ -3330,6 +3446,11 @@ var init_github_projects = __esm({
3330
3446
  const node = await this.resolveProjectNode(projectNumber);
3331
3447
  if (!node)
3332
3448
  return null;
3449
+ if (node.closed === true)
3450
+ return null;
3451
+ if (this.config.projectTitle?.trim() && node.title !== this.config.projectTitle.trim()) {
3452
+ return null;
3453
+ }
3333
3454
  return { id: node.id, number: node.number, title: node.title, url: node.url };
3334
3455
  } catch {
3335
3456
  return null;
@@ -5977,12 +6098,19 @@ function formatDiscordPayload(ctx) {
5977
6098
  function formatTelegramPayload(ctx) {
5978
6099
  const emoji = getEventEmoji(ctx.event);
5979
6100
  const title = ctx.event === "run_succeeded" ? "PR Opened" : getEventTitle(ctx.event);
5980
- if (ctx.prUrl && ctx.prTitle) {
6101
+ if (ctx.prUrl || ctx.prTitle || ctx.prNumber !== void 0) {
5981
6102
  const lines = [];
6103
+ const prLabelParts = ["PR"];
6104
+ if (ctx.prNumber !== void 0) {
6105
+ prLabelParts.push(`#${ctx.prNumber}`);
6106
+ }
6107
+ const prLabel = ctx.prTitle ? `${prLabelParts.join(" ")}: ${ctx.prTitle}` : prLabelParts.join(" ");
5982
6108
  lines.push(`*${escapeMarkdownV2(emoji + " " + title)}*`);
5983
6109
  lines.push("");
5984
- lines.push(`${escapeMarkdownV2("\u{1F4CB}")} *${escapeMarkdownV2("PR #" + (ctx.prNumber ?? "") + ": " + ctx.prTitle)}*`);
5985
- lines.push(`${escapeMarkdownV2("\u{1F517}")} ${escapeMarkdownV2(ctx.prUrl)}`);
6110
+ lines.push(`${escapeMarkdownV2("\u{1F4CB}")} *${escapeMarkdownV2(prLabel)}*`);
6111
+ if (ctx.prUrl) {
6112
+ lines.push(`${escapeMarkdownV2("\u{1F517}")} ${escapeMarkdownV2(ctx.prUrl)}`);
6113
+ }
5986
6114
  if (ctx.prBody && ctx.prBody.trim().length > 0) {
5987
6115
  const summary = extractSummary(ctx.prBody);
5988
6116
  if (summary) {
@@ -6030,7 +6158,16 @@ function formatTelegramPayload(ctx) {
6030
6158
  }
6031
6159
  }
6032
6160
  lines.push("");
6033
- lines.push(escapeMarkdownV2(`\u2699\uFE0F Project: ${ctx.projectName} | Provider: ${ctx.provider}`));
6161
+ lines.push(escapeMarkdownV2("\u2699\uFE0F Meta"));
6162
+ lines.push(escapeMarkdownV2(`Project: ${ctx.projectName}`));
6163
+ lines.push(escapeMarkdownV2(`Provider: ${ctx.provider}`));
6164
+ lines.push(escapeMarkdownV2(`Exit code: ${ctx.exitCode}`));
6165
+ if (ctx.prdName) {
6166
+ lines.push(escapeMarkdownV2(`PRD: ${ctx.prdName}`));
6167
+ }
6168
+ if (ctx.branchName) {
6169
+ lines.push(escapeMarkdownV2(`Branch: ${ctx.branchName}`));
6170
+ }
6034
6171
  return {
6035
6172
  text: lines.join("\n"),
6036
6173
  parse_mode: "MarkdownV2"
@@ -8651,6 +8788,16 @@ function buildIssueBody(finding) {
8651
8788
  async function syncAuditFindingsToBoard(config, projectDir) {
8652
8789
  const findings = loadAuditFindings(projectDir);
8653
8790
  const targetColumn = config.audit.targetColumn;
8791
+ if (!config.audit.createIssues) {
8792
+ return {
8793
+ status: "skipped",
8794
+ findingsCount: findings.length,
8795
+ issuesCreated: 0,
8796
+ issuesFailed: 0,
8797
+ targetColumn: null,
8798
+ summary: "audit board issue creation is disabled"
8799
+ };
8800
+ }
8654
8801
  if (findings.length === 0) {
8655
8802
  return {
8656
8803
  status: "skipped",
@@ -9597,6 +9744,7 @@ __export(dist_exports, {
9597
9744
  DEFAULT_ANALYTICS_SCHEDULE: () => DEFAULT_ANALYTICS_SCHEDULE,
9598
9745
  DEFAULT_ANALYTICS_TARGET_COLUMN: () => DEFAULT_ANALYTICS_TARGET_COLUMN,
9599
9746
  DEFAULT_AUDIT: () => DEFAULT_AUDIT,
9747
+ DEFAULT_AUDIT_CREATE_ISSUES: () => DEFAULT_AUDIT_CREATE_ISSUES,
9600
9748
  DEFAULT_AUDIT_ENABLED: () => DEFAULT_AUDIT_ENABLED,
9601
9749
  DEFAULT_AUDIT_MAX_RUNTIME: () => DEFAULT_AUDIT_MAX_RUNTIME,
9602
9750
  DEFAULT_AUDIT_SCHEDULE: () => DEFAULT_AUDIT_SCHEDULE,
@@ -10643,7 +10791,8 @@ function initCommand(program2) {
10643
10791
  rawConfig.boardProvider = {
10644
10792
  enabled: true,
10645
10793
  provider: "github",
10646
- projectNumber: board.number
10794
+ projectNumber: board.number,
10795
+ projectTitle: board.title
10647
10796
  };
10648
10797
  fs23.writeFileSync(configPath, JSON.stringify(rawConfig, null, 2) + "\n");
10649
10798
  boardSetupStatus = `Created (#${board.number})`;
@@ -10888,6 +11037,27 @@ function resolveRunNotificationEvent(exitCode, scriptStatus) {
10888
11037
  }
10889
11038
  return null;
10890
11039
  }
11040
+ function extractPrUrlFromOutput(output) {
11041
+ if (!output) {
11042
+ return void 0;
11043
+ }
11044
+ const matches = Array.from(
11045
+ output.matchAll(/https:\/\/github\.com\/[^\s)]+\/pull\/\d+/g),
11046
+ (match) => match[0]
11047
+ );
11048
+ return matches.at(-1);
11049
+ }
11050
+ function extractResultValueFromOutput(output, key) {
11051
+ if (!output) {
11052
+ return void 0;
11053
+ }
11054
+ const escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
11055
+ const regex = new RegExp(`\\b${escapedKey}=([^\\s|]+)`, "g");
11056
+ const matches = Array.from(output.matchAll(regex), (match) => match[1]).filter(
11057
+ (value) => value !== void 0 && value.length > 0
11058
+ );
11059
+ return matches.at(-1);
11060
+ }
10891
11061
  function shouldAttemptCrossProjectFallback(options, scriptStatus) {
10892
11062
  if (options.crossProjectFallback !== true) {
10893
11063
  return false;
@@ -10903,6 +11073,71 @@ function shouldAttemptCrossProjectFallback(options, scriptStatus) {
10903
11073
  }
10904
11074
  return scriptStatus === "skip_no_eligible_prd";
10905
11075
  }
11076
+ function parsePrNumberFromUrl(prUrl) {
11077
+ const match = prUrl?.match(/\/pull\/(\d+)(?:\b|[/?#])/);
11078
+ if (!match?.[1]) {
11079
+ return void 0;
11080
+ }
11081
+ const parsed = parseInt(match[1], 10);
11082
+ return Number.isNaN(parsed) ? void 0 : parsed;
11083
+ }
11084
+ function getRunPrMetadata(scriptResult, rawOutput) {
11085
+ const prUrl = scriptResult?.data.pr_url ?? extractPrUrlFromOutput(rawOutput);
11086
+ const branchName = scriptResult?.data.branch ?? extractResultValueFromOutput(rawOutput, "branch");
11087
+ const prNumber = parsePrNumberFromUrl(prUrl) ?? (scriptResult?.data.pr_number ? parseInt(scriptResult.data.pr_number, 10) : void 0);
11088
+ return {
11089
+ prUrl,
11090
+ branchName,
11091
+ prNumber: prNumber !== void 0 && !Number.isNaN(prNumber) ? prNumber : void 0
11092
+ };
11093
+ }
11094
+ function fetchRunPrDetails(config, projectDir, metadata) {
11095
+ if (metadata.prNumber !== void 0) {
11096
+ const details = fetchPrDetailsByNumber(metadata.prNumber, projectDir);
11097
+ if (details) {
11098
+ return details;
11099
+ }
11100
+ }
11101
+ if (metadata.prUrl) {
11102
+ const details = fetchPrDetailsForBranch(metadata.prUrl, projectDir);
11103
+ if (details) {
11104
+ return details;
11105
+ }
11106
+ }
11107
+ if (metadata.branchName) {
11108
+ const details = fetchPrDetailsForBranch(metadata.branchName, projectDir);
11109
+ if (details) {
11110
+ return details;
11111
+ }
11112
+ }
11113
+ return fetchPrDetails(config.branchPrefix, projectDir);
11114
+ }
11115
+ function buildRunNotificationContext(config, projectDir, event, exitCode, scriptResult, prDetails, rawOutput) {
11116
+ const metadata = getRunPrMetadata(scriptResult, rawOutput);
11117
+ const timeoutDuration = event === "run_timeout" ? config.maxRuntime : void 0;
11118
+ const checkpointValue = scriptResult?.data.checkpoint;
11119
+ const checkpointStatus = checkpointValue === "created" || checkpointValue === "available" || checkpointValue === "none" ? checkpointValue : void 0;
11120
+ return {
11121
+ event,
11122
+ projectName: path23.basename(projectDir),
11123
+ exitCode,
11124
+ provider: config.provider,
11125
+ prdName: scriptResult?.data.prd ?? extractResultValueFromOutput(rawOutput, "prd"),
11126
+ branchName: metadata.branchName,
11127
+ duration: timeoutDuration,
11128
+ scriptStatus: scriptResult?.status,
11129
+ failureReason: scriptResult?.data.reason,
11130
+ failureDetail: scriptResult?.data.detail,
11131
+ checkpointStatus,
11132
+ prUrl: prDetails?.url || metadata.prUrl,
11133
+ prTitle: prDetails?.title,
11134
+ prBody: prDetails?.body,
11135
+ prNumber: prDetails?.number ?? metadata.prNumber,
11136
+ filesChanged: prDetails?.changedFiles,
11137
+ additions: prDetails?.additions,
11138
+ deletions: prDetails?.deletions
11139
+ };
11140
+ }
10906
11141
  function getCrossProjectFallbackCandidates(currentProjectDir) {
10907
11142
  const current = path23.resolve(currentProjectDir);
10908
11143
  const { valid, invalid } = validateRegistry();
@@ -10911,7 +11146,7 @@ function getCrossProjectFallbackCandidates(currentProjectDir) {
10911
11146
  }
10912
11147
  return valid.filter((entry) => path23.resolve(entry.path) !== current);
10913
11148
  }
10914
- async function sendRunCompletionNotifications(config, projectDir, options, exitCode, scriptResult) {
11149
+ async function sendRunCompletionNotifications(config, projectDir, options, exitCode, scriptResult, rawOutput) {
10915
11150
  if (isRateLimitFallbackTriggered(scriptResult?.data)) {
10916
11151
  const nonTelegramWebhooks = (config.notifications?.webhooks ?? []).filter(
10917
11152
  (wh) => wh.type !== "telegram"
@@ -10935,42 +11170,18 @@ async function sendRunCompletionNotifications(config, projectDir, options, exitC
10935
11170
  const event = resolveRunNotificationEvent(exitCode, scriptResult?.status);
10936
11171
  let prDetails = null;
10937
11172
  if (event === "run_succeeded") {
10938
- const prUrl = scriptResult?.data.pr_url;
10939
- const branch = scriptResult?.data.branch;
10940
- if (prUrl) {
10941
- prDetails = fetchPrDetailsForBranch(prUrl, projectDir);
10942
- }
10943
- if (!prDetails && branch) {
10944
- prDetails = fetchPrDetailsForBranch(branch, projectDir);
10945
- }
10946
- if (!prDetails) {
10947
- prDetails = fetchPrDetails(config.branchPrefix, projectDir);
10948
- }
11173
+ prDetails = fetchRunPrDetails(config, projectDir, getRunPrMetadata(scriptResult, rawOutput));
10949
11174
  }
10950
11175
  if (event) {
10951
- const timeoutDuration = event === "run_timeout" ? config.maxRuntime : void 0;
10952
- const checkpointValue = scriptResult?.data.checkpoint;
10953
- const checkpointStatus = checkpointValue === "created" || checkpointValue === "available" || checkpointValue === "none" ? checkpointValue : void 0;
10954
- const _ctx = {
11176
+ const _ctx = buildRunNotificationContext(
11177
+ config,
11178
+ projectDir,
10955
11179
  event,
10956
- projectName: path23.basename(projectDir),
10957
11180
  exitCode,
10958
- provider: config.provider,
10959
- prdName: scriptResult?.data.prd,
10960
- branchName: scriptResult?.data.branch,
10961
- duration: timeoutDuration,
10962
- scriptStatus: scriptResult?.status,
10963
- failureReason: scriptResult?.data.reason,
10964
- failureDetail: scriptResult?.data.detail,
10965
- checkpointStatus,
10966
- prUrl: prDetails?.url,
10967
- prTitle: prDetails?.title,
10968
- prBody: prDetails?.body,
10969
- prNumber: prDetails?.number,
10970
- filesChanged: prDetails?.changedFiles,
10971
- additions: prDetails?.additions,
10972
- deletions: prDetails?.deletions
10973
- };
11181
+ scriptResult,
11182
+ prDetails,
11183
+ rawOutput
11184
+ );
10974
11185
  await sendNotifications(config, _ctx);
10975
11186
  } else if (!options.dryRun) {
10976
11187
  info("Skipping completion notification (no actionable run result)");
@@ -11021,7 +11232,9 @@ ${stderr}`);
11021
11232
  candidate.path,
11022
11233
  options,
11023
11234
  exitCode,
11024
- scriptResult
11235
+ scriptResult,
11236
+ `${stdout}
11237
+ ${stderr}`
11025
11238
  );
11026
11239
  }
11027
11240
  if (exitCode !== 0) {
@@ -11363,7 +11576,15 @@ ${stderr}`);
11363
11576
  });
11364
11577
  } catch {
11365
11578
  }
11366
- await sendRunCompletionNotifications(config, projectDir, options, exitCode, scriptResult);
11579
+ await sendRunCompletionNotifications(
11580
+ config,
11581
+ projectDir,
11582
+ options,
11583
+ exitCode,
11584
+ scriptResult,
11585
+ `${stdout}
11586
+ ${stderr}`
11587
+ );
11367
11588
  }
11368
11589
  if (shouldAttemptCrossProjectFallback(options, scriptResult?.status)) {
11369
11590
  const executedFallback = await runCrossProjectFallback(projectDir, options);
@@ -11973,6 +12194,7 @@ import * as path26 from "path";
11973
12194
  function buildEnvVars4(config, options) {
11974
12195
  const env = buildBaseEnvVars(config, "audit", options.dryRun);
11975
12196
  env.NW_AUDIT_MAX_RUNTIME = String(config.audit.maxRuntime);
12197
+ env.NW_AUDIT_CREATE_ISSUES = config.audit.createIssues ? "1" : "0";
11976
12198
  env.NW_CLAUDE_MODEL_ID = CLAUDE_MODEL_IDS[config.primaryFallbackModel ?? config.claudeModel ?? "sonnet"];
11977
12199
  const telegramWebhooks = getTelegramStatusWebhooks(config);
11978
12200
  if (telegramWebhooks.length > 0) {
@@ -12012,7 +12234,10 @@ function auditCommand(program2) {
12012
12234
  configTable.push(["Provider", auditProvider]);
12013
12235
  configTable.push(["Provider CLI", PROVIDER_COMMANDS[auditProvider]]);
12014
12236
  configTable.push(["Max Runtime", `${config.audit.maxRuntime}s`]);
12015
- configTable.push(["Target Column", config.audit.targetColumn]);
12237
+ configTable.push(["Create Board Issues", config.audit.createIssues ? "yes" : "no"]);
12238
+ if (config.audit.createIssues) {
12239
+ configTable.push(["Target Column", config.audit.targetColumn]);
12240
+ }
12016
12241
  configTable.push(["Report File", path26.join(projectDir, "logs", "audit-report.md")]);
12017
12242
  console.log(configTable.toString());
12018
12243
  header("Provider Invocation");
@@ -16626,6 +16851,9 @@ function validateConfigChanges(changes, currentConfig) {
16626
16851
  if (audit.maxRuntime !== void 0 && (typeof audit.maxRuntime !== "number" || audit.maxRuntime < 60)) {
16627
16852
  return "audit.maxRuntime must be a number >= 60";
16628
16853
  }
16854
+ if (audit.createIssues !== void 0 && typeof audit.createIssues !== "boolean") {
16855
+ return "audit.createIssues must be a boolean";
16856
+ }
16629
16857
  if (audit.targetColumn !== void 0 && !BOARD_COLUMNS.includes(audit.targetColumn)) {
16630
16858
  return `audit.targetColumn must be one of: ${BOARD_COLUMNS.join(", ")}`;
16631
16859
  }
@@ -16695,6 +16923,9 @@ function validateConfigChanges(changes, currentConfig) {
16695
16923
  if (changes.boardProvider.projectNumber !== void 0 && (typeof changes.boardProvider.projectNumber !== "number" || !Number.isInteger(changes.boardProvider.projectNumber) || changes.boardProvider.projectNumber <= 0)) {
16696
16924
  return "boardProvider.projectNumber must be an integer > 0";
16697
16925
  }
16926
+ if (changes.boardProvider.projectTitle !== void 0 && (typeof changes.boardProvider.projectTitle !== "string" || changes.boardProvider.projectTitle.trim().length === 0)) {
16927
+ return "boardProvider.projectTitle must be a non-empty string";
16928
+ }
16698
16929
  if (changes.boardProvider.repo !== void 0 && (typeof changes.boardProvider.repo !== "string" || changes.boardProvider.repo.trim().length === 0)) {
16699
16930
  return "boardProvider.repo must be a non-empty string";
16700
16931
  }
@@ -19245,7 +19476,8 @@ async function ensureBoardConfigured(config, cwd, provider, options) {
19245
19476
  ...config.boardProvider,
19246
19477
  enabled: config.boardProvider?.enabled ?? true,
19247
19478
  provider: config.boardProvider?.provider ?? "github",
19248
- projectNumber: boardInfo.number
19479
+ projectNumber: boardInfo.number,
19480
+ projectTitle: boardInfo.title
19249
19481
  }
19250
19482
  });
19251
19483
  if (!result.success) {
@@ -19342,7 +19574,8 @@ function boardCommand(program2) {
19342
19574
  const result = saveConfig(cwd, {
19343
19575
  boardProvider: {
19344
19576
  ...config.boardProvider,
19345
- projectNumber: boardInfo.number
19577
+ projectNumber: boardInfo.number,
19578
+ projectTitle: boardInfo.title
19346
19579
  }
19347
19580
  });
19348
19581
  if (!result.success) {