@jonit-dev/night-watch-cli 1.8.2 → 1.8.3

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
@@ -31,7 +31,7 @@ function resolveProviderBucketKey(provider, providerEnv) {
31
31
  return `claude-proxy:${baseUrl}`;
32
32
  }
33
33
  }
34
- var DEFAULT_DEFAULT_BRANCH, DEFAULT_PRD_DIR, 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_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, QA_LOG_NAME, DEFAULT_AUDIT_ENABLED, DEFAULT_AUDIT_SCHEDULE, DEFAULT_AUDIT_MAX_RUNTIME, 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, AUDIT_LOG_NAME, PLANNER_LOG_NAME, ANALYTICS_LOG_NAME, VALID_PROVIDERS, VALID_JOB_TYPES, DEFAULT_JOB_PROVIDERS, 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, 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, QUEUE_LOCK_FILE_NAME;
34
+ var DEFAULT_DEFAULT_BRANCH, DEFAULT_PRD_DIR, 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_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, QA_LOG_NAME, DEFAULT_AUDIT_ENABLED, DEFAULT_AUDIT_SCHEDULE, DEFAULT_AUDIT_MAX_RUNTIME, 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, AUDIT_LOG_NAME, PLANNER_LOG_NAME, ANALYTICS_LOG_NAME, VALID_PROVIDERS, VALID_JOB_TYPES, DEFAULT_JOB_PROVIDERS, 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, QUEUE_LOCK_FILE_NAME;
35
35
  var init_constants = __esm({
36
36
  "../core/dist/constants.js"() {
37
37
  "use strict";
@@ -225,6 +225,7 @@ If no issues are warranted, output an empty array: []`;
225
225
  HISTORY_FILE_NAME = "history.json";
226
226
  PRD_STATES_FILE_NAME = "prd-states.json";
227
227
  STATE_DB_FILE_NAME = "state.db";
228
+ GLOBAL_NOTIFICATIONS_FILE_NAME = "global-notifications.json";
228
229
  MAX_HISTORY_RECORDS_PER_PRD = 10;
229
230
  DEFAULT_QUEUE_ENABLED = true;
230
231
  DEFAULT_QUEUE_MODE = "conservative";
@@ -2029,11 +2030,18 @@ var init_github_projects = __esm({
2029
2030
  await this.ensureStatusColumns(existing.id);
2030
2031
  return { id: existing.id, number: existing.number, title: existing.title, url: existing.url };
2031
2032
  }
2032
- const createData = await graphql(`mutation CreateProject($ownerId: ID!, $title: String!) {
2033
- createProjectV2(input: { ownerId: $ownerId, title: $title }) {
2034
- projectV2 { id number url title }
2033
+ const createData = await graphql(`
2034
+ mutation CreateProject($ownerId: ID!, $title: String!) {
2035
+ createProjectV2(input: { ownerId: $ownerId, title: $title }) {
2036
+ projectV2 {
2037
+ id
2038
+ number
2039
+ url
2040
+ title
2041
+ }
2042
+ }
2035
2043
  }
2036
- }`, { ownerId: owner.id, title }, this.cwd);
2044
+ `, { ownerId: owner.id, title }, this.cwd);
2037
2045
  const project = createData.createProjectV2.projectV2;
2038
2046
  this.cachedProjectId = project.id;
2039
2047
  await this.linkProjectToRepository(project.id);
@@ -2049,24 +2057,34 @@ var init_github_projects = __esm({
2049
2057
  const message = err instanceof Error ? err.message : String(err);
2050
2058
  if (!message.includes("Status field not found"))
2051
2059
  throw err;
2052
- const createFieldData = await graphql(`mutation CreateStatusField($projectId: ID!) {
2053
- createProjectV2Field(input: {
2054
- projectId: $projectId
2055
- dataType: SINGLE_SELECT
2056
- name: "Status"
2057
- singleSelectOptions: [
2058
- { name: "Draft", color: GRAY, description: "" }
2059
- { name: "Ready", color: BLUE, description: "" }
2060
- { name: "In Progress", color: YELLOW, description: "" }
2061
- { name: "Review", color: ORANGE, description: "" }
2062
- { name: "Done", color: GREEN, description: "" }
2063
- ]
2064
- }) {
2065
- projectV2Field {
2066
- ... on ProjectV2SingleSelectField { id options { id name } }
2060
+ const createFieldData = await graphql(`
2061
+ mutation CreateStatusField($projectId: ID!) {
2062
+ createProjectV2Field(
2063
+ input: {
2064
+ projectId: $projectId
2065
+ dataType: SINGLE_SELECT
2066
+ name: "Status"
2067
+ singleSelectOptions: [
2068
+ { name: "Draft", color: GRAY, description: "" }
2069
+ { name: "Ready", color: BLUE, description: "" }
2070
+ { name: "In Progress", color: YELLOW, description: "" }
2071
+ { name: "Review", color: ORANGE, description: "" }
2072
+ { name: "Done", color: GREEN, description: "" }
2073
+ ]
2074
+ }
2075
+ ) {
2076
+ projectV2Field {
2077
+ ... on ProjectV2SingleSelectField {
2078
+ id
2079
+ options {
2080
+ id
2081
+ name
2082
+ }
2083
+ }
2084
+ }
2067
2085
  }
2068
2086
  }
2069
- }`, { projectId: project.id }, this.cwd);
2087
+ `, { projectId: project.id }, this.cwd);
2070
2088
  const field = createFieldData.createProjectV2Field.projectV2Field;
2071
2089
  this.cachedFieldId = field.id;
2072
2090
  this.cachedOptionIds = new Map(field.options.map((o) => [o.name, o.id]));
@@ -2093,11 +2111,23 @@ var init_github_projects = __esm({
2093
2111
  async createIssue(input) {
2094
2112
  const repo = await this.getRepo();
2095
2113
  const { projectId, fieldId, optionIds } = await this.ensureProjectCache();
2096
- const issueArgs = ["issue", "create", "--title", input.title, "--body", input.body, "--repo", repo];
2114
+ const issueArgs = [
2115
+ "issue",
2116
+ "create",
2117
+ "--title",
2118
+ input.title,
2119
+ "--body",
2120
+ input.body,
2121
+ "--repo",
2122
+ repo
2123
+ ];
2097
2124
  if (input.labels && input.labels.length > 0) {
2098
2125
  issueArgs.push("--label", input.labels.join(","));
2099
2126
  }
2100
- const { stdout: issueUrlRaw } = await execFileAsync2("gh", issueArgs, { cwd: this.cwd, encoding: "utf-8" });
2127
+ const { stdout: issueUrlRaw } = await execFileAsync2("gh", issueArgs, {
2128
+ cwd: this.cwd,
2129
+ encoding: "utf-8"
2130
+ });
2101
2131
  const issueUrl = issueUrlRaw.trim();
2102
2132
  const issueNumber = parseInt(issueUrl.split("/").pop() ?? "", 10);
2103
2133
  if (!issueNumber)
@@ -2105,11 +2135,15 @@ var init_github_projects = __esm({
2105
2135
  const [owner, repoName] = repo.split("/");
2106
2136
  const { stdout: nodeIdRaw } = await execFileAsync2("gh", ["api", `repos/${owner}/${repoName}/issues/${issueNumber}`, "--jq", ".node_id"], { cwd: this.cwd, encoding: "utf-8" });
2107
2137
  const issueJson = { number: issueNumber, id: nodeIdRaw.trim(), url: issueUrl };
2108
- const addData = await graphql(`mutation AddProjectItem($projectId: ID!, $contentId: ID!) {
2109
- addProjectV2ItemById(input: { projectId: $projectId, contentId: $contentId }) {
2110
- item { id }
2138
+ const addData = await graphql(`
2139
+ mutation AddProjectItem($projectId: ID!, $contentId: ID!) {
2140
+ addProjectV2ItemById(input: { projectId: $projectId, contentId: $contentId }) {
2141
+ item {
2142
+ id
2143
+ }
2144
+ }
2111
2145
  }
2112
- }`, { projectId, contentId: issueJson.id }, this.cwd);
2146
+ `, { projectId, contentId: issueJson.id }, this.cwd);
2113
2147
  const itemId = addData.addProjectV2ItemById.item.id;
2114
2148
  const targetColumn = input.column ?? "Draft";
2115
2149
  const optionId = optionIds.get(targetColumn);
@@ -2129,11 +2163,45 @@ var init_github_projects = __esm({
2129
2163
  assignees: []
2130
2164
  };
2131
2165
  }
2166
+ async addIssue(issueNumber, column = "Ready") {
2167
+ const repo = await this.getRepo();
2168
+ const { projectId, fieldId, optionIds } = await this.ensureProjectCache();
2169
+ const [owner, repoName] = repo.split("/");
2170
+ const { stdout: nodeIdRaw } = await execFileAsync2("gh", ["api", `repos/${owner}/${repoName}/issues/${issueNumber}`, "--jq", ".node_id"], { cwd: this.cwd, encoding: "utf-8" });
2171
+ const nodeId = nodeIdRaw.trim();
2172
+ if (!nodeId)
2173
+ throw new Error(`Issue #${issueNumber} not found in ${repo}.`);
2174
+ const addData = await graphql(`
2175
+ mutation AddProjectItem($projectId: ID!, $contentId: ID!) {
2176
+ addProjectV2ItemById(input: { projectId: $projectId, contentId: $contentId }) {
2177
+ item {
2178
+ id
2179
+ }
2180
+ }
2181
+ }
2182
+ `, { projectId, contentId: nodeId }, this.cwd);
2183
+ const itemId = addData.addProjectV2ItemById.item.id;
2184
+ const optionId = optionIds.get(column);
2185
+ if (optionId)
2186
+ await this.setItemStatus(projectId, itemId, fieldId, optionId);
2187
+ const full = await this.getIssue(issueNumber);
2188
+ if (full)
2189
+ return { ...full, column };
2190
+ throw new Error(`Added issue #${issueNumber} to project but failed to fetch it back.`);
2191
+ }
2132
2192
  async getIssue(issueNumber) {
2133
2193
  const repo = await this.getRepo();
2134
2194
  let rawIssue;
2135
2195
  try {
2136
- const { stdout: output } = await execFileAsync2("gh", ["issue", "view", String(issueNumber), "--repo", repo, "--json", "number,title,body,url,id,labels,assignees"], { cwd: this.cwd, encoding: "utf-8" });
2196
+ const { stdout: output } = await execFileAsync2("gh", [
2197
+ "issue",
2198
+ "view",
2199
+ String(issueNumber),
2200
+ "--repo",
2201
+ repo,
2202
+ "--json",
2203
+ "number,title,body,url,id,labels,assignees"
2204
+ ], { cwd: this.cwd, encoding: "utf-8" });
2137
2205
  rawIssue = JSON.parse(output);
2138
2206
  } catch {
2139
2207
  return null;
@@ -2238,6 +2306,9 @@ var init_local_kanban = __esm({
2238
2306
  });
2239
2307
  return toIBoardIssue(row);
2240
2308
  }
2309
+ async addIssue(_issueNumber, _column) {
2310
+ throw new Error("addIssue is not supported by the local Kanban provider.");
2311
+ }
2241
2312
  async getIssue(issueNumber) {
2242
2313
  const row = this.repo.getByNumber(issueNumber);
2243
2314
  return row ? toIBoardIssue(row) : null;
@@ -4275,6 +4346,36 @@ var init_log_utils = __esm({
4275
4346
  }
4276
4347
  });
4277
4348
 
4349
+ // ../core/dist/utils/global-config.js
4350
+ import * as fs11 from "fs";
4351
+ import * as os5 from "os";
4352
+ import * as path10 from "path";
4353
+ function getGlobalNotificationsPath() {
4354
+ return path10.join(os5.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_NOTIFICATIONS_FILE_NAME);
4355
+ }
4356
+ function loadGlobalNotificationsConfig() {
4357
+ const filePath = getGlobalNotificationsPath();
4358
+ try {
4359
+ if (!fs11.existsSync(filePath))
4360
+ return { webhook: null };
4361
+ const raw = fs11.readFileSync(filePath, "utf-8");
4362
+ return JSON.parse(raw);
4363
+ } catch {
4364
+ return { webhook: null };
4365
+ }
4366
+ }
4367
+ function saveGlobalNotificationsConfig(config) {
4368
+ const filePath = getGlobalNotificationsPath();
4369
+ fs11.mkdirSync(path10.dirname(filePath), { recursive: true });
4370
+ fs11.writeFileSync(filePath, JSON.stringify(config, null, 2) + "\n", "utf-8");
4371
+ }
4372
+ var init_global_config = __esm({
4373
+ "../core/dist/utils/global-config.js"() {
4374
+ "use strict";
4375
+ init_constants();
4376
+ }
4377
+ });
4378
+
4278
4379
  // ../core/dist/utils/ui.js
4279
4380
  import chalk from "chalk";
4280
4381
  import ora from "ora";
@@ -4631,24 +4732,33 @@ async function sendWebhook(webhook, ctx) {
4631
4732
  warn(`Notification failed (${webhook.type}): ${message}`);
4632
4733
  }
4633
4734
  }
4735
+ function webhookIdentity(wh) {
4736
+ if (wh.type === "telegram")
4737
+ return `telegram:${wh.botToken}:${wh.chatId}`;
4738
+ return `${wh.type}:${wh.url}`;
4739
+ }
4634
4740
  async function sendNotifications(config, ctx) {
4635
- const webhooks = config.notifications?.webhooks ?? [];
4636
- const tasks = [];
4637
- for (const wh of webhooks) {
4638
- tasks.push(sendWebhook(wh, ctx));
4741
+ const projectWebhooks = config.notifications?.webhooks ?? [];
4742
+ const globalConfig = loadGlobalNotificationsConfig();
4743
+ const allWebhooks = [...projectWebhooks];
4744
+ if (globalConfig.webhook) {
4745
+ const projectIds = new Set(projectWebhooks.map(webhookIdentity));
4746
+ if (!projectIds.has(webhookIdentity(globalConfig.webhook))) {
4747
+ allWebhooks.push(globalConfig.webhook);
4748
+ }
4639
4749
  }
4640
- if (tasks.length === 0) {
4750
+ if (allWebhooks.length === 0) {
4641
4751
  return;
4642
4752
  }
4643
- const results = await Promise.allSettled(tasks);
4753
+ const results = await Promise.allSettled(allWebhooks.map((wh) => sendWebhook(wh, ctx)));
4644
4754
  const sent = results.filter((r) => r.status === "fulfilled").length;
4645
- const total = results.length;
4646
- info(`Sent ${sent}/${total} notifications`);
4755
+ info(`Sent ${sent}/${allWebhooks.length} notifications`);
4647
4756
  }
4648
4757
  var MAX_QA_SCREENSHOTS_IN_NOTIFICATION;
4649
4758
  var init_notify = __esm({
4650
4759
  "../core/dist/utils/notify.js"() {
4651
4760
  "use strict";
4761
+ init_global_config();
4652
4762
  init_ui();
4653
4763
  init_github();
4654
4764
  MAX_QA_SCREENSHOTS_IN_NOTIFICATION = 3;
@@ -4685,15 +4795,15 @@ var init_prd_discovery = __esm({
4685
4795
  });
4686
4796
 
4687
4797
  // ../core/dist/utils/prd-utils.js
4688
- import * as fs11 from "fs";
4689
- import * as path10 from "path";
4798
+ import * as fs12 from "fs";
4799
+ import * as path11 from "path";
4690
4800
  function slugify(name) {
4691
4801
  return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
4692
4802
  }
4693
4803
  function getNextPrdNumber(prdDir) {
4694
- if (!fs11.existsSync(prdDir))
4804
+ if (!fs12.existsSync(prdDir))
4695
4805
  return 1;
4696
- const files = fs11.readdirSync(prdDir).filter((f) => f.endsWith(".md"));
4806
+ const files = fs12.readdirSync(prdDir).filter((f) => f.endsWith(".md"));
4697
4807
  const numbers = files.map((f) => {
4698
4808
  const match = f.match(/^(\d+)-/);
4699
4809
  return match ? parseInt(match[1], 10) : 0;
@@ -4701,16 +4811,16 @@ function getNextPrdNumber(prdDir) {
4701
4811
  return Math.max(0, ...numbers) + 1;
4702
4812
  }
4703
4813
  function markPrdDone(prdDir, prdFile) {
4704
- const sourcePath = path10.join(prdDir, prdFile);
4705
- if (!fs11.existsSync(sourcePath)) {
4814
+ const sourcePath = path11.join(prdDir, prdFile);
4815
+ if (!fs12.existsSync(sourcePath)) {
4706
4816
  return false;
4707
4817
  }
4708
- const doneDir = path10.join(prdDir, "done");
4709
- if (!fs11.existsSync(doneDir)) {
4710
- fs11.mkdirSync(doneDir, { recursive: true });
4818
+ const doneDir = path11.join(prdDir, "done");
4819
+ if (!fs12.existsSync(doneDir)) {
4820
+ fs12.mkdirSync(doneDir, { recursive: true });
4711
4821
  }
4712
- const destPath = path10.join(doneDir, prdFile);
4713
- fs11.renameSync(sourcePath, destPath);
4822
+ const destPath = path11.join(doneDir, prdFile);
4823
+ fs12.renameSync(sourcePath, destPath);
4714
4824
  return true;
4715
4825
  }
4716
4826
  var init_prd_utils = __esm({
@@ -4720,16 +4830,16 @@ var init_prd_utils = __esm({
4720
4830
  });
4721
4831
 
4722
4832
  // ../core/dist/utils/registry.js
4723
- import * as fs12 from "fs";
4724
- import * as os5 from "os";
4725
- import * as path11 from "path";
4833
+ import * as fs13 from "fs";
4834
+ import * as os6 from "os";
4835
+ import * as path12 from "path";
4726
4836
  function readLegacyRegistryEntries() {
4727
4837
  const registryPath = getRegistryPath();
4728
- if (!fs12.existsSync(registryPath)) {
4838
+ if (!fs13.existsSync(registryPath)) {
4729
4839
  return [];
4730
4840
  }
4731
4841
  try {
4732
- const raw = fs12.readFileSync(registryPath, "utf-8");
4842
+ const raw = fs13.readFileSync(registryPath, "utf-8");
4733
4843
  const parsed = JSON.parse(raw);
4734
4844
  if (!Array.isArray(parsed)) {
4735
4845
  return [];
@@ -4764,8 +4874,8 @@ function loadRegistryEntriesWithLegacyFallback() {
4764
4874
  return projectRegistry.getAll();
4765
4875
  }
4766
4876
  function getRegistryPath() {
4767
- const base = process.env.NIGHT_WATCH_HOME || path11.join(os5.homedir(), GLOBAL_CONFIG_DIR);
4768
- return path11.join(base, REGISTRY_FILE_NAME);
4877
+ const base = process.env.NIGHT_WATCH_HOME || path12.join(os6.homedir(), GLOBAL_CONFIG_DIR);
4878
+ return path12.join(base, REGISTRY_FILE_NAME);
4769
4879
  }
4770
4880
  function loadRegistry() {
4771
4881
  return loadRegistryEntriesWithLegacyFallback();
@@ -4778,7 +4888,7 @@ function saveRegistry(entries) {
4778
4888
  }
4779
4889
  }
4780
4890
  function registerProject(projectDir) {
4781
- const resolvedPath = path11.resolve(projectDir);
4891
+ const resolvedPath = path12.resolve(projectDir);
4782
4892
  const { projectRegistry } = getRepositories();
4783
4893
  const entries = loadRegistryEntriesWithLegacyFallback();
4784
4894
  const existing = entries.find((e) => e.path === resolvedPath);
@@ -4787,13 +4897,13 @@ function registerProject(projectDir) {
4787
4897
  }
4788
4898
  const name = getProjectName(resolvedPath);
4789
4899
  const nameExists = entries.some((e) => e.name === name);
4790
- const finalName = nameExists ? `${name}-${path11.basename(resolvedPath)}` : name;
4900
+ const finalName = nameExists ? `${name}-${path12.basename(resolvedPath)}` : name;
4791
4901
  const entry = { name: finalName, path: resolvedPath };
4792
4902
  projectRegistry.upsert(entry);
4793
4903
  return entry;
4794
4904
  }
4795
4905
  function unregisterProject(projectDir) {
4796
- const resolvedPath = path11.resolve(projectDir);
4906
+ const resolvedPath = path12.resolve(projectDir);
4797
4907
  loadRegistryEntriesWithLegacyFallback();
4798
4908
  const { projectRegistry } = getRepositories();
4799
4909
  return projectRegistry.remove(resolvedPath);
@@ -4803,7 +4913,7 @@ function validateRegistry() {
4803
4913
  const valid = [];
4804
4914
  const invalid = [];
4805
4915
  for (const entry of entries) {
4806
- if (fs12.existsSync(entry.path) && fs12.existsSync(path11.join(entry.path, CONFIG_FILE_NAME))) {
4916
+ if (fs13.existsSync(entry.path) && fs13.existsSync(path12.join(entry.path, CONFIG_FILE_NAME))) {
4807
4917
  valid.push(entry);
4808
4918
  } else {
4809
4919
  invalid.push(entry);
@@ -4991,18 +5101,18 @@ var init_roadmap_parser = __esm({
4991
5101
  });
4992
5102
 
4993
5103
  // ../core/dist/utils/roadmap-state.js
4994
- import * as fs13 from "fs";
4995
- import * as path12 from "path";
5104
+ import * as fs14 from "fs";
5105
+ import * as path13 from "path";
4996
5106
  function getStateFilePath(prdDir) {
4997
- return path12.join(prdDir, STATE_FILE_NAME);
5107
+ return path13.join(prdDir, STATE_FILE_NAME);
4998
5108
  }
4999
5109
  function readJsonState(prdDir) {
5000
5110
  const statePath = getStateFilePath(prdDir);
5001
- if (!fs13.existsSync(statePath)) {
5111
+ if (!fs14.existsSync(statePath)) {
5002
5112
  return null;
5003
5113
  }
5004
5114
  try {
5005
- const content = fs13.readFileSync(statePath, "utf-8");
5115
+ const content = fs14.readFileSync(statePath, "utf-8");
5006
5116
  const parsed = JSON.parse(content);
5007
5117
  if (typeof parsed !== "object" || parsed === null) {
5008
5118
  return null;
@@ -5040,11 +5150,11 @@ function saveRoadmapState(prdDir, state) {
5040
5150
  const { roadmapState } = getRepositories();
5041
5151
  roadmapState.save(prdDir, state);
5042
5152
  const statePath = getStateFilePath(prdDir);
5043
- const dir = path12.dirname(statePath);
5044
- if (!fs13.existsSync(dir)) {
5045
- fs13.mkdirSync(dir, { recursive: true });
5153
+ const dir = path13.dirname(statePath);
5154
+ if (!fs14.existsSync(dir)) {
5155
+ fs14.mkdirSync(dir, { recursive: true });
5046
5156
  }
5047
- fs13.writeFileSync(statePath, JSON.stringify(state, null, 2) + "\n", "utf-8");
5157
+ fs14.writeFileSync(statePath, JSON.stringify(state, null, 2) + "\n", "utf-8");
5048
5158
  }
5049
5159
  function createEmptyState() {
5050
5160
  return {
@@ -5084,15 +5194,15 @@ var init_roadmap_state = __esm({
5084
5194
  });
5085
5195
 
5086
5196
  // ../core/dist/templates/slicer-prompt.js
5087
- import * as fs14 from "fs";
5088
- import * as path13 from "path";
5197
+ import * as fs15 from "fs";
5198
+ import * as path14 from "path";
5089
5199
  function loadSlicerTemplate(templateDir) {
5090
5200
  if (cachedTemplate) {
5091
5201
  return cachedTemplate;
5092
5202
  }
5093
- const templatePath = templateDir ? path13.join(templateDir, "slicer.md") : path13.resolve(__dirname, "..", "..", "templates", "slicer.md");
5203
+ const templatePath = templateDir ? path14.join(templateDir, "slicer.md") : path14.resolve(__dirname, "..", "..", "templates", "slicer.md");
5094
5204
  try {
5095
- cachedTemplate = fs14.readFileSync(templatePath, "utf-8");
5205
+ cachedTemplate = fs15.readFileSync(templatePath, "utf-8");
5096
5206
  return cachedTemplate;
5097
5207
  } catch (error2) {
5098
5208
  console.warn(`Warning: Could not load slicer template from ${templatePath}, using default:`, error2 instanceof Error ? error2.message : String(error2));
@@ -5117,7 +5227,7 @@ function createSlicerPromptVars(title, section, description, prdDir, prdFilename
5117
5227
  title,
5118
5228
  section,
5119
5229
  description: description || "(No description provided)",
5120
- outputFilePath: path13.join(prdDir, prdFilename),
5230
+ outputFilePath: path14.join(prdDir, prdFilename),
5121
5231
  prdDir
5122
5232
  };
5123
5233
  }
@@ -5211,8 +5321,8 @@ DO NOT forget to write the file.
5211
5321
  });
5212
5322
 
5213
5323
  // ../core/dist/utils/roadmap-scanner.js
5214
- import * as fs15 from "fs";
5215
- import * as path14 from "path";
5324
+ import * as fs16 from "fs";
5325
+ import * as path15 from "path";
5216
5326
  import { spawn } from "child_process";
5217
5327
  import { createHash as createHash3 } from "crypto";
5218
5328
  function normalizeAuditSeverity(raw) {
@@ -5313,11 +5423,11 @@ function auditFindingToRoadmapItem(finding) {
5313
5423
  };
5314
5424
  }
5315
5425
  function collectAuditPlannerItems(projectDir) {
5316
- const reportPath = path14.join(projectDir, "logs", "audit-report.md");
5317
- if (!fs15.existsSync(reportPath)) {
5426
+ const reportPath = path15.join(projectDir, "logs", "audit-report.md");
5427
+ if (!fs16.existsSync(reportPath)) {
5318
5428
  return [];
5319
5429
  }
5320
- const reportContent = fs15.readFileSync(reportPath, "utf-8");
5430
+ const reportContent = fs16.readFileSync(reportPath, "utf-8");
5321
5431
  if (!reportContent.trim() || /\bNO_ISSUES_FOUND\b/.test(reportContent)) {
5322
5432
  return [];
5323
5433
  }
@@ -5326,9 +5436,9 @@ function collectAuditPlannerItems(projectDir) {
5326
5436
  return findings.map(auditFindingToRoadmapItem);
5327
5437
  }
5328
5438
  function getRoadmapStatus(projectDir, config) {
5329
- const roadmapPath = path14.join(projectDir, config.roadmapScanner.roadmapPath);
5439
+ const roadmapPath = path15.join(projectDir, config.roadmapScanner.roadmapPath);
5330
5440
  const scannerEnabled = config.roadmapScanner.enabled;
5331
- if (!fs15.existsSync(roadmapPath)) {
5441
+ if (!fs16.existsSync(roadmapPath)) {
5332
5442
  return {
5333
5443
  found: false,
5334
5444
  enabled: scannerEnabled,
@@ -5339,9 +5449,9 @@ function getRoadmapStatus(projectDir, config) {
5339
5449
  items: []
5340
5450
  };
5341
5451
  }
5342
- const content = fs15.readFileSync(roadmapPath, "utf-8");
5452
+ const content = fs16.readFileSync(roadmapPath, "utf-8");
5343
5453
  const items = parseRoadmap(content);
5344
- const prdDir = path14.join(projectDir, config.prdDir);
5454
+ const prdDir = path15.join(projectDir, config.prdDir);
5345
5455
  const state = loadRoadmapState(prdDir);
5346
5456
  const existingPrdSlugs = scanExistingPrdSlugs(prdDir);
5347
5457
  const statusItems = items.map((item) => {
@@ -5378,10 +5488,10 @@ function getRoadmapStatus(projectDir, config) {
5378
5488
  }
5379
5489
  function scanExistingPrdSlugs(prdDir) {
5380
5490
  const slugs = /* @__PURE__ */ new Set();
5381
- if (!fs15.existsSync(prdDir)) {
5491
+ if (!fs16.existsSync(prdDir)) {
5382
5492
  return slugs;
5383
5493
  }
5384
- const files = fs15.readdirSync(prdDir);
5494
+ const files = fs16.readdirSync(prdDir);
5385
5495
  for (const file of files) {
5386
5496
  if (!file.endsWith(".md")) {
5387
5497
  continue;
@@ -5417,20 +5527,20 @@ async function sliceRoadmapItem(projectDir, prdDir, item, config) {
5417
5527
  const nextNum = getNextPrdNumber(prdDir);
5418
5528
  const padded = String(nextNum).padStart(2, "0");
5419
5529
  const filename = `${padded}-${itemSlug}.md`;
5420
- const filePath = path14.join(prdDir, filename);
5421
- if (!fs15.existsSync(prdDir)) {
5422
- fs15.mkdirSync(prdDir, { recursive: true });
5530
+ const filePath = path15.join(prdDir, filename);
5531
+ if (!fs16.existsSync(prdDir)) {
5532
+ fs16.mkdirSync(prdDir, { recursive: true });
5423
5533
  }
5424
5534
  const promptVars = createSlicerPromptVars(item.title, item.section, item.description, prdDir, filename);
5425
5535
  const prompt2 = renderSlicerPrompt(promptVars);
5426
5536
  const provider = resolveJobProvider(config, "slicer");
5427
5537
  const providerArgs = buildProviderArgs(provider, prompt2, projectDir);
5428
- const logDir = path14.join(projectDir, "logs");
5429
- if (!fs15.existsSync(logDir)) {
5430
- fs15.mkdirSync(logDir, { recursive: true });
5538
+ const logDir = path15.join(projectDir, "logs");
5539
+ if (!fs16.existsSync(logDir)) {
5540
+ fs16.mkdirSync(logDir, { recursive: true });
5431
5541
  }
5432
- const logFile = path14.join(logDir, `slicer-${itemSlug}.log`);
5433
- const logStream = fs15.createWriteStream(logFile, { flags: "w" });
5542
+ const logFile = path15.join(logDir, `slicer-${itemSlug}.log`);
5543
+ const logStream = fs16.createWriteStream(logFile, { flags: "w" });
5434
5544
  logStream.on("error", () => {
5435
5545
  });
5436
5546
  return new Promise((resolve10) => {
@@ -5467,7 +5577,7 @@ async function sliceRoadmapItem(projectDir, prdDir, item, config) {
5467
5577
  });
5468
5578
  return;
5469
5579
  }
5470
- if (!fs15.existsSync(filePath)) {
5580
+ if (!fs16.existsSync(filePath)) {
5471
5581
  resolve10({
5472
5582
  sliced: false,
5473
5583
  error: `Provider did not create expected file: ${filePath}`,
@@ -5490,23 +5600,23 @@ async function sliceNextItem(projectDir, config) {
5490
5600
  error: "Roadmap scanner is disabled"
5491
5601
  };
5492
5602
  }
5493
- const roadmapPath = path14.join(projectDir, config.roadmapScanner.roadmapPath);
5603
+ const roadmapPath = path15.join(projectDir, config.roadmapScanner.roadmapPath);
5494
5604
  const auditItems = collectAuditPlannerItems(projectDir);
5495
- const roadmapExists = fs15.existsSync(roadmapPath);
5605
+ const roadmapExists = fs16.existsSync(roadmapPath);
5496
5606
  if (!roadmapExists && auditItems.length === 0) {
5497
5607
  return {
5498
5608
  sliced: false,
5499
5609
  error: "No pending items to process"
5500
5610
  };
5501
5611
  }
5502
- const roadmapItems = roadmapExists ? parseRoadmap(fs15.readFileSync(roadmapPath, "utf-8")) : [];
5612
+ const roadmapItems = roadmapExists ? parseRoadmap(fs16.readFileSync(roadmapPath, "utf-8")) : [];
5503
5613
  if (roadmapExists && roadmapItems.length === 0 && auditItems.length === 0) {
5504
5614
  return {
5505
5615
  sliced: false,
5506
5616
  error: "No items in roadmap"
5507
5617
  };
5508
5618
  }
5509
- const prdDir = path14.join(projectDir, config.prdDir);
5619
+ const prdDir = path15.join(projectDir, config.prdDir);
5510
5620
  const state = loadRoadmapState(prdDir);
5511
5621
  const existingPrdSlugs = scanExistingPrdSlugs(prdDir);
5512
5622
  const pickEligibleItem = (items) => {
@@ -5677,8 +5787,8 @@ var init_shell = __esm({
5677
5787
  });
5678
5788
 
5679
5789
  // ../core/dist/utils/scheduling.js
5680
- import * as fs16 from "fs";
5681
- import * as path15 from "path";
5790
+ import * as fs17 from "fs";
5791
+ import * as path16 from "path";
5682
5792
  function normalizeSchedulingPriority(priority) {
5683
5793
  if (!Number.isFinite(priority)) {
5684
5794
  return DEFAULT_SCHEDULING_PRIORITY;
@@ -5704,7 +5814,7 @@ function isJobTypeEnabled(config, jobType) {
5704
5814
  }
5705
5815
  }
5706
5816
  function loadPeerConfig(projectPath) {
5707
- if (!fs16.existsSync(projectPath) || !fs16.existsSync(path15.join(projectPath, CONFIG_FILE_NAME))) {
5817
+ if (!fs17.existsSync(projectPath) || !fs17.existsSync(path16.join(projectPath, CONFIG_FILE_NAME))) {
5708
5818
  return null;
5709
5819
  }
5710
5820
  try {
@@ -5715,9 +5825,9 @@ function loadPeerConfig(projectPath) {
5715
5825
  }
5716
5826
  function collectSchedulingPeers(currentProjectDir, currentConfig, jobType) {
5717
5827
  const peers = /* @__PURE__ */ new Map();
5718
- const currentPath = path15.resolve(currentProjectDir);
5828
+ const currentPath = path16.resolve(currentProjectDir);
5719
5829
  const addPeer = (projectPath, config) => {
5720
- const resolvedPath = path15.resolve(projectPath);
5830
+ const resolvedPath = path16.resolve(projectPath);
5721
5831
  if (!isJobTypeEnabled(config, jobType)) {
5722
5832
  return;
5723
5833
  }
@@ -5725,12 +5835,12 @@ function collectSchedulingPeers(currentProjectDir, currentConfig, jobType) {
5725
5835
  path: resolvedPath,
5726
5836
  config,
5727
5837
  schedulingPriority: normalizeSchedulingPriority(config.schedulingPriority),
5728
- sortKey: `${path15.basename(resolvedPath).toLowerCase()}::${resolvedPath.toLowerCase()}`
5838
+ sortKey: `${path16.basename(resolvedPath).toLowerCase()}::${resolvedPath.toLowerCase()}`
5729
5839
  });
5730
5840
  };
5731
5841
  addPeer(currentPath, currentConfig);
5732
5842
  for (const entry of loadRegistry()) {
5733
- const resolvedPath = path15.resolve(entry.path);
5843
+ const resolvedPath = path16.resolve(entry.path);
5734
5844
  if (resolvedPath === currentPath || peers.has(resolvedPath)) {
5735
5845
  continue;
5736
5846
  }
@@ -5748,7 +5858,7 @@ function collectSchedulingPeers(currentProjectDir, currentConfig, jobType) {
5748
5858
  }
5749
5859
  function getSchedulingPlan(projectDir, config, jobType) {
5750
5860
  const peers = collectSchedulingPeers(projectDir, config, jobType);
5751
- const currentPath = path15.resolve(projectDir);
5861
+ const currentPath = path16.resolve(projectDir);
5752
5862
  const slotIndex = Math.max(0, peers.findIndex((peer) => peer.path === currentPath));
5753
5863
  const peerCount = Math.max(1, peers.length);
5754
5864
  const balancedDelayMinutes = peerCount <= 1 ? 0 : Math.floor(slotIndex * 60 / peerCount);
@@ -5840,8 +5950,8 @@ var init_webhook_validator = __esm({
5840
5950
 
5841
5951
  // ../core/dist/utils/worktree-manager.js
5842
5952
  import { execFileSync as execFileSync4 } from "child_process";
5843
- import * as fs17 from "fs";
5844
- import * as path16 from "path";
5953
+ import * as fs18 from "fs";
5954
+ import * as path17 from "path";
5845
5955
  function gitExec(args, cwd, logFile) {
5846
5956
  try {
5847
5957
  const result = execFileSync4("git", args, {
@@ -5851,7 +5961,7 @@ function gitExec(args, cwd, logFile) {
5851
5961
  });
5852
5962
  if (logFile && result) {
5853
5963
  try {
5854
- fs17.appendFileSync(logFile, result);
5964
+ fs18.appendFileSync(logFile, result);
5855
5965
  } catch {
5856
5966
  }
5857
5967
  }
@@ -5860,7 +5970,7 @@ function gitExec(args, cwd, logFile) {
5860
5970
  const errorMessage = error2 instanceof Error ? error2.message : String(error2);
5861
5971
  if (logFile) {
5862
5972
  try {
5863
- fs17.appendFileSync(logFile, errorMessage + "\n");
5973
+ fs18.appendFileSync(logFile, errorMessage + "\n");
5864
5974
  } catch {
5865
5975
  }
5866
5976
  }
@@ -5885,11 +5995,11 @@ function branchExistsRemotely(projectDir, branchName) {
5885
5995
  }
5886
5996
  function prepareBranchWorktree(options) {
5887
5997
  const { projectDir, worktreeDir, branchName, defaultBranch, logFile } = options;
5888
- if (fs17.existsSync(worktreeDir)) {
5998
+ if (fs18.existsSync(worktreeDir)) {
5889
5999
  const isRegistered = isWorktreeRegistered(projectDir, worktreeDir);
5890
6000
  if (!isRegistered) {
5891
6001
  try {
5892
- fs17.rmSync(worktreeDir, { recursive: true, force: true });
6002
+ fs18.rmSync(worktreeDir, { recursive: true, force: true });
5893
6003
  } catch {
5894
6004
  }
5895
6005
  }
@@ -5928,11 +6038,11 @@ function prepareBranchWorktree(options) {
5928
6038
  }
5929
6039
  function prepareDetachedWorktree(options) {
5930
6040
  const { projectDir, worktreeDir, defaultBranch, logFile } = options;
5931
- if (fs17.existsSync(worktreeDir)) {
6041
+ if (fs18.existsSync(worktreeDir)) {
5932
6042
  const isRegistered = isWorktreeRegistered(projectDir, worktreeDir);
5933
6043
  if (!isRegistered) {
5934
6044
  try {
5935
- fs17.rmSync(worktreeDir, { recursive: true, force: true });
6045
+ fs18.rmSync(worktreeDir, { recursive: true, force: true });
5936
6046
  } catch {
5937
6047
  }
5938
6048
  }
@@ -5974,7 +6084,7 @@ function isWorktreeRegistered(projectDir, worktreePath) {
5974
6084
  }
5975
6085
  }
5976
6086
  function cleanupWorktrees(projectDir, scope) {
5977
- const projectName = path16.basename(projectDir);
6087
+ const projectName = path17.basename(projectDir);
5978
6088
  const matchToken = scope ? scope : `${projectName}-nw`;
5979
6089
  const removed = [];
5980
6090
  try {
@@ -6009,16 +6119,16 @@ var init_worktree_manager = __esm({
6009
6119
  });
6010
6120
 
6011
6121
  // ../core/dist/utils/job-queue.js
6012
- import * as os6 from "os";
6013
- import * as path17 from "path";
6122
+ import * as os7 from "os";
6123
+ import * as path18 from "path";
6014
6124
  import Database7 from "better-sqlite3";
6015
6125
  function getStateDbPath() {
6016
- const base = process.env.NIGHT_WATCH_HOME || path17.join(os6.homedir(), GLOBAL_CONFIG_DIR);
6017
- return path17.join(base, STATE_DB_FILE_NAME);
6126
+ const base = process.env.NIGHT_WATCH_HOME || path18.join(os7.homedir(), GLOBAL_CONFIG_DIR);
6127
+ return path18.join(base, STATE_DB_FILE_NAME);
6018
6128
  }
6019
6129
  function getQueueLockPath() {
6020
- const base = process.env.NIGHT_WATCH_HOME || path17.join(os6.homedir(), GLOBAL_CONFIG_DIR);
6021
- return path17.join(base, QUEUE_LOCK_FILE_NAME);
6130
+ const base = process.env.NIGHT_WATCH_HOME || path18.join(os7.homedir(), GLOBAL_CONFIG_DIR);
6131
+ return path18.join(base, QUEUE_LOCK_FILE_NAME);
6022
6132
  }
6023
6133
  function openDb() {
6024
6134
  const dbPath = getStateDbPath();
@@ -6586,9 +6696,9 @@ var init_amplitude_client = __esm({
6586
6696
  });
6587
6697
 
6588
6698
  // ../core/dist/analytics/analytics-runner.js
6589
- import * as fs18 from "fs";
6590
- import * as os7 from "os";
6591
- import * as path18 from "path";
6699
+ import * as fs19 from "fs";
6700
+ import * as os8 from "os";
6701
+ import * as path19 from "path";
6592
6702
  function parseIssuesFromResponse(text) {
6593
6703
  const start = text.indexOf("[");
6594
6704
  const end = text.lastIndexOf("]");
@@ -6617,9 +6727,9 @@ async function runAnalytics(config, projectDir) {
6617
6727
 
6618
6728
  --- AMPLITUDE DATA ---
6619
6729
  ${JSON.stringify(data, null, 2)}`;
6620
- const tmpDir = fs18.mkdtempSync(path18.join(os7.tmpdir(), "nw-analytics-"));
6621
- const promptFile = path18.join(tmpDir, "analytics-prompt.md");
6622
- fs18.writeFileSync(promptFile, prompt2, "utf-8");
6730
+ const tmpDir = fs19.mkdtempSync(path19.join(os8.tmpdir(), "nw-analytics-"));
6731
+ const promptFile = path19.join(tmpDir, "analytics-prompt.md");
6732
+ fs19.writeFileSync(promptFile, prompt2, "utf-8");
6623
6733
  try {
6624
6734
  const provider = resolveJobProvider(config, "analytics");
6625
6735
  const providerCmd = PROVIDER_COMMANDS[provider];
@@ -6636,8 +6746,8 @@ set -euo pipefail
6636
6746
  ${providerCmd} exec --yolo "$(cat ${promptFile})" 2>&1
6637
6747
  `;
6638
6748
  }
6639
- const scriptFile = path18.join(tmpDir, "run-analytics.sh");
6640
- fs18.writeFileSync(scriptFile, scriptContent, { mode: 493 });
6749
+ const scriptFile = path19.join(tmpDir, "run-analytics.sh");
6750
+ fs19.writeFileSync(scriptFile, scriptContent, { mode: 493 });
6641
6751
  const { exitCode, stdout, stderr } = await executeScriptWithOutput(scriptFile, [], config.providerEnv ?? {});
6642
6752
  if (exitCode !== 0) {
6643
6753
  throw new Error(`AI provider exited with code ${exitCode}: ${stderr || stdout}`);
@@ -6675,7 +6785,7 @@ ${stderr}`;
6675
6785
  };
6676
6786
  } finally {
6677
6787
  try {
6678
- fs18.rmSync(tmpDir, { recursive: true, force: true });
6788
+ fs19.rmSync(tmpDir, { recursive: true, force: true });
6679
6789
  } catch {
6680
6790
  }
6681
6791
  }
@@ -6953,6 +7063,7 @@ __export(dist_exports, {
6953
7063
  EXECUTOR_LOG_FILE: () => EXECUTOR_LOG_FILE,
6954
7064
  EXECUTOR_LOG_NAME: () => EXECUTOR_LOG_NAME,
6955
7065
  GLOBAL_CONFIG_DIR: () => GLOBAL_CONFIG_DIR,
7066
+ GLOBAL_NOTIFICATIONS_FILE_NAME: () => GLOBAL_NOTIFICATIONS_FILE_NAME,
6956
7067
  HISTORY_FILE_NAME: () => HISTORY_FILE_NAME,
6957
7068
  HORIZON_LABELS: () => HORIZON_LABELS,
6958
7069
  HORIZON_LABEL_INFO: () => HORIZON_LABEL_INFO,
@@ -7106,6 +7217,7 @@ __export(dist_exports, {
7106
7217
  label: () => label,
7107
7218
  listPrdStatesByStatus: () => listPrdStatesByStatus,
7108
7219
  loadConfig: () => loadConfig,
7220
+ loadGlobalNotificationsConfig: () => loadGlobalNotificationsConfig,
7109
7221
  loadHistory: () => loadHistory,
7110
7222
  loadRegistry: () => loadRegistry,
7111
7223
  loadRoadmapState: () => loadRoadmapState,
@@ -7146,6 +7258,7 @@ __export(dist_exports, {
7146
7258
  runAnalytics: () => runAnalytics,
7147
7259
  runMigrations: () => runMigrations,
7148
7260
  saveConfig: () => saveConfig,
7261
+ saveGlobalNotificationsConfig: () => saveGlobalNotificationsConfig,
7149
7262
  saveHistory: () => saveHistory,
7150
7263
  saveRegistry: () => saveRegistry,
7151
7264
  saveRoadmapState: () => saveRoadmapState,
@@ -7195,6 +7308,7 @@ var init_dist = __esm({
7195
7308
  init_git_utils();
7196
7309
  init_github();
7197
7310
  init_log_utils();
7311
+ init_global_config();
7198
7312
  init_notify();
7199
7313
  init_prd_discovery();
7200
7314
  init_prd_states();
@@ -7221,39 +7335,39 @@ var init_dist = __esm({
7221
7335
  // src/cli.ts
7222
7336
  import "reflect-metadata";
7223
7337
  import { Command as Command3 } from "commander";
7224
- import { existsSync as existsSync29, readFileSync as readFileSync18 } from "fs";
7338
+ import { existsSync as existsSync30, readFileSync as readFileSync19 } from "fs";
7225
7339
  import { fileURLToPath as fileURLToPath4 } from "url";
7226
- import { dirname as dirname8, join as join35 } from "path";
7340
+ import { dirname as dirname9, join as join36 } from "path";
7227
7341
 
7228
7342
  // src/commands/init.ts
7229
7343
  init_dist();
7230
- import fs19 from "fs";
7231
- import path19 from "path";
7344
+ import fs20 from "fs";
7345
+ import path20 from "path";
7232
7346
  import { execSync as execSync3 } from "child_process";
7233
7347
  import { fileURLToPath as fileURLToPath2 } from "url";
7234
- import { dirname as dirname4, join as join17 } from "path";
7348
+ import { dirname as dirname5, join as join18 } from "path";
7235
7349
  import * as readline from "readline";
7236
7350
  var __filename = fileURLToPath2(import.meta.url);
7237
- var __dirname2 = dirname4(__filename);
7351
+ var __dirname2 = dirname5(__filename);
7238
7352
  function findTemplatesDir(startDir) {
7239
7353
  let d = startDir;
7240
7354
  for (let i = 0; i < 8; i++) {
7241
- const candidate = join17(d, "templates");
7242
- if (fs19.existsSync(candidate) && fs19.statSync(candidate).isDirectory()) {
7355
+ const candidate = join18(d, "templates");
7356
+ if (fs20.existsSync(candidate) && fs20.statSync(candidate).isDirectory()) {
7243
7357
  return candidate;
7244
7358
  }
7245
- d = dirname4(d);
7359
+ d = dirname5(d);
7246
7360
  }
7247
- return join17(startDir, "templates");
7361
+ return join18(startDir, "templates");
7248
7362
  }
7249
7363
  var TEMPLATES_DIR = findTemplatesDir(__dirname2);
7250
7364
  function hasPlaywrightDependency(cwd) {
7251
- const packageJsonPath = path19.join(cwd, "package.json");
7252
- if (!fs19.existsSync(packageJsonPath)) {
7365
+ const packageJsonPath = path20.join(cwd, "package.json");
7366
+ if (!fs20.existsSync(packageJsonPath)) {
7253
7367
  return false;
7254
7368
  }
7255
7369
  try {
7256
- const packageJson2 = JSON.parse(fs19.readFileSync(packageJsonPath, "utf-8"));
7370
+ const packageJson2 = JSON.parse(fs20.readFileSync(packageJsonPath, "utf-8"));
7257
7371
  return Boolean(
7258
7372
  packageJson2.dependencies?.["@playwright/test"] || packageJson2.dependencies?.playwright || packageJson2.devDependencies?.["@playwright/test"] || packageJson2.devDependencies?.playwright
7259
7373
  );
@@ -7265,7 +7379,7 @@ function detectPlaywright(cwd) {
7265
7379
  if (hasPlaywrightDependency(cwd)) {
7266
7380
  return true;
7267
7381
  }
7268
- if (fs19.existsSync(path19.join(cwd, "node_modules", ".bin", "playwright"))) {
7382
+ if (fs20.existsSync(path20.join(cwd, "node_modules", ".bin", "playwright"))) {
7269
7383
  return true;
7270
7384
  }
7271
7385
  try {
@@ -7281,10 +7395,10 @@ function detectPlaywright(cwd) {
7281
7395
  }
7282
7396
  }
7283
7397
  function resolvePlaywrightInstallCommand(cwd) {
7284
- if (fs19.existsSync(path19.join(cwd, "pnpm-lock.yaml"))) {
7398
+ if (fs20.existsSync(path20.join(cwd, "pnpm-lock.yaml"))) {
7285
7399
  return "pnpm add -D @playwright/test";
7286
7400
  }
7287
- if (fs19.existsSync(path19.join(cwd, "yarn.lock"))) {
7401
+ if (fs20.existsSync(path20.join(cwd, "yarn.lock"))) {
7288
7402
  return "yarn add -D @playwright/test";
7289
7403
  }
7290
7404
  return "npm install -D @playwright/test";
@@ -7430,8 +7544,8 @@ function promptProviderSelection(providers) {
7430
7544
  });
7431
7545
  }
7432
7546
  function ensureDir(dirPath) {
7433
- if (!fs19.existsSync(dirPath)) {
7434
- fs19.mkdirSync(dirPath, { recursive: true });
7547
+ if (!fs20.existsSync(dirPath)) {
7548
+ fs20.mkdirSync(dirPath, { recursive: true });
7435
7549
  }
7436
7550
  }
7437
7551
  function buildInitConfig(params) {
@@ -7488,30 +7602,30 @@ function buildInitConfig(params) {
7488
7602
  }
7489
7603
  function resolveTemplatePath(templateName, customTemplatesDir, bundledTemplatesDir) {
7490
7604
  if (customTemplatesDir !== null) {
7491
- const customPath = join17(customTemplatesDir, templateName);
7492
- if (fs19.existsSync(customPath)) {
7605
+ const customPath = join18(customTemplatesDir, templateName);
7606
+ if (fs20.existsSync(customPath)) {
7493
7607
  return { path: customPath, source: "custom" };
7494
7608
  }
7495
7609
  }
7496
- return { path: join17(bundledTemplatesDir, templateName), source: "bundled" };
7610
+ return { path: join18(bundledTemplatesDir, templateName), source: "bundled" };
7497
7611
  }
7498
7612
  function processTemplate(templateName, targetPath, replacements, force, sourcePath, source) {
7499
- if (fs19.existsSync(targetPath) && !force) {
7613
+ if (fs20.existsSync(targetPath) && !force) {
7500
7614
  console.log(` Skipped (exists): ${targetPath}`);
7501
7615
  return { created: false, source: source ?? "bundled" };
7502
7616
  }
7503
- const templatePath = sourcePath ?? join17(TEMPLATES_DIR, templateName);
7617
+ const templatePath = sourcePath ?? join18(TEMPLATES_DIR, templateName);
7504
7618
  const resolvedSource = source ?? "bundled";
7505
- let content = fs19.readFileSync(templatePath, "utf-8");
7619
+ let content = fs20.readFileSync(templatePath, "utf-8");
7506
7620
  for (const [key, value] of Object.entries(replacements)) {
7507
7621
  content = content.replaceAll(key, value);
7508
7622
  }
7509
- fs19.writeFileSync(targetPath, content);
7623
+ fs20.writeFileSync(targetPath, content);
7510
7624
  console.log(` Created: ${targetPath} (${resolvedSource})`);
7511
7625
  return { created: true, source: resolvedSource };
7512
7626
  }
7513
7627
  function addToGitignore(cwd) {
7514
- const gitignorePath = path19.join(cwd, ".gitignore");
7628
+ const gitignorePath = path20.join(cwd, ".gitignore");
7515
7629
  const entries = [
7516
7630
  {
7517
7631
  pattern: "/logs/",
@@ -7525,13 +7639,13 @@ function addToGitignore(cwd) {
7525
7639
  },
7526
7640
  { pattern: "*.claim", label: "*.claim", check: (c) => c.includes("*.claim") }
7527
7641
  ];
7528
- if (!fs19.existsSync(gitignorePath)) {
7642
+ if (!fs20.existsSync(gitignorePath)) {
7529
7643
  const lines = ["# Night Watch", ...entries.map((e) => e.pattern), ""];
7530
- fs19.writeFileSync(gitignorePath, lines.join("\n"));
7644
+ fs20.writeFileSync(gitignorePath, lines.join("\n"));
7531
7645
  console.log(` Created: ${gitignorePath} (with Night Watch entries)`);
7532
7646
  return;
7533
7647
  }
7534
- const content = fs19.readFileSync(gitignorePath, "utf-8");
7648
+ const content = fs20.readFileSync(gitignorePath, "utf-8");
7535
7649
  const missing = entries.filter((e) => !e.check(content));
7536
7650
  if (missing.length === 0) {
7537
7651
  console.log(` Skipped (exists): Night Watch entries in .gitignore`);
@@ -7539,7 +7653,7 @@ function addToGitignore(cwd) {
7539
7653
  }
7540
7654
  const additions = missing.map((e) => e.pattern).join("\n");
7541
7655
  const newContent = content.trimEnd() + "\n\n# Night Watch\n" + additions + "\n";
7542
- fs19.writeFileSync(gitignorePath, newContent);
7656
+ fs20.writeFileSync(gitignorePath, newContent);
7543
7657
  console.log(` Updated: ${gitignorePath} (added ${missing.map((e) => e.label).join(", ")})`);
7544
7658
  }
7545
7659
  function initCommand(program2) {
@@ -7660,28 +7774,28 @@ function initCommand(program2) {
7660
7774
  "${DEFAULT_BRANCH}": defaultBranch
7661
7775
  };
7662
7776
  step(6, totalSteps, "Creating PRD directory structure...");
7663
- const prdDirPath = path19.join(cwd, prdDir);
7664
- const doneDirPath = path19.join(prdDirPath, "done");
7777
+ const prdDirPath = path20.join(cwd, prdDir);
7778
+ const doneDirPath = path20.join(prdDirPath, "done");
7665
7779
  ensureDir(doneDirPath);
7666
7780
  success(`Created ${prdDirPath}/`);
7667
7781
  success(`Created ${doneDirPath}/`);
7668
7782
  step(7, totalSteps, "Creating logs directory...");
7669
- const logsPath = path19.join(cwd, LOG_DIR);
7783
+ const logsPath = path20.join(cwd, LOG_DIR);
7670
7784
  ensureDir(logsPath);
7671
7785
  success(`Created ${logsPath}/`);
7672
7786
  addToGitignore(cwd);
7673
7787
  step(8, totalSteps, "Creating instructions directory...");
7674
- const instructionsDir = path19.join(cwd, "instructions");
7788
+ const instructionsDir = path20.join(cwd, "instructions");
7675
7789
  ensureDir(instructionsDir);
7676
7790
  success(`Created ${instructionsDir}/`);
7677
7791
  const existingConfig = loadConfig(cwd);
7678
- const customTemplatesDirPath = path19.join(cwd, existingConfig.templatesDir);
7679
- const customTemplatesDir = fs19.existsSync(customTemplatesDirPath) ? customTemplatesDirPath : null;
7792
+ const customTemplatesDirPath = path20.join(cwd, existingConfig.templatesDir);
7793
+ const customTemplatesDir = fs20.existsSync(customTemplatesDirPath) ? customTemplatesDirPath : null;
7680
7794
  const templateSources = [];
7681
7795
  const nwResolution = resolveTemplatePath("executor.md", customTemplatesDir, TEMPLATES_DIR);
7682
7796
  const nwResult = processTemplate(
7683
7797
  "executor.md",
7684
- path19.join(instructionsDir, "executor.md"),
7798
+ path20.join(instructionsDir, "executor.md"),
7685
7799
  replacements,
7686
7800
  force,
7687
7801
  nwResolution.path,
@@ -7695,7 +7809,7 @@ function initCommand(program2) {
7695
7809
  );
7696
7810
  const peResult = processTemplate(
7697
7811
  "prd-executor.md",
7698
- path19.join(instructionsDir, "prd-executor.md"),
7812
+ path20.join(instructionsDir, "prd-executor.md"),
7699
7813
  replacements,
7700
7814
  force,
7701
7815
  peResolution.path,
@@ -7705,7 +7819,7 @@ function initCommand(program2) {
7705
7819
  const prResolution = resolveTemplatePath("pr-reviewer.md", customTemplatesDir, TEMPLATES_DIR);
7706
7820
  const prResult = processTemplate(
7707
7821
  "pr-reviewer.md",
7708
- path19.join(instructionsDir, "pr-reviewer.md"),
7822
+ path20.join(instructionsDir, "pr-reviewer.md"),
7709
7823
  replacements,
7710
7824
  force,
7711
7825
  prResolution.path,
@@ -7715,7 +7829,7 @@ function initCommand(program2) {
7715
7829
  const qaResolution = resolveTemplatePath("qa.md", customTemplatesDir, TEMPLATES_DIR);
7716
7830
  const qaResult = processTemplate(
7717
7831
  "qa.md",
7718
- path19.join(instructionsDir, "qa.md"),
7832
+ path20.join(instructionsDir, "qa.md"),
7719
7833
  replacements,
7720
7834
  force,
7721
7835
  qaResolution.path,
@@ -7725,7 +7839,7 @@ function initCommand(program2) {
7725
7839
  const auditResolution = resolveTemplatePath("audit.md", customTemplatesDir, TEMPLATES_DIR);
7726
7840
  const auditResult = processTemplate(
7727
7841
  "audit.md",
7728
- path19.join(instructionsDir, "audit.md"),
7842
+ path20.join(instructionsDir, "audit.md"),
7729
7843
  replacements,
7730
7844
  force,
7731
7845
  auditResolution.path,
@@ -7733,8 +7847,8 @@ function initCommand(program2) {
7733
7847
  );
7734
7848
  templateSources.push({ name: "audit.md", source: auditResult.source });
7735
7849
  step(9, totalSteps, "Creating configuration file...");
7736
- const configPath = path19.join(cwd, CONFIG_FILE_NAME);
7737
- if (fs19.existsSync(configPath) && !force) {
7850
+ const configPath = path20.join(cwd, CONFIG_FILE_NAME);
7851
+ if (fs20.existsSync(configPath) && !force) {
7738
7852
  console.log(` Skipped (exists): ${configPath}`);
7739
7853
  } else {
7740
7854
  const config = buildInitConfig({
@@ -7744,11 +7858,11 @@ function initCommand(program2) {
7744
7858
  reviewerEnabled,
7745
7859
  prdDir
7746
7860
  });
7747
- fs19.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
7861
+ fs20.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
7748
7862
  success(`Created ${configPath}`);
7749
7863
  }
7750
7864
  step(10, totalSteps, "Setting up GitHub Project board...");
7751
- const existingRaw = JSON.parse(fs19.readFileSync(configPath, "utf-8"));
7865
+ const existingRaw = JSON.parse(fs20.readFileSync(configPath, "utf-8"));
7752
7866
  const existingBoard = existingRaw.boardProvider;
7753
7867
  let boardSetupStatus = "Skipped";
7754
7868
  if (existingBoard?.projectNumber && !force) {
@@ -7770,13 +7884,13 @@ function initCommand(program2) {
7770
7884
  const provider = createBoardProvider({ enabled: true, provider: "github" }, cwd);
7771
7885
  const boardTitle = `${projectName} Night Watch`;
7772
7886
  const board = await provider.setupBoard(boardTitle);
7773
- const rawConfig = JSON.parse(fs19.readFileSync(configPath, "utf-8"));
7887
+ const rawConfig = JSON.parse(fs20.readFileSync(configPath, "utf-8"));
7774
7888
  rawConfig.boardProvider = {
7775
7889
  enabled: true,
7776
7890
  provider: "github",
7777
7891
  projectNumber: board.number
7778
7892
  };
7779
- fs19.writeFileSync(configPath, JSON.stringify(rawConfig, null, 2) + "\n");
7893
+ fs20.writeFileSync(configPath, JSON.stringify(rawConfig, null, 2) + "\n");
7780
7894
  boardSetupStatus = `Created (#${board.number})`;
7781
7895
  success(`GitHub Project board "${boardTitle}" ready (#${board.number})`);
7782
7896
  } catch (boardErr) {
@@ -7900,8 +8014,8 @@ function getTelegramStatusWebhooks(config) {
7900
8014
  }
7901
8015
 
7902
8016
  // src/commands/run.ts
7903
- import * as fs20 from "fs";
7904
- import * as path20 from "path";
8017
+ import * as fs21 from "fs";
8018
+ import * as path21 from "path";
7905
8019
  function resolveRunNotificationEvent(exitCode, scriptStatus) {
7906
8020
  if (exitCode === 124) {
7907
8021
  return "run_timeout";
@@ -7933,12 +8047,12 @@ function shouldAttemptCrossProjectFallback(options, scriptStatus) {
7933
8047
  return scriptStatus === "skip_no_eligible_prd";
7934
8048
  }
7935
8049
  function getCrossProjectFallbackCandidates(currentProjectDir) {
7936
- const current = path20.resolve(currentProjectDir);
8050
+ const current = path21.resolve(currentProjectDir);
7937
8051
  const { valid, invalid } = validateRegistry();
7938
8052
  for (const entry of invalid) {
7939
8053
  warn(`Skipping invalid registry entry: ${entry.path}`);
7940
8054
  }
7941
- return valid.filter((entry) => path20.resolve(entry.path) !== current);
8055
+ return valid.filter((entry) => path21.resolve(entry.path) !== current);
7942
8056
  }
7943
8057
  async function sendRunCompletionNotifications(config, projectDir, options, exitCode, scriptResult) {
7944
8058
  if (isRateLimitFallbackTriggered(scriptResult?.data)) {
@@ -7948,7 +8062,7 @@ async function sendRunCompletionNotifications(config, projectDir, options, exitC
7948
8062
  if (nonTelegramWebhooks.length > 0) {
7949
8063
  const _rateLimitCtx = {
7950
8064
  event: "rate_limit_fallback",
7951
- projectName: path20.basename(projectDir),
8065
+ projectName: path21.basename(projectDir),
7952
8066
  exitCode,
7953
8067
  provider: config.provider
7954
8068
  };
@@ -7976,7 +8090,7 @@ async function sendRunCompletionNotifications(config, projectDir, options, exitC
7976
8090
  const timeoutDuration = event === "run_timeout" ? config.maxRuntime : void 0;
7977
8091
  const _ctx = {
7978
8092
  event,
7979
- projectName: path20.basename(projectDir),
8093
+ projectName: path21.basename(projectDir),
7980
8094
  exitCode,
7981
8095
  provider: config.provider,
7982
8096
  prdName: scriptResult?.data.prd,
@@ -8138,20 +8252,20 @@ function applyCliOverrides(config, options) {
8138
8252
  return overridden;
8139
8253
  }
8140
8254
  function scanPrdDirectory(projectDir, prdDir, maxRuntime) {
8141
- const absolutePrdDir = path20.join(projectDir, prdDir);
8142
- const doneDir = path20.join(absolutePrdDir, "done");
8255
+ const absolutePrdDir = path21.join(projectDir, prdDir);
8256
+ const doneDir = path21.join(absolutePrdDir, "done");
8143
8257
  const pending = [];
8144
8258
  const completed = [];
8145
- if (fs20.existsSync(absolutePrdDir)) {
8146
- const entries = fs20.readdirSync(absolutePrdDir, { withFileTypes: true });
8259
+ if (fs21.existsSync(absolutePrdDir)) {
8260
+ const entries = fs21.readdirSync(absolutePrdDir, { withFileTypes: true });
8147
8261
  for (const entry of entries) {
8148
8262
  if (entry.isFile() && entry.name.endsWith(".md")) {
8149
- const claimPath = path20.join(absolutePrdDir, entry.name + CLAIM_FILE_EXTENSION);
8263
+ const claimPath = path21.join(absolutePrdDir, entry.name + CLAIM_FILE_EXTENSION);
8150
8264
  let claimed = false;
8151
8265
  let claimInfo = null;
8152
- if (fs20.existsSync(claimPath)) {
8266
+ if (fs21.existsSync(claimPath)) {
8153
8267
  try {
8154
- const content = fs20.readFileSync(claimPath, "utf-8");
8268
+ const content = fs21.readFileSync(claimPath, "utf-8");
8155
8269
  const data = JSON.parse(content);
8156
8270
  const age = Math.floor(Date.now() / 1e3) - data.timestamp;
8157
8271
  if (age < maxRuntime) {
@@ -8165,8 +8279,8 @@ function scanPrdDirectory(projectDir, prdDir, maxRuntime) {
8165
8279
  }
8166
8280
  }
8167
8281
  }
8168
- if (fs20.existsSync(doneDir)) {
8169
- const entries = fs20.readdirSync(doneDir, { withFileTypes: true });
8282
+ if (fs21.existsSync(doneDir)) {
8283
+ const entries = fs21.readdirSync(doneDir, { withFileTypes: true });
8170
8284
  for (const entry of entries) {
8171
8285
  if (entry.isFile() && entry.name.endsWith(".md")) {
8172
8286
  completed.push(entry.name);
@@ -8326,7 +8440,7 @@ ${stderr}`);
8326
8440
  // src/commands/review.ts
8327
8441
  init_dist();
8328
8442
  import { execFileSync as execFileSync5 } from "child_process";
8329
- import * as path21 from "path";
8443
+ import * as path22 from "path";
8330
8444
  function shouldSendReviewNotification(scriptStatus) {
8331
8445
  if (!scriptStatus) {
8332
8446
  return true;
@@ -8566,7 +8680,7 @@ ${stderr}`);
8566
8680
  const finalScore = parseFinalReviewScore(scriptResult?.data.final_score);
8567
8681
  const _reviewCtx = {
8568
8682
  event: "review_completed",
8569
- projectName: path21.basename(projectDir),
8683
+ projectName: path22.basename(projectDir),
8570
8684
  exitCode,
8571
8685
  provider: formatProviderDisplay(envVars.NW_PROVIDER_CMD, envVars.NW_PROVIDER_LABEL),
8572
8686
  prUrl: prDetails?.url,
@@ -8587,7 +8701,7 @@ ${stderr}`);
8587
8701
  const autoMergedPrDetails = fetchPrDetailsByNumber(autoMergedPrNumber, projectDir);
8588
8702
  const _mergeCtx = {
8589
8703
  event: "pr_auto_merged",
8590
- projectName: path21.basename(projectDir),
8704
+ projectName: path22.basename(projectDir),
8591
8705
  exitCode,
8592
8706
  provider: formatProviderDisplay(envVars.NW_PROVIDER_CMD, envVars.NW_PROVIDER_LABEL),
8593
8707
  prNumber: autoMergedPrDetails?.number ?? autoMergedPrNumber,
@@ -8612,7 +8726,7 @@ ${stderr}`);
8612
8726
 
8613
8727
  // src/commands/qa.ts
8614
8728
  init_dist();
8615
- import * as path22 from "path";
8729
+ import * as path23 from "path";
8616
8730
  function shouldSendQaNotification(scriptStatus) {
8617
8731
  if (!scriptStatus) {
8618
8732
  return true;
@@ -8746,7 +8860,7 @@ ${stderr}`);
8746
8860
  const qaScreenshotUrls = primaryQaPr !== void 0 ? fetchQaScreenshotUrlsForPr(primaryQaPr, projectDir, repo) : [];
8747
8861
  const _qaCtx = {
8748
8862
  event: "qa_completed",
8749
- projectName: path22.basename(projectDir),
8863
+ projectName: path23.basename(projectDir),
8750
8864
  exitCode,
8751
8865
  provider: formatProviderDisplay(envVars.NW_PROVIDER_CMD, envVars.NW_PROVIDER_LABEL),
8752
8866
  prNumber: prDetails?.number ?? primaryQaPr,
@@ -8772,8 +8886,8 @@ ${stderr}`);
8772
8886
 
8773
8887
  // src/commands/audit.ts
8774
8888
  init_dist();
8775
- import * as fs21 from "fs";
8776
- import * as path23 from "path";
8889
+ import * as fs22 from "fs";
8890
+ import * as path24 from "path";
8777
8891
  function buildEnvVars4(config, options) {
8778
8892
  const env = buildBaseEnvVars(config, "audit", options.dryRun);
8779
8893
  env.NW_AUDIT_MAX_RUNTIME = String(config.audit.maxRuntime);
@@ -8816,7 +8930,7 @@ function auditCommand(program2) {
8816
8930
  configTable.push(["Provider", auditProvider]);
8817
8931
  configTable.push(["Provider CLI", PROVIDER_COMMANDS[auditProvider]]);
8818
8932
  configTable.push(["Max Runtime", `${config.audit.maxRuntime}s`]);
8819
- configTable.push(["Report File", path23.join(projectDir, "logs", "audit-report.md")]);
8933
+ configTable.push(["Report File", path24.join(projectDir, "logs", "audit-report.md")]);
8820
8934
  console.log(configTable.toString());
8821
8935
  header("Provider Invocation");
8822
8936
  const providerCmd = PROVIDER_COMMANDS[auditProvider];
@@ -8851,8 +8965,8 @@ ${stderr}`);
8851
8965
  } else if (scriptResult?.status?.startsWith("skip_")) {
8852
8966
  spinner.succeed("Code audit skipped");
8853
8967
  } else {
8854
- const reportPath = path23.join(projectDir, "logs", "audit-report.md");
8855
- if (!fs21.existsSync(reportPath)) {
8968
+ const reportPath = path24.join(projectDir, "logs", "audit-report.md");
8969
+ if (!fs22.existsSync(reportPath)) {
8856
8970
  spinner.fail("Code audit finished without a report file");
8857
8971
  process.exit(1);
8858
8972
  }
@@ -8863,9 +8977,9 @@ ${stderr}`);
8863
8977
  const providerExit = scriptResult?.data?.provider_exit;
8864
8978
  const exitDetail = providerExit && providerExit !== String(exitCode) ? `, provider exit ${providerExit}` : "";
8865
8979
  spinner.fail(`Code audit exited with code ${exitCode}${statusSuffix}${exitDetail}`);
8866
- const logPath = path23.join(projectDir, "logs", "audit.log");
8867
- if (fs21.existsSync(logPath)) {
8868
- const logLines = fs21.readFileSync(logPath, "utf-8").split("\n").filter((l) => l.trim()).slice(-8);
8980
+ const logPath = path24.join(projectDir, "logs", "audit.log");
8981
+ if (fs22.existsSync(logPath)) {
8982
+ const logLines = fs22.readFileSync(logPath, "utf-8").split("\n").filter((l) => l.trim()).slice(-8);
8869
8983
  if (logLines.length > 0) {
8870
8984
  process.stderr.write(logLines.join("\n") + "\n");
8871
8985
  }
@@ -8943,16 +9057,16 @@ function analyticsCommand(program2) {
8943
9057
  // src/commands/install.ts
8944
9058
  init_dist();
8945
9059
  import { execSync as execSync4 } from "child_process";
8946
- import * as path24 from "path";
8947
- import * as fs22 from "fs";
9060
+ import * as path25 from "path";
9061
+ import * as fs23 from "fs";
8948
9062
  function shellQuote(value) {
8949
9063
  return `'${value.replace(/'/g, `'"'"'`)}'`;
8950
9064
  }
8951
9065
  function getNightWatchBinPath() {
8952
9066
  try {
8953
9067
  const npmBin = execSync4("npm bin -g", { encoding: "utf-8" }).trim();
8954
- const binPath = path24.join(npmBin, "night-watch");
8955
- if (fs22.existsSync(binPath)) {
9068
+ const binPath = path25.join(npmBin, "night-watch");
9069
+ if (fs23.existsSync(binPath)) {
8956
9070
  return binPath;
8957
9071
  }
8958
9072
  } catch {
@@ -8965,17 +9079,17 @@ function getNightWatchBinPath() {
8965
9079
  }
8966
9080
  function getNodeBinDir() {
8967
9081
  if (process.execPath && process.execPath !== "node") {
8968
- return path24.dirname(process.execPath);
9082
+ return path25.dirname(process.execPath);
8969
9083
  }
8970
9084
  try {
8971
9085
  const nodePath = execSync4("which node", { encoding: "utf-8" }).trim();
8972
- return path24.dirname(nodePath);
9086
+ return path25.dirname(nodePath);
8973
9087
  } catch {
8974
9088
  return "";
8975
9089
  }
8976
9090
  }
8977
9091
  function buildCronPathPrefix(nodeBinDir, nightWatchBin) {
8978
- const nightWatchBinDir = nightWatchBin.includes("/") || nightWatchBin.includes("\\") ? path24.dirname(nightWatchBin) : "";
9092
+ const nightWatchBinDir = nightWatchBin.includes("/") || nightWatchBin.includes("\\") ? path25.dirname(nightWatchBin) : "";
8979
9093
  const pathParts = Array.from(
8980
9094
  new Set([nodeBinDir, nightWatchBinDir].filter((part) => part.length > 0))
8981
9095
  );
@@ -8991,12 +9105,12 @@ function performInstall(projectDir, config, options) {
8991
9105
  const nightWatchBin = getNightWatchBinPath();
8992
9106
  const projectName = getProjectName(projectDir);
8993
9107
  const marker = generateMarker(projectName);
8994
- const logDir = path24.join(projectDir, LOG_DIR);
8995
- if (!fs22.existsSync(logDir)) {
8996
- fs22.mkdirSync(logDir, { recursive: true });
9108
+ const logDir = path25.join(projectDir, LOG_DIR);
9109
+ if (!fs23.existsSync(logDir)) {
9110
+ fs23.mkdirSync(logDir, { recursive: true });
8997
9111
  }
8998
- const executorLog = path24.join(logDir, "executor.log");
8999
- const reviewerLog = path24.join(logDir, "reviewer.log");
9112
+ const executorLog = path25.join(logDir, "executor.log");
9113
+ const reviewerLog = path25.join(logDir, "reviewer.log");
9000
9114
  if (!options?.force) {
9001
9115
  const existingEntries2 = Array.from(
9002
9116
  /* @__PURE__ */ new Set([...getEntries(marker), ...getProjectEntries(projectDir)])
@@ -9033,7 +9147,7 @@ function performInstall(projectDir, config, options) {
9033
9147
  const installSlicer = options?.noSlicer === true ? false : config.roadmapScanner.enabled;
9034
9148
  if (installSlicer) {
9035
9149
  const slicerSchedule = config.roadmapScanner.slicerSchedule;
9036
- const slicerLog = path24.join(logDir, "slicer.log");
9150
+ const slicerLog = path25.join(logDir, "slicer.log");
9037
9151
  const slicerEntry = `${slicerSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} planner >> ${shellQuote(slicerLog)} 2>&1 ${marker}`;
9038
9152
  entries.push(slicerEntry);
9039
9153
  }
@@ -9041,7 +9155,7 @@ function performInstall(projectDir, config, options) {
9041
9155
  const installQa = disableQa ? false : config.qa.enabled;
9042
9156
  if (installQa) {
9043
9157
  const qaSchedule = config.qa.schedule;
9044
- const qaLog = path24.join(logDir, "qa.log");
9158
+ const qaLog = path25.join(logDir, "qa.log");
9045
9159
  const qaEntry = `${qaSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} qa >> ${shellQuote(qaLog)} 2>&1 ${marker}`;
9046
9160
  entries.push(qaEntry);
9047
9161
  }
@@ -9049,7 +9163,7 @@ function performInstall(projectDir, config, options) {
9049
9163
  const installAudit = disableAudit ? false : config.audit.enabled;
9050
9164
  if (installAudit) {
9051
9165
  const auditSchedule = config.audit.schedule;
9052
- const auditLog = path24.join(logDir, "audit.log");
9166
+ const auditLog = path25.join(logDir, "audit.log");
9053
9167
  const auditEntry = `${auditSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} audit >> ${shellQuote(auditLog)} 2>&1 ${marker}`;
9054
9168
  entries.push(auditEntry);
9055
9169
  }
@@ -9057,7 +9171,7 @@ function performInstall(projectDir, config, options) {
9057
9171
  const installAnalytics = disableAnalytics ? false : config.analytics.enabled;
9058
9172
  if (installAnalytics) {
9059
9173
  const analyticsSchedule = config.analytics.schedule;
9060
- const analyticsLog = path24.join(logDir, "analytics.log");
9174
+ const analyticsLog = path25.join(logDir, "analytics.log");
9061
9175
  const analyticsEntry = `${analyticsSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} analytics >> ${shellQuote(analyticsLog)} 2>&1 ${marker}`;
9062
9176
  entries.push(analyticsEntry);
9063
9177
  }
@@ -9088,12 +9202,12 @@ function installCommand(program2) {
9088
9202
  const nightWatchBin = getNightWatchBinPath();
9089
9203
  const projectName = getProjectName(projectDir);
9090
9204
  const marker = generateMarker(projectName);
9091
- const logDir = path24.join(projectDir, LOG_DIR);
9092
- if (!fs22.existsSync(logDir)) {
9093
- fs22.mkdirSync(logDir, { recursive: true });
9205
+ const logDir = path25.join(projectDir, LOG_DIR);
9206
+ if (!fs23.existsSync(logDir)) {
9207
+ fs23.mkdirSync(logDir, { recursive: true });
9094
9208
  }
9095
- const executorLog = path24.join(logDir, "executor.log");
9096
- const reviewerLog = path24.join(logDir, "reviewer.log");
9209
+ const executorLog = path25.join(logDir, "executor.log");
9210
+ const reviewerLog = path25.join(logDir, "reviewer.log");
9097
9211
  const existingEntries = Array.from(
9098
9212
  /* @__PURE__ */ new Set([...getEntries(marker), ...getProjectEntries(projectDir)])
9099
9213
  );
@@ -9129,7 +9243,7 @@ function installCommand(program2) {
9129
9243
  const installSlicer = options.noSlicer === true ? false : config.roadmapScanner.enabled;
9130
9244
  let slicerLog;
9131
9245
  if (installSlicer) {
9132
- slicerLog = path24.join(logDir, "slicer.log");
9246
+ slicerLog = path25.join(logDir, "slicer.log");
9133
9247
  const slicerSchedule = config.roadmapScanner.slicerSchedule;
9134
9248
  const slicerEntry = `${slicerSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} planner >> ${shellQuote(slicerLog)} 2>&1 ${marker}`;
9135
9249
  entries.push(slicerEntry);
@@ -9138,7 +9252,7 @@ function installCommand(program2) {
9138
9252
  const installQa = disableQa ? false : config.qa.enabled;
9139
9253
  let qaLog;
9140
9254
  if (installQa) {
9141
- qaLog = path24.join(logDir, "qa.log");
9255
+ qaLog = path25.join(logDir, "qa.log");
9142
9256
  const qaSchedule = config.qa.schedule;
9143
9257
  const qaEntry = `${qaSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} qa >> ${shellQuote(qaLog)} 2>&1 ${marker}`;
9144
9258
  entries.push(qaEntry);
@@ -9147,7 +9261,7 @@ function installCommand(program2) {
9147
9261
  const installAudit = disableAudit ? false : config.audit.enabled;
9148
9262
  let auditLog;
9149
9263
  if (installAudit) {
9150
- auditLog = path24.join(logDir, "audit.log");
9264
+ auditLog = path25.join(logDir, "audit.log");
9151
9265
  const auditSchedule = config.audit.schedule;
9152
9266
  const auditEntry = `${auditSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} audit >> ${shellQuote(auditLog)} 2>&1 ${marker}`;
9153
9267
  entries.push(auditEntry);
@@ -9156,7 +9270,7 @@ function installCommand(program2) {
9156
9270
  const installAnalytics = disableAnalytics ? false : config.analytics.enabled;
9157
9271
  let analyticsLog;
9158
9272
  if (installAnalytics) {
9159
- analyticsLog = path24.join(logDir, "analytics.log");
9273
+ analyticsLog = path25.join(logDir, "analytics.log");
9160
9274
  const analyticsSchedule = config.analytics.schedule;
9161
9275
  const analyticsEntry = `${analyticsSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} analytics >> ${shellQuote(analyticsLog)} 2>&1 ${marker}`;
9162
9276
  entries.push(analyticsEntry);
@@ -9204,8 +9318,8 @@ function installCommand(program2) {
9204
9318
 
9205
9319
  // src/commands/uninstall.ts
9206
9320
  init_dist();
9207
- import * as path25 from "path";
9208
- import * as fs23 from "fs";
9321
+ import * as path26 from "path";
9322
+ import * as fs24 from "fs";
9209
9323
  function performUninstall(projectDir, options) {
9210
9324
  try {
9211
9325
  const projectName = getProjectName(projectDir);
@@ -9220,19 +9334,19 @@ function performUninstall(projectDir, options) {
9220
9334
  const removedCount = removeEntriesForProject(projectDir, marker);
9221
9335
  unregisterProject(projectDir);
9222
9336
  if (!options?.keepLogs) {
9223
- const logDir = path25.join(projectDir, "logs");
9224
- if (fs23.existsSync(logDir)) {
9337
+ const logDir = path26.join(projectDir, "logs");
9338
+ if (fs24.existsSync(logDir)) {
9225
9339
  const logFiles = ["executor.log", "reviewer.log", "slicer.log", "audit.log"];
9226
9340
  logFiles.forEach((logFile) => {
9227
- const logPath = path25.join(logDir, logFile);
9228
- if (fs23.existsSync(logPath)) {
9229
- fs23.unlinkSync(logPath);
9341
+ const logPath = path26.join(logDir, logFile);
9342
+ if (fs24.existsSync(logPath)) {
9343
+ fs24.unlinkSync(logPath);
9230
9344
  }
9231
9345
  });
9232
9346
  try {
9233
- const remainingFiles = fs23.readdirSync(logDir);
9347
+ const remainingFiles = fs24.readdirSync(logDir);
9234
9348
  if (remainingFiles.length === 0) {
9235
- fs23.rmdirSync(logDir);
9349
+ fs24.rmdirSync(logDir);
9236
9350
  }
9237
9351
  } catch {
9238
9352
  }
@@ -9265,21 +9379,21 @@ function uninstallCommand(program2) {
9265
9379
  existingEntries.forEach((entry) => dim(` ${entry}`));
9266
9380
  const removedCount = removeEntriesForProject(projectDir, marker);
9267
9381
  if (!options.keepLogs) {
9268
- const logDir = path25.join(projectDir, "logs");
9269
- if (fs23.existsSync(logDir)) {
9382
+ const logDir = path26.join(projectDir, "logs");
9383
+ if (fs24.existsSync(logDir)) {
9270
9384
  const logFiles = ["executor.log", "reviewer.log", "slicer.log", "audit.log"];
9271
9385
  let logsRemoved = 0;
9272
9386
  logFiles.forEach((logFile) => {
9273
- const logPath = path25.join(logDir, logFile);
9274
- if (fs23.existsSync(logPath)) {
9275
- fs23.unlinkSync(logPath);
9387
+ const logPath = path26.join(logDir, logFile);
9388
+ if (fs24.existsSync(logPath)) {
9389
+ fs24.unlinkSync(logPath);
9276
9390
  logsRemoved++;
9277
9391
  }
9278
9392
  });
9279
9393
  try {
9280
- const remainingFiles = fs23.readdirSync(logDir);
9394
+ const remainingFiles = fs24.readdirSync(logDir);
9281
9395
  if (remainingFiles.length === 0) {
9282
- fs23.rmdirSync(logDir);
9396
+ fs24.rmdirSync(logDir);
9283
9397
  }
9284
9398
  } catch {
9285
9399
  }
@@ -9515,14 +9629,14 @@ function statusCommand(program2) {
9515
9629
  // src/commands/logs.ts
9516
9630
  init_dist();
9517
9631
  import { spawn as spawn3 } from "child_process";
9518
- import * as path26 from "path";
9519
- import * as fs24 from "fs";
9632
+ import * as path27 from "path";
9633
+ import * as fs25 from "fs";
9520
9634
  function getLastLines(filePath, lineCount) {
9521
- if (!fs24.existsSync(filePath)) {
9635
+ if (!fs25.existsSync(filePath)) {
9522
9636
  return `Log file not found: ${filePath}`;
9523
9637
  }
9524
9638
  try {
9525
- const content = fs24.readFileSync(filePath, "utf-8");
9639
+ const content = fs25.readFileSync(filePath, "utf-8");
9526
9640
  const lines = content.trim().split("\n");
9527
9641
  return lines.slice(-lineCount).join("\n");
9528
9642
  } catch (error2) {
@@ -9530,7 +9644,7 @@ function getLastLines(filePath, lineCount) {
9530
9644
  }
9531
9645
  }
9532
9646
  function followLog(filePath) {
9533
- if (!fs24.existsSync(filePath)) {
9647
+ if (!fs25.existsSync(filePath)) {
9534
9648
  console.log(`Log file not found: ${filePath}`);
9535
9649
  console.log("The log file will be created when the first execution runs.");
9536
9650
  return;
@@ -9550,13 +9664,13 @@ function logsCommand(program2) {
9550
9664
  program2.command("logs").description("View night-watch log output").option("-n, --lines <count>", "Number of lines to show", "50").option("-f, --follow", "Follow log output (tail -f)").option("-t, --type <type>", "Log type to view (executor|reviewer|qa|audit|planner|all)", "all").action(async (options) => {
9551
9665
  try {
9552
9666
  const projectDir = process.cwd();
9553
- const logDir = path26.join(projectDir, LOG_DIR);
9667
+ const logDir = path27.join(projectDir, LOG_DIR);
9554
9668
  const lineCount = parseInt(options.lines || "50", 10);
9555
- const executorLog = path26.join(logDir, EXECUTOR_LOG_FILE);
9556
- const reviewerLog = path26.join(logDir, REVIEWER_LOG_FILE);
9557
- const qaLog = path26.join(logDir, `${QA_LOG_NAME}.log`);
9558
- const auditLog = path26.join(logDir, `${AUDIT_LOG_NAME}.log`);
9559
- const plannerLog = path26.join(logDir, `${PLANNER_LOG_NAME}.log`);
9669
+ const executorLog = path27.join(logDir, EXECUTOR_LOG_FILE);
9670
+ const reviewerLog = path27.join(logDir, REVIEWER_LOG_FILE);
9671
+ const qaLog = path27.join(logDir, `${QA_LOG_NAME}.log`);
9672
+ const auditLog = path27.join(logDir, `${AUDIT_LOG_NAME}.log`);
9673
+ const plannerLog = path27.join(logDir, `${PLANNER_LOG_NAME}.log`);
9560
9674
  const logType = options.type?.toLowerCase() || "all";
9561
9675
  const showExecutor = logType === "all" || logType === "run" || logType === "executor";
9562
9676
  const showReviewer = logType === "all" || logType === "review" || logType === "reviewer";
@@ -9620,15 +9734,15 @@ function logsCommand(program2) {
9620
9734
 
9621
9735
  // src/commands/prd.ts
9622
9736
  init_dist();
9623
- import * as fs25 from "fs";
9624
- import * as path27 from "path";
9737
+ import * as fs26 from "fs";
9738
+ import * as path28 from "path";
9625
9739
  import * as readline2 from "readline";
9626
9740
  function slugify2(name) {
9627
9741
  return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
9628
9742
  }
9629
9743
  function getNextPrdNumber2(prdDir) {
9630
- if (!fs25.existsSync(prdDir)) return 1;
9631
- const files = fs25.readdirSync(prdDir).filter((f) => f.endsWith(".md"));
9744
+ if (!fs26.existsSync(prdDir)) return 1;
9745
+ const files = fs26.readdirSync(prdDir).filter((f) => f.endsWith(".md"));
9632
9746
  const numbers = files.map((f) => {
9633
9747
  const match = f.match(/^(\d+)-/);
9634
9748
  return match ? parseInt(match[1], 10) : 0;
@@ -9649,10 +9763,10 @@ function parseDependencies(content) {
9649
9763
  }
9650
9764
  function isClaimActive(claimPath, maxRuntime) {
9651
9765
  try {
9652
- if (!fs25.existsSync(claimPath)) {
9766
+ if (!fs26.existsSync(claimPath)) {
9653
9767
  return { active: false };
9654
9768
  }
9655
- const content = fs25.readFileSync(claimPath, "utf-8");
9769
+ const content = fs26.readFileSync(claimPath, "utf-8");
9656
9770
  const claim = JSON.parse(content);
9657
9771
  const age = Math.floor(Date.now() / 1e3) - claim.timestamp;
9658
9772
  if (age < maxRuntime) {
@@ -9668,9 +9782,9 @@ function prdCommand(program2) {
9668
9782
  prd.command("create").description("Generate a new PRD markdown file from template").argument("<name>", "PRD name (used for title and filename)").option("-i, --interactive", "Prompt for complexity, dependencies, and phase count", false).option("-t, --template <path>", "Path to a custom template file").option("--deps <files>", "Comma-separated dependency filenames").option("--phases <count>", "Number of execution phases", "3").option("--no-number", "Skip auto-numbering prefix").action(async (name, options) => {
9669
9783
  const projectDir = process.cwd();
9670
9784
  const config = loadConfig(projectDir);
9671
- const prdDir = path27.join(projectDir, config.prdDir);
9672
- if (!fs25.existsSync(prdDir)) {
9673
- fs25.mkdirSync(prdDir, { recursive: true });
9785
+ const prdDir = path28.join(projectDir, config.prdDir);
9786
+ if (!fs26.existsSync(prdDir)) {
9787
+ fs26.mkdirSync(prdDir, { recursive: true });
9674
9788
  }
9675
9789
  let complexityScore = 5;
9676
9790
  let dependsOn = [];
@@ -9729,20 +9843,20 @@ function prdCommand(program2) {
9729
9843
  } else {
9730
9844
  filename = `${slug}.md`;
9731
9845
  }
9732
- const filePath = path27.join(prdDir, filename);
9733
- if (fs25.existsSync(filePath)) {
9846
+ const filePath = path28.join(prdDir, filename);
9847
+ if (fs26.existsSync(filePath)) {
9734
9848
  error(`File already exists: ${filePath}`);
9735
9849
  dim("Use a different name or remove the existing file.");
9736
9850
  process.exit(1);
9737
9851
  }
9738
9852
  let customTemplate;
9739
9853
  if (options.template) {
9740
- const templatePath = path27.resolve(options.template);
9741
- if (!fs25.existsSync(templatePath)) {
9854
+ const templatePath = path28.resolve(options.template);
9855
+ if (!fs26.existsSync(templatePath)) {
9742
9856
  error(`Template file not found: ${templatePath}`);
9743
9857
  process.exit(1);
9744
9858
  }
9745
- customTemplate = fs25.readFileSync(templatePath, "utf-8");
9859
+ customTemplate = fs26.readFileSync(templatePath, "utf-8");
9746
9860
  }
9747
9861
  const vars = {
9748
9862
  title: name,
@@ -9753,7 +9867,7 @@ function prdCommand(program2) {
9753
9867
  phaseCount
9754
9868
  };
9755
9869
  const content = renderPrdTemplate(vars, customTemplate);
9756
- fs25.writeFileSync(filePath, content, "utf-8");
9870
+ fs26.writeFileSync(filePath, content, "utf-8");
9757
9871
  header("PRD Created");
9758
9872
  success(`Created: ${filePath}`);
9759
9873
  info(`Title: ${name}`);
@@ -9765,15 +9879,15 @@ function prdCommand(program2) {
9765
9879
  prd.command("list").description("List all PRDs with status").option("--json", "Output as JSON").action(async (options) => {
9766
9880
  const projectDir = process.cwd();
9767
9881
  const config = loadConfig(projectDir);
9768
- const absolutePrdDir = path27.join(projectDir, config.prdDir);
9769
- const doneDir = path27.join(absolutePrdDir, "done");
9882
+ const absolutePrdDir = path28.join(projectDir, config.prdDir);
9883
+ const doneDir = path28.join(absolutePrdDir, "done");
9770
9884
  const pending = [];
9771
- if (fs25.existsSync(absolutePrdDir)) {
9772
- const files = fs25.readdirSync(absolutePrdDir).filter((f) => f.endsWith(".md"));
9885
+ if (fs26.existsSync(absolutePrdDir)) {
9886
+ const files = fs26.readdirSync(absolutePrdDir).filter((f) => f.endsWith(".md"));
9773
9887
  for (const file of files) {
9774
- const content = fs25.readFileSync(path27.join(absolutePrdDir, file), "utf-8");
9888
+ const content = fs26.readFileSync(path28.join(absolutePrdDir, file), "utf-8");
9775
9889
  const deps = parseDependencies(content);
9776
- const claimPath = path27.join(absolutePrdDir, file + CLAIM_FILE_EXTENSION);
9890
+ const claimPath = path28.join(absolutePrdDir, file + CLAIM_FILE_EXTENSION);
9777
9891
  const claimStatus = isClaimActive(claimPath, config.maxRuntime);
9778
9892
  pending.push({
9779
9893
  name: file,
@@ -9784,10 +9898,10 @@ function prdCommand(program2) {
9784
9898
  }
9785
9899
  }
9786
9900
  const done = [];
9787
- if (fs25.existsSync(doneDir)) {
9788
- const files = fs25.readdirSync(doneDir).filter((f) => f.endsWith(".md"));
9901
+ if (fs26.existsSync(doneDir)) {
9902
+ const files = fs26.readdirSync(doneDir).filter((f) => f.endsWith(".md"));
9789
9903
  for (const file of files) {
9790
- const content = fs25.readFileSync(path27.join(doneDir, file), "utf-8");
9904
+ const content = fs26.readFileSync(path28.join(doneDir, file), "utf-8");
9791
9905
  const deps = parseDependencies(content);
9792
9906
  done.push({ name: file, dependencies: deps });
9793
9907
  }
@@ -9826,7 +9940,7 @@ import blessed6 from "blessed";
9826
9940
  // src/commands/dashboard/tab-status.ts
9827
9941
  init_dist();
9828
9942
  import blessed from "blessed";
9829
- import * as fs26 from "fs";
9943
+ import * as fs27 from "fs";
9830
9944
  function sortPrdsByPriority(prds, priority) {
9831
9945
  if (priority.length === 0) return prds;
9832
9946
  const priorityMap = /* @__PURE__ */ new Map();
@@ -9922,7 +10036,7 @@ function renderLogPane(projectDir, logs) {
9922
10036
  let newestMtime = 0;
9923
10037
  for (const log of existingLogs) {
9924
10038
  try {
9925
- const stat = fs26.statSync(log.path);
10039
+ const stat = fs27.statSync(log.path);
9926
10040
  if (stat.mtimeMs > newestMtime) {
9927
10041
  newestMtime = stat.mtimeMs;
9928
10042
  newestLog = log;
@@ -11577,8 +11691,8 @@ function createActionsTab() {
11577
11691
  // src/commands/dashboard/tab-logs.ts
11578
11692
  init_dist();
11579
11693
  import blessed5 from "blessed";
11580
- import * as fs27 from "fs";
11581
- import * as path28 from "path";
11694
+ import * as fs28 from "fs";
11695
+ import * as path29 from "path";
11582
11696
  var LOG_NAMES = ["executor", "reviewer"];
11583
11697
  var LOG_LINES = 200;
11584
11698
  function createLogsTab() {
@@ -11619,7 +11733,7 @@ function createLogsTab() {
11619
11733
  let activeKeyHandlers = [];
11620
11734
  let activeCtx = null;
11621
11735
  function getLogPath(projectDir, logName) {
11622
- return path28.join(projectDir, "logs", `${logName}.log`);
11736
+ return path29.join(projectDir, "logs", `${logName}.log`);
11623
11737
  }
11624
11738
  function updateSelector() {
11625
11739
  const tabs = LOG_NAMES.map((name, idx) => {
@@ -11633,7 +11747,7 @@ function createLogsTab() {
11633
11747
  function loadLog(ctx) {
11634
11748
  const logName = LOG_NAMES[selectedLogIndex];
11635
11749
  const logPath = getLogPath(ctx.projectDir, logName);
11636
- if (!fs27.existsSync(logPath)) {
11750
+ if (!fs28.existsSync(logPath)) {
11637
11751
  logContent.setContent(
11638
11752
  `{yellow-fg}No ${logName}.log file found{/yellow-fg}
11639
11753
 
@@ -11643,7 +11757,7 @@ Log will appear here once the ${logName} runs.`
11643
11757
  return;
11644
11758
  }
11645
11759
  try {
11646
- const stat = fs27.statSync(logPath);
11760
+ const stat = fs28.statSync(logPath);
11647
11761
  const sizeKB = (stat.size / 1024).toFixed(1);
11648
11762
  logContent.setLabel(`[ ${logName}.log - ${sizeKB} KB ]`);
11649
11763
  } catch {
@@ -12149,13 +12263,13 @@ function doctorCommand(program2) {
12149
12263
 
12150
12264
  // src/commands/serve.ts
12151
12265
  init_dist();
12152
- import * as fs32 from "fs";
12266
+ import * as fs33 from "fs";
12153
12267
 
12154
12268
  // ../server/dist/index.js
12155
12269
  init_dist();
12156
- import * as fs31 from "fs";
12157
- import * as path34 from "path";
12158
- import { dirname as dirname7 } from "path";
12270
+ import * as fs32 from "fs";
12271
+ import * as path35 from "path";
12272
+ import { dirname as dirname8 } from "path";
12159
12273
  import { fileURLToPath as fileURLToPath3 } from "url";
12160
12274
  import cors from "cors";
12161
12275
  import express from "express";
@@ -12239,8 +12353,8 @@ function setupGracefulShutdown(server, beforeClose) {
12239
12353
 
12240
12354
  // ../server/dist/middleware/project-resolver.middleware.js
12241
12355
  init_dist();
12242
- import * as fs28 from "fs";
12243
- import * as path29 from "path";
12356
+ import * as fs29 from "fs";
12357
+ import * as path30 from "path";
12244
12358
  function resolveProject(req, res, next) {
12245
12359
  const projectId = req.params.projectId;
12246
12360
  const decodedId = decodeURIComponent(projectId).replace(/~/g, "/");
@@ -12250,7 +12364,7 @@ function resolveProject(req, res, next) {
12250
12364
  res.status(404).json({ error: `Project not found: ${decodedId}` });
12251
12365
  return;
12252
12366
  }
12253
- if (!fs28.existsSync(entry.path) || !fs28.existsSync(path29.join(entry.path, CONFIG_FILE_NAME))) {
12367
+ if (!fs29.existsSync(entry.path) || !fs29.existsSync(path30.join(entry.path, CONFIG_FILE_NAME))) {
12254
12368
  res.status(404).json({ error: `Project path invalid or missing config: ${entry.path}` });
12255
12369
  return;
12256
12370
  }
@@ -12295,8 +12409,8 @@ function startSseStatusWatcher(clients, projectDir, getConfig) {
12295
12409
 
12296
12410
  // ../server/dist/routes/action.routes.js
12297
12411
  init_dist();
12298
- import * as fs29 from "fs";
12299
- import * as path30 from "path";
12412
+ import * as fs30 from "fs";
12413
+ import * as path31 from "path";
12300
12414
  import { execSync as execSync5, spawn as spawn5 } from "child_process";
12301
12415
  import { Router } from "express";
12302
12416
 
@@ -12334,17 +12448,17 @@ function getBoardProvider(config, projectDir) {
12334
12448
  function cleanOrphanedClaims(dir) {
12335
12449
  let entries;
12336
12450
  try {
12337
- entries = fs29.readdirSync(dir, { withFileTypes: true });
12451
+ entries = fs30.readdirSync(dir, { withFileTypes: true });
12338
12452
  } catch {
12339
12453
  return;
12340
12454
  }
12341
12455
  for (const entry of entries) {
12342
- const fullPath = path30.join(dir, entry.name);
12456
+ const fullPath = path31.join(dir, entry.name);
12343
12457
  if (entry.isDirectory() && entry.name !== "done") {
12344
12458
  cleanOrphanedClaims(fullPath);
12345
12459
  } else if (entry.name.endsWith(CLAIM_FILE_EXTENSION)) {
12346
12460
  try {
12347
- fs29.unlinkSync(fullPath);
12461
+ fs30.unlinkSync(fullPath);
12348
12462
  } catch {
12349
12463
  }
12350
12464
  }
@@ -12501,19 +12615,19 @@ function createActionRouteHandlers(ctx) {
12501
12615
  res.status(400).json({ error: "Invalid PRD name" });
12502
12616
  return;
12503
12617
  }
12504
- const prdDir = path30.join(projectDir, config.prdDir);
12618
+ const prdDir = path31.join(projectDir, config.prdDir);
12505
12619
  const normalized = prdName.endsWith(".md") ? prdName : `${prdName}.md`;
12506
- const pendingPath = path30.join(prdDir, normalized);
12507
- const donePath = path30.join(prdDir, "done", normalized);
12508
- if (fs29.existsSync(pendingPath)) {
12620
+ const pendingPath = path31.join(prdDir, normalized);
12621
+ const donePath = path31.join(prdDir, "done", normalized);
12622
+ if (fs30.existsSync(pendingPath)) {
12509
12623
  res.json({ message: `"${normalized}" is already pending` });
12510
12624
  return;
12511
12625
  }
12512
- if (!fs29.existsSync(donePath)) {
12626
+ if (!fs30.existsSync(donePath)) {
12513
12627
  res.status(404).json({ error: `PRD "${normalized}" not found in done/` });
12514
12628
  return;
12515
12629
  }
12516
- fs29.renameSync(donePath, pendingPath);
12630
+ fs30.renameSync(donePath, pendingPath);
12517
12631
  res.json({ message: `Moved "${normalized}" back to pending` });
12518
12632
  } catch (error2) {
12519
12633
  res.status(500).json({
@@ -12531,11 +12645,11 @@ function createActionRouteHandlers(ctx) {
12531
12645
  res.status(409).json({ error: "Executor is actively running \u2014 use Stop instead" });
12532
12646
  return;
12533
12647
  }
12534
- if (fs29.existsSync(lockPath)) {
12535
- fs29.unlinkSync(lockPath);
12648
+ if (fs30.existsSync(lockPath)) {
12649
+ fs30.unlinkSync(lockPath);
12536
12650
  }
12537
- const prdDir = path30.join(projectDir, config.prdDir);
12538
- if (fs29.existsSync(prdDir)) {
12651
+ const prdDir = path31.join(projectDir, config.prdDir);
12652
+ if (fs30.existsSync(prdDir)) {
12539
12653
  cleanOrphanedClaims(prdDir);
12540
12654
  }
12541
12655
  broadcastSSE(ctx.getSseClients(req), "status_changed", await fetchStatusSnapshot(projectDir, config));
@@ -13194,6 +13308,26 @@ function createConfigRoutes(deps) {
13194
13308
  });
13195
13309
  return router;
13196
13310
  }
13311
+ function createGlobalNotificationsRoutes() {
13312
+ const router = Router3();
13313
+ router.get("/", (_req, res) => {
13314
+ res.json(loadGlobalNotificationsConfig());
13315
+ });
13316
+ router.put("/", (req, res) => {
13317
+ const { webhook } = req.body;
13318
+ if (webhook !== null && webhook !== void 0) {
13319
+ const issues = validateWebhook(webhook);
13320
+ if (issues.length > 0) {
13321
+ res.status(400).json({ error: `Invalid webhook: ${issues.join(", ")}` });
13322
+ return;
13323
+ }
13324
+ }
13325
+ const config = { webhook: webhook ?? null };
13326
+ saveGlobalNotificationsConfig(config);
13327
+ res.json(loadGlobalNotificationsConfig());
13328
+ });
13329
+ return router;
13330
+ }
13197
13331
  function createProjectConfigRoutes() {
13198
13332
  const router = Router3({ mergeParams: true });
13199
13333
  router.get("/config", (req, res) => {
@@ -13227,8 +13361,8 @@ function createProjectConfigRoutes() {
13227
13361
 
13228
13362
  // ../server/dist/routes/doctor.routes.js
13229
13363
  init_dist();
13230
- import * as fs30 from "fs";
13231
- import * as path31 from "path";
13364
+ import * as fs31 from "fs";
13365
+ import * as path32 from "path";
13232
13366
  import { execSync as execSync6 } from "child_process";
13233
13367
  import { Router as Router4 } from "express";
13234
13368
  function runDoctorChecks(projectDir, config) {
@@ -13261,7 +13395,7 @@ function runDoctorChecks(projectDir, config) {
13261
13395
  });
13262
13396
  }
13263
13397
  try {
13264
- const projectName = path31.basename(projectDir);
13398
+ const projectName = path32.basename(projectDir);
13265
13399
  const marker = generateMarker(projectName);
13266
13400
  const crontabEntries = [...getEntries(marker), ...getProjectEntries(projectDir)];
13267
13401
  if (crontabEntries.length > 0) {
@@ -13284,8 +13418,8 @@ function runDoctorChecks(projectDir, config) {
13284
13418
  detail: "Failed to check crontab"
13285
13419
  });
13286
13420
  }
13287
- const configPath = path31.join(projectDir, CONFIG_FILE_NAME);
13288
- if (fs30.existsSync(configPath)) {
13421
+ const configPath = path32.join(projectDir, CONFIG_FILE_NAME);
13422
+ if (fs31.existsSync(configPath)) {
13289
13423
  checks.push({ name: "config", status: "pass", detail: "Config file exists" });
13290
13424
  } else {
13291
13425
  checks.push({
@@ -13294,9 +13428,9 @@ function runDoctorChecks(projectDir, config) {
13294
13428
  detail: "Config file not found (using defaults)"
13295
13429
  });
13296
13430
  }
13297
- const prdDir = path31.join(projectDir, config.prdDir);
13298
- if (fs30.existsSync(prdDir)) {
13299
- const prds = fs30.readdirSync(prdDir).filter((f) => f.endsWith(".md"));
13431
+ const prdDir = path32.join(projectDir, config.prdDir);
13432
+ if (fs31.existsSync(prdDir)) {
13433
+ const prds = fs31.readdirSync(prdDir).filter((f) => f.endsWith(".md"));
13300
13434
  checks.push({
13301
13435
  name: "prdDir",
13302
13436
  status: "pass",
@@ -13339,7 +13473,7 @@ function createProjectDoctorRoutes() {
13339
13473
 
13340
13474
  // ../server/dist/routes/log.routes.js
13341
13475
  init_dist();
13342
- import * as path32 from "path";
13476
+ import * as path33 from "path";
13343
13477
  import { Router as Router5 } from "express";
13344
13478
  function createLogRoutes(deps) {
13345
13479
  const { projectDir } = deps;
@@ -13358,7 +13492,7 @@ function createLogRoutes(deps) {
13358
13492
  const lines = typeof linesParam === "string" ? parseInt(linesParam, 10) : 200;
13359
13493
  const linesToRead = isNaN(lines) || lines < 1 ? 200 : Math.min(lines, 1e4);
13360
13494
  const fileName = LOG_FILE_NAMES[name] || name;
13361
- const logPath = path32.join(projectDir, LOG_DIR, `${fileName}.log`);
13495
+ const logPath = path33.join(projectDir, LOG_DIR, `${fileName}.log`);
13362
13496
  const logLines = getLastLogLines(logPath, linesToRead);
13363
13497
  res.json({ name, lines: logLines });
13364
13498
  } catch (error2) {
@@ -13384,7 +13518,7 @@ function createProjectLogRoutes() {
13384
13518
  const lines = typeof linesParam === "string" ? parseInt(linesParam, 10) : 200;
13385
13519
  const linesToRead = isNaN(lines) || lines < 1 ? 200 : Math.min(lines, 1e4);
13386
13520
  const fileName = LOG_FILE_NAMES[name] || name;
13387
- const logPath = path32.join(projectDir, LOG_DIR, `${fileName}.log`);
13521
+ const logPath = path33.join(projectDir, LOG_DIR, `${fileName}.log`);
13388
13522
  const logLines = getLastLogLines(logPath, linesToRead);
13389
13523
  res.json({ name, lines: logLines });
13390
13524
  } catch (error2) {
@@ -13419,7 +13553,7 @@ function createProjectPrdRoutes() {
13419
13553
 
13420
13554
  // ../server/dist/routes/roadmap.routes.js
13421
13555
  init_dist();
13422
- import * as path33 from "path";
13556
+ import * as path34 from "path";
13423
13557
  import { Router as Router7 } from "express";
13424
13558
  function createRoadmapRouteHandlers(ctx) {
13425
13559
  const router = Router7({ mergeParams: true });
@@ -13429,7 +13563,7 @@ function createRoadmapRouteHandlers(ctx) {
13429
13563
  const config = ctx.getConfig(req);
13430
13564
  const projectDir = ctx.getProjectDir(req);
13431
13565
  const status = getRoadmapStatus(projectDir, config);
13432
- const prdDir = path33.join(projectDir, config.prdDir);
13566
+ const prdDir = path34.join(projectDir, config.prdDir);
13433
13567
  const state = loadRoadmapState(prdDir);
13434
13568
  res.json({
13435
13569
  ...status,
@@ -13761,26 +13895,26 @@ function createQueueRoutes(deps) {
13761
13895
 
13762
13896
  // ../server/dist/index.js
13763
13897
  var __filename2 = fileURLToPath3(import.meta.url);
13764
- var __dirname3 = dirname7(__filename2);
13898
+ var __dirname3 = dirname8(__filename2);
13765
13899
  function resolveWebDistPath() {
13766
- const bundled = path34.join(__dirname3, "web");
13767
- if (fs31.existsSync(path34.join(bundled, "index.html")))
13900
+ const bundled = path35.join(__dirname3, "web");
13901
+ if (fs32.existsSync(path35.join(bundled, "index.html")))
13768
13902
  return bundled;
13769
13903
  let d = __dirname3;
13770
13904
  for (let i = 0; i < 8; i++) {
13771
- if (fs31.existsSync(path34.join(d, "turbo.json"))) {
13772
- const dev = path34.join(d, "web/dist");
13773
- if (fs31.existsSync(path34.join(dev, "index.html")))
13905
+ if (fs32.existsSync(path35.join(d, "turbo.json"))) {
13906
+ const dev = path35.join(d, "web/dist");
13907
+ if (fs32.existsSync(path35.join(dev, "index.html")))
13774
13908
  return dev;
13775
13909
  break;
13776
13910
  }
13777
- d = dirname7(d);
13911
+ d = dirname8(d);
13778
13912
  }
13779
13913
  return bundled;
13780
13914
  }
13781
13915
  function setupStaticFiles(app) {
13782
13916
  const webDistPath = resolveWebDistPath();
13783
- if (fs31.existsSync(webDistPath)) {
13917
+ if (fs32.existsSync(webDistPath)) {
13784
13918
  app.use(express.static(webDistPath));
13785
13919
  }
13786
13920
  app.use((req, res, next) => {
@@ -13788,8 +13922,8 @@ function setupStaticFiles(app) {
13788
13922
  next();
13789
13923
  return;
13790
13924
  }
13791
- const indexPath = path34.resolve(webDistPath, "index.html");
13792
- if (fs31.existsSync(indexPath)) {
13925
+ const indexPath = path35.resolve(webDistPath, "index.html");
13926
+ if (fs32.existsSync(indexPath)) {
13793
13927
  res.sendFile(indexPath, (err) => {
13794
13928
  if (err)
13795
13929
  next();
@@ -13827,6 +13961,7 @@ function createApp(projectDir) {
13827
13961
  app.use("/api/logs", createLogRoutes({ projectDir }));
13828
13962
  app.use("/api/doctor", createDoctorRoutes({ projectDir, getConfig: () => config }));
13829
13963
  app.use("/api/queue", createQueueRoutes({ getConfig: () => config }));
13964
+ app.use("/api/global-notifications", createGlobalNotificationsRoutes());
13830
13965
  app.get("/api/prs", async (_req, res) => {
13831
13966
  try {
13832
13967
  res.json(await collectPrInfo(projectDir, config.branchPatterns));
@@ -13899,13 +14034,14 @@ function createGlobalApp() {
13899
14034
  }
13900
14035
  });
13901
14036
  app.use("/api/queue", createGlobalQueueRoutes());
14037
+ app.use("/api/global-notifications", createGlobalNotificationsRoutes());
13902
14038
  app.use("/api/projects/:projectId", resolveProject, createProjectRouter());
13903
14039
  setupStaticFiles(app);
13904
14040
  app.use(errorHandler);
13905
14041
  return app;
13906
14042
  }
13907
14043
  function bootContainer() {
13908
- initContainer(path34.dirname(getDbPath()));
14044
+ initContainer(path35.dirname(getDbPath()));
13909
14045
  }
13910
14046
  function startServer(projectDir, port) {
13911
14047
  bootContainer();
@@ -13958,8 +14094,8 @@ function isProcessRunning2(pid) {
13958
14094
  }
13959
14095
  function readPid(lockPath) {
13960
14096
  try {
13961
- if (!fs32.existsSync(lockPath)) return null;
13962
- const raw = fs32.readFileSync(lockPath, "utf-8").trim();
14097
+ if (!fs33.existsSync(lockPath)) return null;
14098
+ const raw = fs33.readFileSync(lockPath, "utf-8").trim();
13963
14099
  const pid = parseInt(raw, 10);
13964
14100
  return Number.isFinite(pid) ? pid : null;
13965
14101
  } catch {
@@ -13971,10 +14107,10 @@ function acquireServeLock(mode, port) {
13971
14107
  let stalePidCleaned;
13972
14108
  for (let attempt = 0; attempt < 2; attempt++) {
13973
14109
  try {
13974
- const fd = fs32.openSync(lockPath, "wx");
13975
- fs32.writeFileSync(fd, `${process.pid}
14110
+ const fd = fs33.openSync(lockPath, "wx");
14111
+ fs33.writeFileSync(fd, `${process.pid}
13976
14112
  `);
13977
- fs32.closeSync(fd);
14113
+ fs33.closeSync(fd);
13978
14114
  return { acquired: true, lockPath, stalePidCleaned };
13979
14115
  } catch (error2) {
13980
14116
  const err = error2;
@@ -13995,7 +14131,7 @@ function acquireServeLock(mode, port) {
13995
14131
  };
13996
14132
  }
13997
14133
  try {
13998
- fs32.unlinkSync(lockPath);
14134
+ fs33.unlinkSync(lockPath);
13999
14135
  if (existingPid) {
14000
14136
  stalePidCleaned = existingPid;
14001
14137
  }
@@ -14018,10 +14154,10 @@ function acquireServeLock(mode, port) {
14018
14154
  }
14019
14155
  function releaseServeLock(lockPath) {
14020
14156
  try {
14021
- if (!fs32.existsSync(lockPath)) return;
14157
+ if (!fs33.existsSync(lockPath)) return;
14022
14158
  const lockPid = readPid(lockPath);
14023
14159
  if (lockPid !== null && lockPid !== process.pid) return;
14024
- fs32.unlinkSync(lockPath);
14160
+ fs33.unlinkSync(lockPath);
14025
14161
  } catch {
14026
14162
  }
14027
14163
  }
@@ -14117,14 +14253,14 @@ function historyCommand(program2) {
14117
14253
  // src/commands/update.ts
14118
14254
  init_dist();
14119
14255
  import { spawnSync } from "child_process";
14120
- import * as fs33 from "fs";
14121
- import * as path35 from "path";
14256
+ import * as fs34 from "fs";
14257
+ import * as path36 from "path";
14122
14258
  var DEFAULT_GLOBAL_SPEC = "@jonit-dev/night-watch-cli@latest";
14123
14259
  function parseProjectDirs(projects, cwd) {
14124
14260
  if (!projects || projects.trim().length === 0) {
14125
14261
  return [cwd];
14126
14262
  }
14127
- const dirs = projects.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0).map((entry) => path35.resolve(cwd, entry));
14263
+ const dirs = projects.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0).map((entry) => path36.resolve(cwd, entry));
14128
14264
  return Array.from(new Set(dirs));
14129
14265
  }
14130
14266
  function shouldInstallGlobal(options) {
@@ -14166,7 +14302,7 @@ function updateCommand(program2) {
14166
14302
  }
14167
14303
  const nightWatchBin = resolveNightWatchBin();
14168
14304
  for (const projectDir of projectDirs) {
14169
- if (!fs33.existsSync(projectDir) || !fs33.statSync(projectDir).isDirectory()) {
14305
+ if (!fs34.existsSync(projectDir) || !fs34.statSync(projectDir).isDirectory()) {
14170
14306
  warn(`Skipping invalid project directory: ${projectDir}`);
14171
14307
  continue;
14172
14308
  }
@@ -14210,8 +14346,8 @@ function prdStateCommand(program2) {
14210
14346
 
14211
14347
  // src/commands/retry.ts
14212
14348
  init_dist();
14213
- import * as fs34 from "fs";
14214
- import * as path36 from "path";
14349
+ import * as fs35 from "fs";
14350
+ import * as path37 from "path";
14215
14351
  function normalizePrdName(name) {
14216
14352
  if (!name.endsWith(".md")) {
14217
14353
  return `${name}.md`;
@@ -14219,26 +14355,26 @@ function normalizePrdName(name) {
14219
14355
  return name;
14220
14356
  }
14221
14357
  function getDonePrds(doneDir) {
14222
- if (!fs34.existsSync(doneDir)) {
14358
+ if (!fs35.existsSync(doneDir)) {
14223
14359
  return [];
14224
14360
  }
14225
- return fs34.readdirSync(doneDir).filter((f) => f.endsWith(".md"));
14361
+ return fs35.readdirSync(doneDir).filter((f) => f.endsWith(".md"));
14226
14362
  }
14227
14363
  function retryCommand(program2) {
14228
14364
  program2.command("retry <prdName>").description("Move a completed PRD from done/ back to pending").action((prdName) => {
14229
14365
  const projectDir = process.cwd();
14230
14366
  const config = loadConfig(projectDir);
14231
- const prdDir = path36.join(projectDir, config.prdDir);
14232
- const doneDir = path36.join(prdDir, "done");
14367
+ const prdDir = path37.join(projectDir, config.prdDir);
14368
+ const doneDir = path37.join(prdDir, "done");
14233
14369
  const normalizedPrdName = normalizePrdName(prdName);
14234
- const pendingPath = path36.join(prdDir, normalizedPrdName);
14235
- if (fs34.existsSync(pendingPath)) {
14370
+ const pendingPath = path37.join(prdDir, normalizedPrdName);
14371
+ if (fs35.existsSync(pendingPath)) {
14236
14372
  info(`"${normalizedPrdName}" is already pending, nothing to retry.`);
14237
14373
  return;
14238
14374
  }
14239
- const donePath = path36.join(doneDir, normalizedPrdName);
14240
- if (fs34.existsSync(donePath)) {
14241
- fs34.renameSync(donePath, pendingPath);
14375
+ const donePath = path37.join(doneDir, normalizedPrdName);
14376
+ if (fs35.existsSync(donePath)) {
14377
+ fs35.renameSync(donePath, pendingPath);
14242
14378
  success(`Moved "${normalizedPrdName}" back to pending.`);
14243
14379
  dim(`From: ${donePath}`);
14244
14380
  dim(`To: ${pendingPath}`);
@@ -14490,7 +14626,7 @@ function prdsCommand(program2) {
14490
14626
 
14491
14627
  // src/commands/cancel.ts
14492
14628
  init_dist();
14493
- import * as fs35 from "fs";
14629
+ import * as fs36 from "fs";
14494
14630
  import * as readline3 from "readline";
14495
14631
  function getLockFilePaths2(projectDir) {
14496
14632
  const runtimeKey = projectRuntimeKey(projectDir);
@@ -14537,7 +14673,7 @@ async function cancelProcess2(processType, lockPath, force = false) {
14537
14673
  const pid = lockStatus.pid;
14538
14674
  if (!lockStatus.running) {
14539
14675
  try {
14540
- fs35.unlinkSync(lockPath);
14676
+ fs36.unlinkSync(lockPath);
14541
14677
  return {
14542
14678
  success: true,
14543
14679
  message: `${processType} is not running (cleaned up stale lock file for PID ${pid})`,
@@ -14575,7 +14711,7 @@ async function cancelProcess2(processType, lockPath, force = false) {
14575
14711
  await sleep2(3e3);
14576
14712
  if (!isProcessRunning3(pid)) {
14577
14713
  try {
14578
- fs35.unlinkSync(lockPath);
14714
+ fs36.unlinkSync(lockPath);
14579
14715
  } catch {
14580
14716
  }
14581
14717
  return {
@@ -14610,7 +14746,7 @@ async function cancelProcess2(processType, lockPath, force = false) {
14610
14746
  await sleep2(500);
14611
14747
  if (!isProcessRunning3(pid)) {
14612
14748
  try {
14613
- fs35.unlinkSync(lockPath);
14749
+ fs36.unlinkSync(lockPath);
14614
14750
  } catch {
14615
14751
  }
14616
14752
  return {
@@ -14671,31 +14807,31 @@ function cancelCommand(program2) {
14671
14807
 
14672
14808
  // src/commands/slice.ts
14673
14809
  init_dist();
14674
- import * as fs36 from "fs";
14675
- import * as path37 from "path";
14810
+ import * as fs37 from "fs";
14811
+ import * as path38 from "path";
14676
14812
  function plannerLockPath2(projectDir) {
14677
14813
  return `${LOCK_FILE_PREFIX}slicer-${projectRuntimeKey(projectDir)}.lock`;
14678
14814
  }
14679
14815
  function acquirePlannerLock(projectDir) {
14680
14816
  const lockFile = plannerLockPath2(projectDir);
14681
- if (fs36.existsSync(lockFile)) {
14682
- const pidRaw = fs36.readFileSync(lockFile, "utf-8").trim();
14817
+ if (fs37.existsSync(lockFile)) {
14818
+ const pidRaw = fs37.readFileSync(lockFile, "utf-8").trim();
14683
14819
  const pid = parseInt(pidRaw, 10);
14684
14820
  if (!Number.isNaN(pid) && isProcessRunning(pid)) {
14685
14821
  return { acquired: false, lockFile, pid };
14686
14822
  }
14687
14823
  try {
14688
- fs36.unlinkSync(lockFile);
14824
+ fs37.unlinkSync(lockFile);
14689
14825
  } catch {
14690
14826
  }
14691
14827
  }
14692
- fs36.writeFileSync(lockFile, String(process.pid));
14828
+ fs37.writeFileSync(lockFile, String(process.pid));
14693
14829
  return { acquired: true, lockFile };
14694
14830
  }
14695
14831
  function releasePlannerLock(lockFile) {
14696
14832
  try {
14697
- if (fs36.existsSync(lockFile)) {
14698
- fs36.unlinkSync(lockFile);
14833
+ if (fs37.existsSync(lockFile)) {
14834
+ fs37.unlinkSync(lockFile);
14699
14835
  }
14700
14836
  } catch {
14701
14837
  }
@@ -14704,12 +14840,12 @@ function resolvePlannerIssueColumn(config) {
14704
14840
  return config.roadmapScanner.issueColumn === "Ready" ? "Ready" : "Draft";
14705
14841
  }
14706
14842
  function buildPlannerIssueBody(projectDir, config, result) {
14707
- const relativePrdPath = path37.join(config.prdDir, result.file ?? "").replace(/\\/g, "/");
14708
- const absolutePrdPath = path37.join(projectDir, config.prdDir, result.file ?? "");
14843
+ const relativePrdPath = path38.join(config.prdDir, result.file ?? "").replace(/\\/g, "/");
14844
+ const absolutePrdPath = path38.join(projectDir, config.prdDir, result.file ?? "");
14709
14845
  const sourceItem = result.item;
14710
14846
  let prdContent;
14711
14847
  try {
14712
- prdContent = fs36.readFileSync(absolutePrdPath, "utf-8");
14848
+ prdContent = fs37.readFileSync(absolutePrdPath, "utf-8");
14713
14849
  } catch {
14714
14850
  prdContent = `Unable to read generated PRD file at \`${relativePrdPath}\`.`;
14715
14851
  }
@@ -14885,7 +15021,7 @@ function sliceCommand(program2) {
14885
15021
  if (!options.dryRun) {
14886
15022
  await sendNotifications(config, {
14887
15023
  event: "run_started",
14888
- projectName: path37.basename(projectDir),
15024
+ projectName: path38.basename(projectDir),
14889
15025
  exitCode: 0,
14890
15026
  provider: config.provider
14891
15027
  });
@@ -14920,7 +15056,7 @@ function sliceCommand(program2) {
14920
15056
  if (!options.dryRun && result.sliced) {
14921
15057
  await sendNotifications(config, {
14922
15058
  event: "run_succeeded",
14923
- projectName: path37.basename(projectDir),
15059
+ projectName: path38.basename(projectDir),
14924
15060
  exitCode,
14925
15061
  provider: config.provider,
14926
15062
  prTitle: result.item?.title
@@ -14928,7 +15064,7 @@ function sliceCommand(program2) {
14928
15064
  } else if (!options.dryRun && !nothingPending) {
14929
15065
  await sendNotifications(config, {
14930
15066
  event: "run_failed",
14931
- projectName: path37.basename(projectDir),
15067
+ projectName: path38.basename(projectDir),
14932
15068
  exitCode,
14933
15069
  provider: config.provider
14934
15070
  });
@@ -14944,21 +15080,21 @@ function sliceCommand(program2) {
14944
15080
 
14945
15081
  // src/commands/state.ts
14946
15082
  init_dist();
14947
- import * as os8 from "os";
14948
- import * as path38 from "path";
15083
+ import * as os9 from "os";
15084
+ import * as path39 from "path";
14949
15085
  import chalk5 from "chalk";
14950
15086
  import { Command } from "commander";
14951
15087
  function createStateCommand() {
14952
15088
  const state = new Command("state");
14953
15089
  state.description("Manage Night Watch state");
14954
15090
  state.command("migrate").description("Migrate legacy JSON state files to SQLite").option("--dry-run", "Show what would be migrated without making changes").action((opts) => {
14955
- const nightWatchHome = process.env.NIGHT_WATCH_HOME || path38.join(os8.homedir(), GLOBAL_CONFIG_DIR);
15091
+ const nightWatchHome = process.env.NIGHT_WATCH_HOME || path39.join(os9.homedir(), GLOBAL_CONFIG_DIR);
14956
15092
  if (opts.dryRun) {
14957
15093
  console.log(chalk5.cyan("Dry-run mode: no changes will be made.\n"));
14958
15094
  console.log(`Legacy JSON files that would be migrated from: ${chalk5.bold(nightWatchHome)}`);
14959
- console.log(` ${path38.join(nightWatchHome, "projects.json")}`);
14960
- console.log(` ${path38.join(nightWatchHome, "history.json")}`);
14961
- console.log(` ${path38.join(nightWatchHome, "prd-states.json")}`);
15095
+ console.log(` ${path39.join(nightWatchHome, "projects.json")}`);
15096
+ console.log(` ${path39.join(nightWatchHome, "history.json")}`);
15097
+ console.log(` ${path39.join(nightWatchHome, "prd-states.json")}`);
14962
15098
  console.log(` <project>/<prdDir>/.roadmap-state.json (per project)`);
14963
15099
  console.log(chalk5.dim("\nRun without --dry-run to apply the migration."));
14964
15100
  return;
@@ -14996,8 +15132,8 @@ function createStateCommand() {
14996
15132
  init_dist();
14997
15133
  init_dist();
14998
15134
  import { execFileSync as execFileSync6 } from "child_process";
14999
- import * as fs37 from "fs";
15000
- import * as path39 from "path";
15135
+ import * as fs38 from "fs";
15136
+ import * as path40 from "path";
15001
15137
  import * as readline4 from "readline";
15002
15138
  import chalk6 from "chalk";
15003
15139
  async function run(fn) {
@@ -15019,7 +15155,7 @@ function getProvider(config, cwd) {
15019
15155
  return createBoardProvider(bp, cwd);
15020
15156
  }
15021
15157
  function defaultBoardTitle(cwd) {
15022
- return `${path39.basename(cwd)} Night Watch`;
15158
+ return `${path40.basename(cwd)} Night Watch`;
15023
15159
  }
15024
15160
  async function ensureBoardConfigured(config, cwd, provider, options) {
15025
15161
  if (config.boardProvider?.projectNumber) {
@@ -15218,11 +15354,11 @@ function boardCommand(program2) {
15218
15354
  let body = options.body ?? "";
15219
15355
  if (options.bodyFile) {
15220
15356
  const filePath = options.bodyFile;
15221
- if (!fs37.existsSync(filePath)) {
15357
+ if (!fs38.existsSync(filePath)) {
15222
15358
  console.error(`File not found: ${filePath}`);
15223
15359
  process.exit(1);
15224
15360
  }
15225
- body = fs37.readFileSync(filePath, "utf-8");
15361
+ body = fs38.readFileSync(filePath, "utf-8");
15226
15362
  }
15227
15363
  const labels = [];
15228
15364
  if (options.label) {
@@ -15250,6 +15386,25 @@ function boardCommand(program2) {
15250
15386
  }
15251
15387
  })
15252
15388
  );
15389
+ board.command("add-issue").description("Add an existing GitHub issue to the board").argument("<number>", "Issue number").option("--column <name>", "Target column (default: Ready)", "Ready").action(
15390
+ async (number, options) => run(async () => {
15391
+ const cwd = process.cwd();
15392
+ const config = loadConfig(cwd);
15393
+ const provider = getProvider(config, cwd);
15394
+ await ensureBoardConfigured(config, cwd, provider);
15395
+ if (!BOARD_COLUMNS.includes(options.column)) {
15396
+ console.error(
15397
+ `Invalid column "${options.column}". Valid columns: ${BOARD_COLUMNS.join(", ")}`
15398
+ );
15399
+ process.exit(1);
15400
+ }
15401
+ const issue = await provider.addIssue(
15402
+ parseInt(number, 10),
15403
+ options.column
15404
+ );
15405
+ success(`Added issue #${issue.number} "${issue.title}" to ${options.column}`);
15406
+ })
15407
+ );
15253
15408
  board.command("status").description("Show the current state of all issues grouped by column").option("--json", "Output raw JSON").option("--group-by <field>", "Group by: priority, category, or column (default: column)").action(
15254
15409
  async (options) => run(async () => {
15255
15410
  const cwd = process.cwd();
@@ -15444,12 +15599,12 @@ function boardCommand(program2) {
15444
15599
  const config = loadConfig(cwd);
15445
15600
  const provider = getProvider(config, cwd);
15446
15601
  await ensureBoardConfigured(config, cwd, provider);
15447
- const roadmapPath = options.roadmap ?? path39.join(cwd, "ROADMAP.md");
15448
- if (!fs37.existsSync(roadmapPath)) {
15602
+ const roadmapPath = options.roadmap ?? path40.join(cwd, "ROADMAP.md");
15603
+ if (!fs38.existsSync(roadmapPath)) {
15449
15604
  console.error(`Roadmap file not found: ${roadmapPath}`);
15450
15605
  process.exit(1);
15451
15606
  }
15452
- const roadmapContent = fs37.readFileSync(roadmapPath, "utf-8");
15607
+ const roadmapContent = fs38.readFileSync(roadmapPath, "utf-8");
15453
15608
  const items = parseRoadmap(roadmapContent);
15454
15609
  const uncheckedItems = getUncheckedItems(items);
15455
15610
  if (uncheckedItems.length === 0) {
@@ -15573,7 +15728,7 @@ function boardCommand(program2) {
15573
15728
  // src/commands/queue.ts
15574
15729
  init_dist();
15575
15730
  init_dist();
15576
- import * as path40 from "path";
15731
+ import * as path41 from "path";
15577
15732
  import { spawn as spawn6 } from "child_process";
15578
15733
  import chalk7 from "chalk";
15579
15734
  import { Command as Command2 } from "commander";
@@ -15693,7 +15848,7 @@ function createQueueCommand() {
15693
15848
  process.exit(1);
15694
15849
  }
15695
15850
  }
15696
- const projectName = path40.basename(projectDir);
15851
+ const projectName = path41.basename(projectDir);
15697
15852
  const queueConfig = loadConfig(projectDir).queue;
15698
15853
  const id = enqueueJob(projectDir, projectName, jobType, envVars, queueConfig);
15699
15854
  console.log(chalk7.green(`Enqueued ${jobType} for ${projectName} (ID: ${id})`));
@@ -15849,17 +16004,17 @@ function notifyCommand(program2) {
15849
16004
 
15850
16005
  // src/cli.ts
15851
16006
  var __filename3 = fileURLToPath4(import.meta.url);
15852
- var __dirname4 = dirname8(__filename3);
16007
+ var __dirname4 = dirname9(__filename3);
15853
16008
  function findPackageRoot(dir) {
15854
16009
  let d = dir;
15855
16010
  for (let i = 0; i < 5; i++) {
15856
- if (existsSync29(join35(d, "package.json"))) return d;
15857
- d = dirname8(d);
16011
+ if (existsSync30(join36(d, "package.json"))) return d;
16012
+ d = dirname9(d);
15858
16013
  }
15859
16014
  return dir;
15860
16015
  }
15861
16016
  var packageRoot = findPackageRoot(__dirname4);
15862
- var packageJson = JSON.parse(readFileSync18(join35(packageRoot, "package.json"), "utf-8"));
16017
+ var packageJson = JSON.parse(readFileSync19(join36(packageRoot, "package.json"), "utf-8"));
15863
16018
  var program = new Command3();
15864
16019
  program.name("night-watch").description("Autonomous PRD execution using Claude CLI + cron").version(packageJson.version);
15865
16020
  initCommand(program);