@jonit-dev/night-watch-cli 1.8.12-beta.9 → 1.8.14-beta.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 (36) hide show
  1. package/dist/cli.js +376 -201
  2. package/dist/commands/audit.d.ts.map +1 -1
  3. package/dist/commands/audit.js +5 -1
  4. package/dist/commands/audit.js.map +1 -1
  5. package/dist/commands/board.d.ts.map +1 -1
  6. package/dist/commands/board.js +2 -0
  7. package/dist/commands/board.js.map +1 -1
  8. package/dist/commands/dashboard/tab-config.d.ts.map +1 -1
  9. package/dist/commands/dashboard/tab-config.js +1 -11
  10. package/dist/commands/dashboard/tab-config.js.map +1 -1
  11. package/dist/commands/doctor.d.ts +1 -6
  12. package/dist/commands/doctor.d.ts.map +1 -1
  13. package/dist/commands/doctor.js +2 -59
  14. package/dist/commands/doctor.js.map +1 -1
  15. package/dist/commands/init.d.ts.map +1 -1
  16. package/dist/commands/init.js +1 -0
  17. package/dist/commands/init.js.map +1 -1
  18. package/dist/commands/notify.d.ts.map +1 -1
  19. package/dist/commands/notify.js +3 -13
  20. package/dist/commands/notify.js.map +1 -1
  21. package/dist/commands/run.d.ts +14 -1
  22. package/dist/commands/run.d.ts.map +1 -1
  23. package/dist/commands/run.js +97 -40
  24. package/dist/commands/run.js.map +1 -1
  25. package/dist/scripts/night-watch-audit-cron.sh +11 -1
  26. package/dist/scripts/night-watch-cron.sh +4 -2
  27. package/dist/scripts/night-watch-merger-cron.sh +177 -32
  28. package/dist/scripts/night-watch-pr-reviewer-cron.sh +107 -2
  29. package/dist/templates/audit.md +64 -30
  30. package/dist/templates/night-watch-audit.md +71 -30
  31. package/dist/templates/night-watch-pr-reviewer.md +7 -6
  32. package/dist/templates/pr-reviewer.md +7 -6
  33. package/dist/web/assets/index-CL3Q-KB4.css +1 -0
  34. package/dist/web/assets/index-FDOCfjkP.js +442 -0
  35. package/dist/web/index.html +2 -2
  36. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -11,9 +11,27 @@ var __export = (target, all) => {
11
11
  };
12
12
 
13
13
  // ../core/dist/types.js
14
+ var NOTIFICATION_EVENTS;
14
15
  var init_types = __esm({
15
16
  "../core/dist/types.js"() {
16
17
  "use strict";
18
+ NOTIFICATION_EVENTS = [
19
+ "run_started",
20
+ "run_succeeded",
21
+ "run_failed",
22
+ "run_timeout",
23
+ "run_no_work",
24
+ "review_completed",
25
+ "review_ready_for_human",
26
+ "pr_auto_merged",
27
+ "rate_limit_fallback",
28
+ "qa_completed",
29
+ "pr_resolver_completed",
30
+ "pr_resolver_conflict_resolved",
31
+ "pr_resolver_failed",
32
+ "merge_completed",
33
+ "merge_failed"
34
+ ];
17
35
  }
18
36
  });
19
37
 
@@ -290,13 +308,14 @@ var init_job_registry = __esm({
290
308
  {
291
309
  id: "audit",
292
310
  name: "Auditor",
293
- description: "Performs code audits and creates issues for findings",
311
+ description: "Performs consolidated architecture and code quality audits",
294
312
  cliCommand: "audit",
295
313
  logName: "audit",
296
314
  lockSuffix: "-audit.lock",
297
315
  queuePriority: 10,
298
316
  envPrefix: "NW_AUDIT",
299
317
  extraFields: [
318
+ { name: "createIssues", type: "boolean", defaultValue: false },
300
319
  {
301
320
  name: "targetColumn",
302
321
  type: "enum",
@@ -305,9 +324,10 @@ var init_job_registry = __esm({
305
324
  }
306
325
  ],
307
326
  defaultConfig: {
308
- enabled: true,
327
+ enabled: false,
309
328
  schedule: "50 3 * * 1",
310
329
  maxRuntime: 1800,
330
+ createIssues: false,
311
331
  targetColumn: "Draft"
312
332
  }
313
333
  },
@@ -390,7 +410,7 @@ function resolveProviderBucketKey(provider, providerEnv) {
390
410
  return `claude-proxy:${baseUrl}`;
391
411
  }
392
412
  }
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;
413
+ 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
414
  var init_constants = __esm({
395
415
  "../core/dist/constants.js"() {
396
416
  "use strict";
@@ -471,14 +491,16 @@ var init_constants = __esm({
471
491
  validatedLabel: DEFAULT_QA_VALIDATED_LABEL
472
492
  };
473
493
  QA_LOG_NAME = "night-watch-qa";
474
- DEFAULT_AUDIT_ENABLED = true;
494
+ DEFAULT_AUDIT_ENABLED = false;
475
495
  DEFAULT_AUDIT_SCHEDULE = "50 3 * * 1";
476
496
  DEFAULT_AUDIT_MAX_RUNTIME = 1800;
497
+ DEFAULT_AUDIT_CREATE_ISSUES = false;
477
498
  DEFAULT_AUDIT_TARGET_COLUMN = "Draft";
478
499
  DEFAULT_AUDIT = {
479
500
  enabled: DEFAULT_AUDIT_ENABLED,
480
501
  schedule: DEFAULT_AUDIT_SCHEDULE,
481
502
  maxRuntime: DEFAULT_AUDIT_MAX_RUNTIME,
503
+ createIssues: DEFAULT_AUDIT_CREATE_ISSUES,
482
504
  targetColumn: DEFAULT_AUDIT_TARGET_COLUMN
483
505
  };
484
506
  DEFAULT_ANALYTICS_ENABLED = false;
@@ -816,6 +838,9 @@ function normalizeConfig(rawConfig) {
816
838
  if (typeof rawBoardProvider.projectNumber === "number") {
817
839
  bp.projectNumber = rawBoardProvider.projectNumber;
818
840
  }
841
+ if (typeof rawBoardProvider.projectTitle === "string") {
842
+ bp.projectTitle = rawBoardProvider.projectTitle;
843
+ }
819
844
  if (typeof rawBoardProvider.repo === "string") {
820
845
  bp.repo = rawBoardProvider.repo;
821
846
  }
@@ -867,7 +892,11 @@ function normalizeConfig(rawConfig) {
867
892
  continue;
868
893
  const rawJob = readObject2(rawConfig[jobId]);
869
894
  if (rawJob) {
870
- normalized[jobId] = normalizeJobConfig(rawJob, jobDef);
895
+ const normalizedJob = normalizeJobConfig(rawJob, jobDef);
896
+ if (jobId === "audit" && rawJob.createIssues === void 0 && rawJob.targetColumn !== void 0) {
897
+ normalizedJob.createIssues = true;
898
+ }
899
+ normalized[jobId] = normalizedJob;
871
900
  }
872
901
  }
873
902
  const prResolverDef = getJobDef("pr-resolver");
@@ -2926,12 +2955,18 @@ var init_github_projects_base = __esm({
2926
2955
  if (this.cachedOwner && this.cachedRepositoryId)
2927
2956
  return this.cachedOwner;
2928
2957
  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 }
2958
+ const data = await graphql(`
2959
+ query ResolveRepoOwner($owner: String!, $name: String!) {
2960
+ repository(owner: $owner, name: $name) {
2961
+ id
2962
+ owner {
2963
+ __typename
2964
+ id
2965
+ login
2966
+ }
2967
+ }
2933
2968
  }
2934
- }`, { owner, name }, this.cwd);
2969
+ `, { owner, name }, this.cwd);
2935
2970
  if (!data.repository)
2936
2971
  throw new Error(`Repository ${owner}/${name} not found.`);
2937
2972
  const ownerNode = data.repository.owner;
@@ -2967,24 +3002,49 @@ var init_github_projects_base = __esm({
2967
3002
  }
2968
3003
  return null;
2969
3004
  }
3005
+ assertProjectUsable(projectNode) {
3006
+ if (projectNode.closed === true) {
3007
+ 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\`.`);
3008
+ }
3009
+ const expectedTitle = this.config.projectTitle?.trim();
3010
+ if (expectedTitle && projectNode.title !== expectedTitle) {
3011
+ 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\`.`);
3012
+ }
3013
+ }
2970
3014
  /** Try user query first, fall back to org query. */
2971
3015
  async fetchProjectNode(login, projectNumber) {
2972
3016
  try {
2973
- const userData = await graphql(`query GetProject($login: String!, $number: Int!) {
2974
- user(login: $login) {
2975
- projectV2(number: $number) { id number title url }
3017
+ const userData = await graphql(`
3018
+ query GetProject($login: String!, $number: Int!) {
3019
+ user(login: $login) {
3020
+ projectV2(number: $number) {
3021
+ id
3022
+ number
3023
+ title
3024
+ url
3025
+ closed
3026
+ }
3027
+ }
2976
3028
  }
2977
- }`, { login, number: projectNumber }, this.cwd);
3029
+ `, { login, number: projectNumber }, this.cwd);
2978
3030
  if (userData.user?.projectV2)
2979
3031
  return userData.user.projectV2;
2980
3032
  } catch {
2981
3033
  }
2982
3034
  try {
2983
- const orgData = await graphql(`query GetOrgProject($login: String!, $number: Int!) {
2984
- organization(login: $login) {
2985
- projectV2(number: $number) { id number title url }
3035
+ const orgData = await graphql(`
3036
+ query GetOrgProject($login: String!, $number: Int!) {
3037
+ organization(login: $login) {
3038
+ projectV2(number: $number) {
3039
+ id
3040
+ number
3041
+ title
3042
+ url
3043
+ closed
3044
+ }
3045
+ }
2986
3046
  }
2987
- }`, { login, number: projectNumber }, this.cwd);
3047
+ `, { login, number: projectNumber }, this.cwd);
2988
3048
  if (orgData.organization?.projectV2)
2989
3049
  return orgData.organization.projectV2;
2990
3050
  } catch {
@@ -2993,13 +3053,21 @@ var init_github_projects_base = __esm({
2993
3053
  }
2994
3054
  async ensureProjectCache() {
2995
3055
  if (this.cachedProjectId !== null && this.cachedFieldId !== null && this.cachedOptionIds.size > 0) {
2996
- return { projectId: this.cachedProjectId, fieldId: this.cachedFieldId, optionIds: this.cachedOptionIds };
3056
+ return {
3057
+ projectId: this.cachedProjectId,
3058
+ fieldId: this.cachedFieldId,
3059
+ optionIds: this.cachedOptionIds
3060
+ };
2997
3061
  }
2998
3062
  if (this.cachedProjectId !== null) {
2999
3063
  const statusField2 = await this.fetchStatusField(this.cachedProjectId);
3000
3064
  this.cachedFieldId = statusField2.fieldId;
3001
3065
  this.cachedOptionIds = statusField2.optionIds;
3002
- return { projectId: this.cachedProjectId, fieldId: this.cachedFieldId, optionIds: this.cachedOptionIds };
3066
+ return {
3067
+ projectId: this.cachedProjectId,
3068
+ fieldId: this.cachedFieldId,
3069
+ optionIds: this.cachedOptionIds
3070
+ };
3003
3071
  }
3004
3072
  const projectNumber = this.config.projectNumber;
3005
3073
  if (!projectNumber) {
@@ -3009,28 +3077,38 @@ var init_github_projects_base = __esm({
3009
3077
  if (!projectNode) {
3010
3078
  throw new Error(`GitHub Project #${projectNumber} not found for repository owner "${await this.getRepoOwnerLogin()}".`);
3011
3079
  }
3080
+ this.assertProjectUsable(projectNode);
3012
3081
  this.cachedProjectId = projectNode.id;
3013
3082
  const statusField = await this.fetchStatusField(projectNode.id);
3014
3083
  this.cachedFieldId = statusField.fieldId;
3015
3084
  this.cachedOptionIds = statusField.optionIds;
3016
- return { projectId: this.cachedProjectId, fieldId: this.cachedFieldId, optionIds: this.cachedOptionIds };
3085
+ return {
3086
+ projectId: this.cachedProjectId,
3087
+ fieldId: this.cachedFieldId,
3088
+ optionIds: this.cachedOptionIds
3089
+ };
3017
3090
  }
3018
3091
  // -------------------------------------------------------------------------
3019
3092
  // Status field management
3020
3093
  // -------------------------------------------------------------------------
3021
3094
  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 }
3095
+ const fieldData = await graphql(`
3096
+ query GetStatusField($projectId: ID!) {
3097
+ node(id: $projectId) {
3098
+ ... on ProjectV2 {
3099
+ field(name: "Status") {
3100
+ ... on ProjectV2SingleSelectField {
3101
+ id
3102
+ options {
3103
+ id
3104
+ name
3105
+ }
3106
+ }
3029
3107
  }
3030
3108
  }
3031
3109
  }
3032
3110
  }
3033
- }`, { projectId }, this.cwd);
3111
+ `, { projectId }, this.cwd);
3034
3112
  const field = fieldData.node?.field;
3035
3113
  if (!field) {
3036
3114
  throw new Error(`Status field not found on project ${projectId}. Run \`night-watch board setup\` to create it.`);
@@ -3038,18 +3116,23 @@ var init_github_projects_base = __esm({
3038
3116
  return { fieldId: field.id, optionIds: new Map(field.options.map((o) => [o.name, o.id])) };
3039
3117
  }
3040
3118
  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 }
3119
+ const fieldData = await graphql(`
3120
+ query GetStatusField($projectId: ID!) {
3121
+ node(id: $projectId) {
3122
+ ... on ProjectV2 {
3123
+ field(name: "Status") {
3124
+ ... on ProjectV2SingleSelectField {
3125
+ id
3126
+ options {
3127
+ id
3128
+ name
3129
+ }
3130
+ }
3048
3131
  }
3049
3132
  }
3050
3133
  }
3051
3134
  }
3052
- }`, { projectId }, this.cwd);
3135
+ `, { projectId }, this.cwd);
3053
3136
  const field = fieldData.node?.field;
3054
3137
  if (!field)
3055
3138
  return;
@@ -3057,31 +3140,47 @@ var init_github_projects_base = __esm({
3057
3140
  const required = ["Draft", "Ready", "In Progress", "Review", "Done"];
3058
3141
  if (required.every((n) => existing.has(n)))
3059
3142
  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 } }
3143
+ await graphql(`
3144
+ mutation UpdateField($fieldId: ID!) {
3145
+ updateProjectV2Field(
3146
+ input: {
3147
+ fieldId: $fieldId
3148
+ singleSelectOptions: [
3149
+ { name: "Draft", color: GRAY, description: "" }
3150
+ { name: "Ready", color: BLUE, description: "" }
3151
+ { name: "In Progress", color: YELLOW, description: "" }
3152
+ { name: "Review", color: ORANGE, description: "" }
3153
+ { name: "Done", color: GREEN, description: "" }
3154
+ ]
3155
+ }
3156
+ ) {
3157
+ projectV2Field {
3158
+ ... on ProjectV2SingleSelectField {
3159
+ id
3160
+ options {
3161
+ id
3162
+ name
3163
+ }
3164
+ }
3165
+ }
3073
3166
  }
3074
3167
  }
3075
- }`, { fieldId: field.id }, this.cwd);
3168
+ `, { fieldId: field.id }, this.cwd);
3076
3169
  }
3077
3170
  async linkProjectToRepository(projectId) {
3078
3171
  const repositoryId = await this.getRepositoryNodeId();
3079
3172
  try {
3080
- await graphql(`mutation LinkProjectToRepository($projectId: ID!, $repositoryId: ID!) {
3081
- linkProjectV2ToRepository(input: { projectId: $projectId, repositoryId: $repositoryId }) {
3082
- repository { id }
3173
+ await graphql(`
3174
+ mutation LinkProjectToRepository($projectId: ID!, $repositoryId: ID!) {
3175
+ linkProjectV2ToRepository(
3176
+ input: { projectId: $projectId, repositoryId: $repositoryId }
3177
+ ) {
3178
+ repository {
3179
+ id
3180
+ }
3181
+ }
3083
3182
  }
3084
- }`, { projectId, repositoryId }, this.cwd);
3183
+ `, { projectId, repositoryId }, this.cwd);
3085
3184
  } catch (err) {
3086
3185
  const message = err instanceof Error ? err.message : String(err);
3087
3186
  if (message.toLowerCase().includes("already") && message.toLowerCase().includes("project"))
@@ -3162,16 +3261,22 @@ var init_github_projects_base = __esm({
3162
3261
  };
3163
3262
  }
3164
3263
  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 }
3264
+ await graphql(`
3265
+ mutation UpdateItemField($projectId: ID!, $itemId: ID!, $fieldId: ID!, $optionId: String!) {
3266
+ updateProjectV2ItemFieldValue(
3267
+ input: {
3268
+ projectId: $projectId
3269
+ itemId: $itemId
3270
+ fieldId: $fieldId
3271
+ value: { singleSelectOptionId: $optionId }
3272
+ }
3273
+ ) {
3274
+ projectV2Item {
3275
+ id
3276
+ }
3277
+ }
3173
3278
  }
3174
- }`, { projectId, itemId, fieldId, optionId }, this.cwd);
3279
+ `, { projectId, itemId, fieldId, optionId }, this.cwd);
3175
3280
  }
3176
3281
  // -------------------------------------------------------------------------
3177
3282
  // Project listing
@@ -3179,19 +3284,39 @@ var init_github_projects_base = __esm({
3179
3284
  async findExistingProject(owner, title) {
3180
3285
  try {
3181
3286
  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 } }
3287
+ const data2 = await graphql(`
3288
+ query ListUserProjects($login: String!) {
3289
+ user(login: $login) {
3290
+ projectsV2(first: 50) {
3291
+ nodes {
3292
+ id
3293
+ number
3294
+ title
3295
+ url
3296
+ closed
3297
+ }
3298
+ }
3299
+ }
3185
3300
  }
3186
- }`, { login: owner.login }, this.cwd);
3187
- return data2.user?.projectsV2.nodes.find((p) => p.title === title) ?? null;
3301
+ `, { login: owner.login }, this.cwd);
3302
+ return data2.user?.projectsV2.nodes.find((p) => p.title === title && p.closed !== true) ?? null;
3188
3303
  }
3189
- const data = await graphql(`query ListOrgProjects($login: String!) {
3190
- organization(login: $login) {
3191
- projectsV2(first: 50) { nodes { id number title url } }
3304
+ const data = await graphql(`
3305
+ query ListOrgProjects($login: String!) {
3306
+ organization(login: $login) {
3307
+ projectsV2(first: 50) {
3308
+ nodes {
3309
+ id
3310
+ number
3311
+ title
3312
+ url
3313
+ closed
3314
+ }
3315
+ }
3316
+ }
3192
3317
  }
3193
- }`, { login: owner.login }, this.cwd);
3194
- return data.organization?.projectsV2.nodes.find((p) => p.title === title) ?? null;
3318
+ `, { login: owner.login }, this.cwd);
3319
+ return data.organization?.projectsV2.nodes.find((p) => p.title === title && p.closed !== true) ?? null;
3195
3320
  } catch {
3196
3321
  return null;
3197
3322
  }
@@ -3224,7 +3349,16 @@ var init_github_projects = __esm({
3224
3349
  async createRepositoryIssue(repo, input) {
3225
3350
  const requestedLabels = input.labels ?? [];
3226
3351
  const buildIssueArgs = (labels) => {
3227
- const args = ["issue", "create", "--title", input.title, "--body", input.body, "--repo", repo];
3352
+ const args = [
3353
+ "issue",
3354
+ "create",
3355
+ "--title",
3356
+ input.title,
3357
+ "--body",
3358
+ input.body,
3359
+ "--repo",
3360
+ repo
3361
+ ];
3228
3362
  if (labels.length > 0) {
3229
3363
  args.push("--label", labels.join(","));
3230
3364
  }
@@ -3330,6 +3464,11 @@ var init_github_projects = __esm({
3330
3464
  const node = await this.resolveProjectNode(projectNumber);
3331
3465
  if (!node)
3332
3466
  return null;
3467
+ if (node.closed === true)
3468
+ return null;
3469
+ if (this.config.projectTitle?.trim() && node.title !== this.config.projectTitle.trim()) {
3470
+ return null;
3471
+ }
3333
3472
  return { id: node.id, number: node.number, title: node.title, url: node.url };
3334
3473
  } catch {
3335
3474
  return null;
@@ -5977,12 +6116,19 @@ function formatDiscordPayload(ctx) {
5977
6116
  function formatTelegramPayload(ctx) {
5978
6117
  const emoji = getEventEmoji(ctx.event);
5979
6118
  const title = ctx.event === "run_succeeded" ? "PR Opened" : getEventTitle(ctx.event);
5980
- if (ctx.prUrl && ctx.prTitle) {
6119
+ if (ctx.prUrl || ctx.prTitle || ctx.prNumber !== void 0) {
5981
6120
  const lines = [];
6121
+ const prLabelParts = ["PR"];
6122
+ if (ctx.prNumber !== void 0) {
6123
+ prLabelParts.push(`#${ctx.prNumber}`);
6124
+ }
6125
+ const prLabel = ctx.prTitle ? `${prLabelParts.join(" ")}: ${ctx.prTitle}` : prLabelParts.join(" ");
5982
6126
  lines.push(`*${escapeMarkdownV2(emoji + " " + title)}*`);
5983
6127
  lines.push("");
5984
- lines.push(`${escapeMarkdownV2("\u{1F4CB}")} *${escapeMarkdownV2("PR #" + (ctx.prNumber ?? "") + ": " + ctx.prTitle)}*`);
5985
- lines.push(`${escapeMarkdownV2("\u{1F517}")} ${escapeMarkdownV2(ctx.prUrl)}`);
6128
+ lines.push(`${escapeMarkdownV2("\u{1F4CB}")} *${escapeMarkdownV2(prLabel)}*`);
6129
+ if (ctx.prUrl) {
6130
+ lines.push(`${escapeMarkdownV2("\u{1F517}")} ${escapeMarkdownV2(ctx.prUrl)}`);
6131
+ }
5986
6132
  if (ctx.prBody && ctx.prBody.trim().length > 0) {
5987
6133
  const summary = extractSummary(ctx.prBody);
5988
6134
  if (summary) {
@@ -6030,7 +6176,16 @@ function formatTelegramPayload(ctx) {
6030
6176
  }
6031
6177
  }
6032
6178
  lines.push("");
6033
- lines.push(escapeMarkdownV2(`\u2699\uFE0F Project: ${ctx.projectName} | Provider: ${ctx.provider}`));
6179
+ lines.push(escapeMarkdownV2("\u2699\uFE0F Meta"));
6180
+ lines.push(escapeMarkdownV2(`Project: ${ctx.projectName}`));
6181
+ lines.push(escapeMarkdownV2(`Provider: ${ctx.provider}`));
6182
+ lines.push(escapeMarkdownV2(`Exit code: ${ctx.exitCode}`));
6183
+ if (ctx.prdName) {
6184
+ lines.push(escapeMarkdownV2(`PRD: ${ctx.prdName}`));
6185
+ }
6186
+ if (ctx.branchName) {
6187
+ lines.push(escapeMarkdownV2(`Branch: ${ctx.branchName}`));
6188
+ }
6034
6189
  return {
6035
6190
  text: lines.join("\n"),
6036
6191
  parse_mode: "MarkdownV2"
@@ -7469,18 +7624,8 @@ function validateWebhook(webhook) {
7469
7624
  if (!webhook.events || webhook.events.length === 0) {
7470
7625
  issues.push("No events configured");
7471
7626
  } else {
7472
- const validEvents = [
7473
- "run_started",
7474
- "run_succeeded",
7475
- "run_failed",
7476
- "run_timeout",
7477
- "review_completed",
7478
- "pr_auto_merged",
7479
- "rate_limit_fallback",
7480
- "qa_completed"
7481
- ];
7482
7627
  for (const event of webhook.events) {
7483
- if (!validEvents.includes(event)) {
7628
+ if (!NOTIFICATION_EVENTS.includes(event)) {
7484
7629
  issues.push(`Invalid event: ${event}`);
7485
7630
  }
7486
7631
  }
@@ -7516,6 +7661,7 @@ function validateWebhook(webhook) {
7516
7661
  var init_webhook_validator = __esm({
7517
7662
  "../core/dist/utils/webhook-validator.js"() {
7518
7663
  "use strict";
7664
+ init_types();
7519
7665
  }
7520
7666
  });
7521
7667
 
@@ -8651,6 +8797,16 @@ function buildIssueBody(finding) {
8651
8797
  async function syncAuditFindingsToBoard(config, projectDir) {
8652
8798
  const findings = loadAuditFindings(projectDir);
8653
8799
  const targetColumn = config.audit.targetColumn;
8800
+ if (!config.audit.createIssues) {
8801
+ return {
8802
+ status: "skipped",
8803
+ findingsCount: findings.length,
8804
+ issuesCreated: 0,
8805
+ issuesFailed: 0,
8806
+ targetColumn: null,
8807
+ summary: "audit board issue creation is disabled"
8808
+ };
8809
+ }
8654
8810
  if (findings.length === 0) {
8655
8811
  return {
8656
8812
  status: "skipped",
@@ -9597,6 +9753,7 @@ __export(dist_exports, {
9597
9753
  DEFAULT_ANALYTICS_SCHEDULE: () => DEFAULT_ANALYTICS_SCHEDULE,
9598
9754
  DEFAULT_ANALYTICS_TARGET_COLUMN: () => DEFAULT_ANALYTICS_TARGET_COLUMN,
9599
9755
  DEFAULT_AUDIT: () => DEFAULT_AUDIT,
9756
+ DEFAULT_AUDIT_CREATE_ISSUES: () => DEFAULT_AUDIT_CREATE_ISSUES,
9600
9757
  DEFAULT_AUDIT_ENABLED: () => DEFAULT_AUDIT_ENABLED,
9601
9758
  DEFAULT_AUDIT_MAX_RUNTIME: () => DEFAULT_AUDIT_MAX_RUNTIME,
9602
9759
  DEFAULT_AUDIT_SCHEDULE: () => DEFAULT_AUDIT_SCHEDULE,
@@ -9693,6 +9850,7 @@ __export(dist_exports, {
9693
9850
  MAX_HISTORY_RECORDS_PER_PRD: () => MAX_HISTORY_RECORDS_PER_PRD,
9694
9851
  MERGER_LOG_NAME: () => MERGER_LOG_NAME,
9695
9852
  NIGHT_WATCH_LABELS: () => NIGHT_WATCH_LABELS,
9853
+ NOTIFICATION_EVENTS: () => NOTIFICATION_EVENTS,
9696
9854
  PLANNER_LOG_NAME: () => PLANNER_LOG_NAME,
9697
9855
  PRD_STATES_FILE_NAME: () => PRD_STATES_FILE_NAME,
9698
9856
  PRD_TEMPLATE: () => PRD_TEMPLATE,
@@ -10643,7 +10801,8 @@ function initCommand(program2) {
10643
10801
  rawConfig.boardProvider = {
10644
10802
  enabled: true,
10645
10803
  provider: "github",
10646
- projectNumber: board.number
10804
+ projectNumber: board.number,
10805
+ projectTitle: board.title
10647
10806
  };
10648
10807
  fs23.writeFileSync(configPath, JSON.stringify(rawConfig, null, 2) + "\n");
10649
10808
  boardSetupStatus = `Created (#${board.number})`;
@@ -10888,6 +11047,27 @@ function resolveRunNotificationEvent(exitCode, scriptStatus) {
10888
11047
  }
10889
11048
  return null;
10890
11049
  }
11050
+ function extractPrUrlFromOutput(output) {
11051
+ if (!output) {
11052
+ return void 0;
11053
+ }
11054
+ const matches = Array.from(
11055
+ output.matchAll(/https:\/\/github\.com\/[^\s)]+\/pull\/\d+/g),
11056
+ (match) => match[0]
11057
+ );
11058
+ return matches.at(-1);
11059
+ }
11060
+ function extractResultValueFromOutput(output, key) {
11061
+ if (!output) {
11062
+ return void 0;
11063
+ }
11064
+ const escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
11065
+ const regex = new RegExp(`\\b${escapedKey}=([^\\s|]+)`, "g");
11066
+ const matches = Array.from(output.matchAll(regex), (match) => match[1]).filter(
11067
+ (value) => value !== void 0 && value.length > 0
11068
+ );
11069
+ return matches.at(-1);
11070
+ }
10891
11071
  function shouldAttemptCrossProjectFallback(options, scriptStatus) {
10892
11072
  if (options.crossProjectFallback !== true) {
10893
11073
  return false;
@@ -10903,6 +11083,71 @@ function shouldAttemptCrossProjectFallback(options, scriptStatus) {
10903
11083
  }
10904
11084
  return scriptStatus === "skip_no_eligible_prd";
10905
11085
  }
11086
+ function parsePrNumberFromUrl(prUrl) {
11087
+ const match = prUrl?.match(/\/pull\/(\d+)(?:\b|[/?#])/);
11088
+ if (!match?.[1]) {
11089
+ return void 0;
11090
+ }
11091
+ const parsed = parseInt(match[1], 10);
11092
+ return Number.isNaN(parsed) ? void 0 : parsed;
11093
+ }
11094
+ function getRunPrMetadata(scriptResult, rawOutput) {
11095
+ const prUrl = scriptResult?.data.pr_url ?? extractPrUrlFromOutput(rawOutput);
11096
+ const branchName = scriptResult?.data.branch ?? extractResultValueFromOutput(rawOutput, "branch");
11097
+ const prNumber = parsePrNumberFromUrl(prUrl) ?? (scriptResult?.data.pr_number ? parseInt(scriptResult.data.pr_number, 10) : void 0);
11098
+ return {
11099
+ prUrl,
11100
+ branchName,
11101
+ prNumber: prNumber !== void 0 && !Number.isNaN(prNumber) ? prNumber : void 0
11102
+ };
11103
+ }
11104
+ function fetchRunPrDetails(config, projectDir, metadata) {
11105
+ if (metadata.prNumber !== void 0) {
11106
+ const details = fetchPrDetailsByNumber(metadata.prNumber, projectDir);
11107
+ if (details) {
11108
+ return details;
11109
+ }
11110
+ }
11111
+ if (metadata.prUrl) {
11112
+ const details = fetchPrDetailsForBranch(metadata.prUrl, projectDir);
11113
+ if (details) {
11114
+ return details;
11115
+ }
11116
+ }
11117
+ if (metadata.branchName) {
11118
+ const details = fetchPrDetailsForBranch(metadata.branchName, projectDir);
11119
+ if (details) {
11120
+ return details;
11121
+ }
11122
+ }
11123
+ return fetchPrDetails(config.branchPrefix, projectDir);
11124
+ }
11125
+ function buildRunNotificationContext(config, projectDir, event, exitCode, scriptResult, prDetails, rawOutput) {
11126
+ const metadata = getRunPrMetadata(scriptResult, rawOutput);
11127
+ const timeoutDuration = event === "run_timeout" ? config.maxRuntime : void 0;
11128
+ const checkpointValue = scriptResult?.data.checkpoint;
11129
+ const checkpointStatus = checkpointValue === "created" || checkpointValue === "available" || checkpointValue === "none" ? checkpointValue : void 0;
11130
+ return {
11131
+ event,
11132
+ projectName: path23.basename(projectDir),
11133
+ exitCode,
11134
+ provider: config.provider,
11135
+ prdName: scriptResult?.data.prd ?? extractResultValueFromOutput(rawOutput, "prd"),
11136
+ branchName: metadata.branchName,
11137
+ duration: timeoutDuration,
11138
+ scriptStatus: scriptResult?.status,
11139
+ failureReason: scriptResult?.data.reason,
11140
+ failureDetail: scriptResult?.data.detail,
11141
+ checkpointStatus,
11142
+ prUrl: prDetails?.url || metadata.prUrl,
11143
+ prTitle: prDetails?.title,
11144
+ prBody: prDetails?.body,
11145
+ prNumber: prDetails?.number ?? metadata.prNumber,
11146
+ filesChanged: prDetails?.changedFiles,
11147
+ additions: prDetails?.additions,
11148
+ deletions: prDetails?.deletions
11149
+ };
11150
+ }
10906
11151
  function getCrossProjectFallbackCandidates(currentProjectDir) {
10907
11152
  const current = path23.resolve(currentProjectDir);
10908
11153
  const { valid, invalid } = validateRegistry();
@@ -10911,7 +11156,7 @@ function getCrossProjectFallbackCandidates(currentProjectDir) {
10911
11156
  }
10912
11157
  return valid.filter((entry) => path23.resolve(entry.path) !== current);
10913
11158
  }
10914
- async function sendRunCompletionNotifications(config, projectDir, options, exitCode, scriptResult) {
11159
+ async function sendRunCompletionNotifications(config, projectDir, options, exitCode, scriptResult, rawOutput) {
10915
11160
  if (isRateLimitFallbackTriggered(scriptResult?.data)) {
10916
11161
  const nonTelegramWebhooks = (config.notifications?.webhooks ?? []).filter(
10917
11162
  (wh) => wh.type !== "telegram"
@@ -10935,42 +11180,18 @@ async function sendRunCompletionNotifications(config, projectDir, options, exitC
10935
11180
  const event = resolveRunNotificationEvent(exitCode, scriptResult?.status);
10936
11181
  let prDetails = null;
10937
11182
  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
- }
11183
+ prDetails = fetchRunPrDetails(config, projectDir, getRunPrMetadata(scriptResult, rawOutput));
10949
11184
  }
10950
11185
  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 = {
11186
+ const _ctx = buildRunNotificationContext(
11187
+ config,
11188
+ projectDir,
10955
11189
  event,
10956
- projectName: path23.basename(projectDir),
10957
11190
  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
- };
11191
+ scriptResult,
11192
+ prDetails,
11193
+ rawOutput
11194
+ );
10974
11195
  await sendNotifications(config, _ctx);
10975
11196
  } else if (!options.dryRun) {
10976
11197
  info("Skipping completion notification (no actionable run result)");
@@ -11021,7 +11242,9 @@ ${stderr}`);
11021
11242
  candidate.path,
11022
11243
  options,
11023
11244
  exitCode,
11024
- scriptResult
11245
+ scriptResult,
11246
+ `${stdout}
11247
+ ${stderr}`
11025
11248
  );
11026
11249
  }
11027
11250
  if (exitCode !== 0) {
@@ -11363,7 +11586,15 @@ ${stderr}`);
11363
11586
  });
11364
11587
  } catch {
11365
11588
  }
11366
- await sendRunCompletionNotifications(config, projectDir, options, exitCode, scriptResult);
11589
+ await sendRunCompletionNotifications(
11590
+ config,
11591
+ projectDir,
11592
+ options,
11593
+ exitCode,
11594
+ scriptResult,
11595
+ `${stdout}
11596
+ ${stderr}`
11597
+ );
11367
11598
  }
11368
11599
  if (shouldAttemptCrossProjectFallback(options, scriptResult?.status)) {
11369
11600
  const executedFallback = await runCrossProjectFallback(projectDir, options);
@@ -11973,6 +12204,7 @@ import * as path26 from "path";
11973
12204
  function buildEnvVars4(config, options) {
11974
12205
  const env = buildBaseEnvVars(config, "audit", options.dryRun);
11975
12206
  env.NW_AUDIT_MAX_RUNTIME = String(config.audit.maxRuntime);
12207
+ env.NW_AUDIT_CREATE_ISSUES = config.audit.createIssues ? "1" : "0";
11976
12208
  env.NW_CLAUDE_MODEL_ID = CLAUDE_MODEL_IDS[config.primaryFallbackModel ?? config.claudeModel ?? "sonnet"];
11977
12209
  const telegramWebhooks = getTelegramStatusWebhooks(config);
11978
12210
  if (telegramWebhooks.length > 0) {
@@ -12012,7 +12244,10 @@ function auditCommand(program2) {
12012
12244
  configTable.push(["Provider", auditProvider]);
12013
12245
  configTable.push(["Provider CLI", PROVIDER_COMMANDS[auditProvider]]);
12014
12246
  configTable.push(["Max Runtime", `${config.audit.maxRuntime}s`]);
12015
- configTable.push(["Target Column", config.audit.targetColumn]);
12247
+ configTable.push(["Create Board Issues", config.audit.createIssues ? "yes" : "no"]);
12248
+ if (config.audit.createIssues) {
12249
+ configTable.push(["Target Column", config.audit.targetColumn]);
12250
+ }
12016
12251
  configTable.push(["Report File", path26.join(projectDir, "logs", "audit-report.md")]);
12017
12252
  console.log(configTable.toString());
12018
12253
  header("Provider Invocation");
@@ -13781,16 +14016,6 @@ function promptTextbox(screen, label2, initialValue, cb) {
13781
14016
  });
13782
14017
  }
13783
14018
  var WEBHOOK_TYPES = ["slack", "discord", "telegram"];
13784
- var NOTIFICATION_EVENTS = [
13785
- "run_started",
13786
- "run_succeeded",
13787
- "run_failed",
13788
- "run_timeout",
13789
- "review_completed",
13790
- "pr_auto_merged",
13791
- "rate_limit_fallback",
13792
- "qa_completed"
13793
- ];
13794
14019
  var CONFIG_FIELDS = [
13795
14020
  { key: "provider", label: "Provider", type: "enum", options: [...BUILT_IN_PRESET_IDS] },
13796
14021
  { key: "reviewerEnabled", label: "Reviewer Enabled", type: "boolean" },
@@ -15510,55 +15735,7 @@ function dashboardCommand(program2) {
15510
15735
 
15511
15736
  // src/commands/doctor.ts
15512
15737
  init_dist();
15513
- function validateWebhook2(webhook) {
15514
- const issues = [];
15515
- if (!webhook.events || webhook.events.length === 0) {
15516
- issues.push("No events configured");
15517
- } else {
15518
- const validEvents = [
15519
- "run_started",
15520
- "run_succeeded",
15521
- "run_failed",
15522
- "run_timeout",
15523
- "review_completed",
15524
- "pr_auto_merged",
15525
- "rate_limit_fallback",
15526
- "qa_completed"
15527
- ];
15528
- for (const event of webhook.events) {
15529
- if (!validEvents.includes(event)) {
15530
- issues.push(`Invalid event: ${event}`);
15531
- }
15532
- }
15533
- }
15534
- switch (webhook.type) {
15535
- case "slack":
15536
- if (!webhook.url) {
15537
- issues.push("Missing URL");
15538
- } else if (!webhook.url.startsWith("https://hooks.slack.com/")) {
15539
- issues.push("URL should start with https://hooks.slack.com/");
15540
- }
15541
- break;
15542
- case "discord":
15543
- if (!webhook.url) {
15544
- issues.push("Missing URL");
15545
- } else if (!webhook.url.startsWith("https://discord.com/api/webhooks/")) {
15546
- issues.push("URL should start with https://discord.com/api/webhooks/");
15547
- }
15548
- break;
15549
- case "telegram":
15550
- if (!webhook.botToken) {
15551
- issues.push("Missing botToken");
15552
- }
15553
- if (!webhook.chatId) {
15554
- issues.push("Missing chatId");
15555
- }
15556
- break;
15557
- default:
15558
- issues.push(`Unknown webhook type: ${webhook.type}`);
15559
- }
15560
- return issues;
15561
- }
15738
+ init_dist();
15562
15739
  function runCheck(checkNum, total, checkName, checkFn, options) {
15563
15740
  step(checkNum, total, `Checking ${checkName}...`);
15564
15741
  const result = checkFn();
@@ -15649,7 +15826,7 @@ function doctorCommand(program2) {
15649
15826
  } else {
15650
15827
  let webhookErrors = 0;
15651
15828
  for (const webhook of config.notifications.webhooks) {
15652
- const issues = validateWebhook2(webhook);
15829
+ const issues = validateWebhook(webhook);
15653
15830
  if (issues.length === 0) {
15654
15831
  success(`${webhook.type} webhook: OK`);
15655
15832
  } else {
@@ -16626,6 +16803,9 @@ function validateConfigChanges(changes, currentConfig) {
16626
16803
  if (audit.maxRuntime !== void 0 && (typeof audit.maxRuntime !== "number" || audit.maxRuntime < 60)) {
16627
16804
  return "audit.maxRuntime must be a number >= 60";
16628
16805
  }
16806
+ if (audit.createIssues !== void 0 && typeof audit.createIssues !== "boolean") {
16807
+ return "audit.createIssues must be a boolean";
16808
+ }
16629
16809
  if (audit.targetColumn !== void 0 && !BOARD_COLUMNS.includes(audit.targetColumn)) {
16630
16810
  return `audit.targetColumn must be one of: ${BOARD_COLUMNS.join(", ")}`;
16631
16811
  }
@@ -16695,6 +16875,9 @@ function validateConfigChanges(changes, currentConfig) {
16695
16875
  if (changes.boardProvider.projectNumber !== void 0 && (typeof changes.boardProvider.projectNumber !== "number" || !Number.isInteger(changes.boardProvider.projectNumber) || changes.boardProvider.projectNumber <= 0)) {
16696
16876
  return "boardProvider.projectNumber must be an integer > 0";
16697
16877
  }
16878
+ if (changes.boardProvider.projectTitle !== void 0 && (typeof changes.boardProvider.projectTitle !== "string" || changes.boardProvider.projectTitle.trim().length === 0)) {
16879
+ return "boardProvider.projectTitle must be a non-empty string";
16880
+ }
16698
16881
  if (changes.boardProvider.repo !== void 0 && (typeof changes.boardProvider.repo !== "string" || changes.boardProvider.repo.trim().length === 0)) {
16699
16882
  return "boardProvider.repo must be a non-empty string";
16700
16883
  }
@@ -19245,7 +19428,8 @@ async function ensureBoardConfigured(config, cwd, provider, options) {
19245
19428
  ...config.boardProvider,
19246
19429
  enabled: config.boardProvider?.enabled ?? true,
19247
19430
  provider: config.boardProvider?.provider ?? "github",
19248
- projectNumber: boardInfo.number
19431
+ projectNumber: boardInfo.number,
19432
+ projectTitle: boardInfo.title
19249
19433
  }
19250
19434
  });
19251
19435
  if (!result.success) {
@@ -19342,7 +19526,8 @@ function boardCommand(program2) {
19342
19526
  const result = saveConfig(cwd, {
19343
19527
  boardProvider: {
19344
19528
  ...config.boardProvider,
19345
- projectNumber: boardInfo.number
19529
+ projectNumber: boardInfo.number,
19530
+ projectTitle: boardInfo.title
19346
19531
  }
19347
19532
  });
19348
19533
  if (!result.success) {
@@ -20146,22 +20331,12 @@ function queueCommand(program2) {
20146
20331
  // src/commands/notify.ts
20147
20332
  init_dist();
20148
20333
  import { basename as basename13 } from "path";
20149
- var VALID_EVENTS = [
20150
- "run_started",
20151
- "run_succeeded",
20152
- "run_failed",
20153
- "run_timeout",
20154
- "review_completed",
20155
- "rate_limit_fallback",
20156
- "pr_auto_merged",
20157
- "qa_completed"
20158
- ];
20159
20334
  function notifyCommand(program2) {
20160
20335
  program2.command("notify <event> <projectDir>").description("Send a notification event via configured webhooks").option("--prd <name>", "PRD name").option("--branch <name>", "Branch name").option("--provider <name>", "Provider name").option("--exit-code <n>", "Exit code", "0").option("--pr-number <n>", "PR number").action(
20161
20336
  async (event, projectDir, options) => {
20162
- if (!VALID_EVENTS.includes(event)) {
20337
+ if (!NOTIFICATION_EVENTS.includes(event)) {
20163
20338
  process.stderr.write(
20164
- `Invalid event: ${event}. Must be one of: ${VALID_EVENTS.join(", ")}
20339
+ `Invalid event: ${event}. Must be one of: ${NOTIFICATION_EVENTS.join(", ")}
20165
20340
  `
20166
20341
  );
20167
20342
  process.exit(2);