@jonit-dev/night-watch-cli 1.8.12-beta.0 → 1.8.12-beta.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (154) hide show
  1. package/dist/cli.d.ts +3 -0
  2. package/dist/cli.js +3281 -651
  3. package/dist/cli.js.map +1 -0
  4. package/dist/commands/agent.d.ts +12 -0
  5. package/dist/commands/agent.d.ts.map +1 -0
  6. package/dist/commands/agent.js +307 -0
  7. package/dist/commands/agent.js.map +1 -0
  8. package/dist/commands/analytics.d.ts +14 -0
  9. package/dist/commands/analytics.d.ts.map +1 -1
  10. package/dist/commands/analytics.js +129 -0
  11. package/dist/commands/analytics.js.map +1 -0
  12. package/dist/commands/audit.d.ts +19 -0
  13. package/dist/commands/audit.d.ts.map +1 -1
  14. package/dist/commands/audit.js +189 -0
  15. package/dist/commands/audit.js.map +1 -0
  16. package/dist/commands/board.d.ts +9 -0
  17. package/dist/commands/board.js +702 -0
  18. package/dist/commands/board.js.map +1 -0
  19. package/dist/commands/cancel.d.ts +46 -0
  20. package/dist/commands/cancel.js +239 -0
  21. package/dist/commands/cancel.js.map +1 -0
  22. package/dist/commands/cron.d.ts +8 -0
  23. package/dist/commands/cron.js +134 -0
  24. package/dist/commands/cron.js.map +1 -0
  25. package/dist/commands/dashboard/tab-actions.d.ts +10 -0
  26. package/dist/commands/dashboard/tab-actions.js +247 -0
  27. package/dist/commands/dashboard/tab-actions.js.map +1 -0
  28. package/dist/commands/dashboard/tab-config.d.ts +21 -0
  29. package/dist/commands/dashboard/tab-config.js +874 -0
  30. package/dist/commands/dashboard/tab-config.js.map +1 -0
  31. package/dist/commands/dashboard/tab-logs.d.ts +10 -0
  32. package/dist/commands/dashboard/tab-logs.js +202 -0
  33. package/dist/commands/dashboard/tab-logs.js.map +1 -0
  34. package/dist/commands/dashboard/tab-schedules.d.ts +21 -0
  35. package/dist/commands/dashboard/tab-schedules.js +320 -0
  36. package/dist/commands/dashboard/tab-schedules.js.map +1 -0
  37. package/dist/commands/dashboard/tab-status.d.ts +32 -0
  38. package/dist/commands/dashboard/tab-status.js +424 -0
  39. package/dist/commands/dashboard/tab-status.js.map +1 -0
  40. package/dist/commands/dashboard/types.d.ts +42 -0
  41. package/dist/commands/dashboard/types.js +5 -0
  42. package/dist/commands/dashboard/types.js.map +1 -0
  43. package/dist/commands/dashboard.d.ts +11 -0
  44. package/dist/commands/dashboard.js +242 -0
  45. package/dist/commands/dashboard.js.map +1 -0
  46. package/dist/commands/doctor.d.ts +16 -0
  47. package/dist/commands/doctor.js +195 -0
  48. package/dist/commands/doctor.js.map +1 -0
  49. package/dist/commands/history.d.ts +7 -0
  50. package/dist/commands/history.js +49 -0
  51. package/dist/commands/history.js.map +1 -0
  52. package/dist/commands/init.d.ts +45 -0
  53. package/dist/commands/init.d.ts.map +1 -1
  54. package/dist/commands/init.js +13 -0
  55. package/dist/commands/init.js.map +1 -1
  56. package/dist/commands/install.d.ts +65 -0
  57. package/dist/commands/install.js +405 -0
  58. package/dist/commands/install.js.map +1 -0
  59. package/dist/commands/logs.d.ts +15 -0
  60. package/dist/commands/logs.js +155 -0
  61. package/dist/commands/logs.js.map +1 -0
  62. package/dist/commands/merge.d.ts +26 -0
  63. package/dist/commands/merge.d.ts.map +1 -1
  64. package/dist/commands/merge.js +186 -0
  65. package/dist/commands/merge.js.map +1 -0
  66. package/dist/commands/notify.d.ts +7 -0
  67. package/dist/commands/notify.js +43 -0
  68. package/dist/commands/notify.js.map +1 -0
  69. package/dist/commands/plan.d.ts +19 -0
  70. package/dist/commands/plan.d.ts.map +1 -1
  71. package/dist/commands/plan.js +133 -0
  72. package/dist/commands/plan.js.map +1 -0
  73. package/dist/commands/prd-state.d.ts +12 -0
  74. package/dist/commands/prd-state.js +47 -0
  75. package/dist/commands/prd-state.js.map +1 -0
  76. package/dist/commands/prd.d.ts +18 -0
  77. package/dist/commands/prd.js +363 -0
  78. package/dist/commands/prd.js.map +1 -0
  79. package/dist/commands/prds.d.ts +13 -0
  80. package/dist/commands/prds.js +194 -0
  81. package/dist/commands/prds.js.map +1 -0
  82. package/dist/commands/prs.d.ts +14 -0
  83. package/dist/commands/prs.js +104 -0
  84. package/dist/commands/prs.js.map +1 -0
  85. package/dist/commands/qa.d.ts +34 -0
  86. package/dist/commands/qa.d.ts.map +1 -1
  87. package/dist/commands/qa.js +238 -0
  88. package/dist/commands/qa.js.map +1 -0
  89. package/dist/commands/queue.d.ts +8 -0
  90. package/dist/commands/queue.d.ts.map +1 -1
  91. package/dist/commands/queue.js +398 -0
  92. package/dist/commands/queue.js.map +1 -0
  93. package/dist/commands/resolve.d.ts +26 -0
  94. package/dist/commands/resolve.d.ts.map +1 -1
  95. package/dist/commands/resolve.js +212 -0
  96. package/dist/commands/resolve.js.map +1 -0
  97. package/dist/commands/retry.d.ts +9 -0
  98. package/dist/commands/retry.js +71 -0
  99. package/dist/commands/retry.js.map +1 -0
  100. package/dist/commands/review.d.ts +79 -0
  101. package/dist/commands/review.d.ts.map +1 -1
  102. package/dist/commands/review.js +47 -75
  103. package/dist/commands/review.js.map +1 -1
  104. package/dist/commands/run.d.ts +88 -0
  105. package/dist/commands/run.d.ts.map +1 -1
  106. package/dist/commands/run.js +85 -1
  107. package/dist/commands/run.js.map +1 -1
  108. package/dist/commands/serve.d.ts +19 -0
  109. package/dist/commands/serve.js +142 -0
  110. package/dist/commands/serve.js.map +1 -0
  111. package/dist/commands/shared/env-builder.d.ts +49 -0
  112. package/dist/commands/shared/env-builder.d.ts.map +1 -1
  113. package/dist/commands/shared/env-builder.js +152 -0
  114. package/dist/commands/shared/env-builder.js.map +1 -0
  115. package/dist/commands/shared/feedback.d.ts +24 -0
  116. package/dist/commands/shared/feedback.d.ts.map +1 -0
  117. package/dist/commands/shared/feedback.js +38 -0
  118. package/dist/commands/shared/feedback.js.map +1 -0
  119. package/dist/commands/slice.d.ts +35 -0
  120. package/dist/commands/slice.d.ts.map +1 -1
  121. package/dist/commands/slice.js +363 -0
  122. package/dist/commands/slice.js.map +1 -0
  123. package/dist/commands/state.d.ts +8 -0
  124. package/dist/commands/state.js +54 -0
  125. package/dist/commands/state.js.map +1 -0
  126. package/dist/commands/status.d.ts +14 -0
  127. package/dist/commands/status.js +297 -0
  128. package/dist/commands/status.js.map +1 -0
  129. package/dist/commands/summary.d.ts +14 -0
  130. package/dist/commands/summary.js +193 -0
  131. package/dist/commands/summary.js.map +1 -0
  132. package/dist/commands/uninstall.d.ts +25 -0
  133. package/dist/commands/uninstall.js +134 -0
  134. package/dist/commands/uninstall.js.map +1 -0
  135. package/dist/commands/update.d.ts +22 -0
  136. package/dist/commands/update.js +90 -0
  137. package/dist/commands/update.js.map +1 -0
  138. package/dist/scripts/night-watch-audit-cron.sh +1 -0
  139. package/dist/scripts/night-watch-cron.sh +186 -23
  140. package/dist/scripts/night-watch-helpers.sh +65 -2
  141. package/dist/scripts/night-watch-merger-cron.sh +137 -36
  142. package/dist/scripts/night-watch-plan-cron.sh +2 -0
  143. package/dist/scripts/night-watch-pr-resolver-cron.sh +6 -0
  144. package/dist/scripts/night-watch-pr-reviewer-cron.sh +22 -5
  145. package/dist/scripts/night-watch-qa-cron.sh +8 -1
  146. package/dist/scripts/night-watch-slicer-cron.sh +3 -0
  147. package/dist/templates/night-watch.config.json +21 -20
  148. package/dist/templates/slicer.md +3 -0
  149. package/dist/web/assets/index-C-xpWpS8.css +1 -0
  150. package/dist/web/assets/index-CEYe-290.js +412 -0
  151. package/dist/web/assets/index-DpvzoXEv.js +442 -0
  152. package/dist/web/assets/index-DyME41HV.css +1 -0
  153. package/dist/web/index.html +2 -2
  154. package/package.json +3 -1
@@ -0,0 +1,134 @@
1
+ /**
2
+ * Uninstall command for Night Watch CLI
3
+ * Removes crontab entries for the current project
4
+ */
5
+ import * as path from 'path';
6
+ import * as fs from 'fs';
7
+ import { dim, generateMarker, getEntries, getProjectEntries, getProjectName, removeEntriesForProject, success, error as uiError, unregisterProject, warn, } from '@night-watch/core';
8
+ /**
9
+ * Core uninstall logic, reusable from dashboard.
10
+ * Returns result without printing to console.
11
+ */
12
+ export function performUninstall(projectDir, options) {
13
+ try {
14
+ const projectName = getProjectName(projectDir);
15
+ const marker = generateMarker(projectName);
16
+ const existingEntries = Array.from(new Set([...getEntries(marker), ...getProjectEntries(projectDir)]));
17
+ if (existingEntries.length === 0) {
18
+ unregisterProject(projectDir);
19
+ return { success: true, removedCount: 0 };
20
+ }
21
+ const removedCount = removeEntriesForProject(projectDir, marker);
22
+ unregisterProject(projectDir);
23
+ if (!options?.keepLogs) {
24
+ const logDir = path.join(projectDir, 'logs');
25
+ if (fs.existsSync(logDir)) {
26
+ const logFiles = [
27
+ 'executor.log',
28
+ 'reviewer.log',
29
+ 'slicer.log',
30
+ 'audit.log',
31
+ 'pr-resolver.log',
32
+ ];
33
+ logFiles.forEach((logFile) => {
34
+ const logPath = path.join(logDir, logFile);
35
+ if (fs.existsSync(logPath)) {
36
+ fs.unlinkSync(logPath);
37
+ }
38
+ });
39
+ try {
40
+ const remainingFiles = fs.readdirSync(logDir);
41
+ if (remainingFiles.length === 0) {
42
+ fs.rmdirSync(logDir);
43
+ }
44
+ }
45
+ catch {
46
+ // Ignore errors removing directory
47
+ }
48
+ }
49
+ }
50
+ return { success: true, removedCount };
51
+ }
52
+ catch (err) {
53
+ return {
54
+ success: false,
55
+ removedCount: 0,
56
+ error: err instanceof Error ? err.message : String(err),
57
+ };
58
+ }
59
+ }
60
+ /**
61
+ * Uninstall crontab entries for night-watch
62
+ */
63
+ export function uninstallCommand(program) {
64
+ program
65
+ .command('uninstall')
66
+ .description('Remove crontab entries')
67
+ .option('--keep-logs', 'Preserve log files')
68
+ .action(async (options) => {
69
+ try {
70
+ // Get project directory
71
+ const projectDir = process.cwd();
72
+ // Get project name
73
+ const projectName = getProjectName(projectDir);
74
+ const marker = generateMarker(projectName);
75
+ // Check if there are entries to remove
76
+ const existingEntries = Array.from(new Set([...getEntries(marker), ...getProjectEntries(projectDir)]));
77
+ if (existingEntries.length === 0) {
78
+ warn(`No Night Watch crontab entries found for ${projectName}.`);
79
+ dim('Nothing to uninstall.');
80
+ return;
81
+ }
82
+ // Show entries that will be removed
83
+ dim(`Removing Night Watch crontab entries for ${projectName}:`);
84
+ existingEntries.forEach((entry) => dim(` ${entry}`));
85
+ // Remove entries
86
+ const removedCount = removeEntriesForProject(projectDir, marker);
87
+ // Handle log files
88
+ if (!options.keepLogs) {
89
+ const logDir = path.join(projectDir, 'logs');
90
+ if (fs.existsSync(logDir)) {
91
+ const logFiles = [
92
+ 'executor.log',
93
+ 'reviewer.log',
94
+ 'slicer.log',
95
+ 'audit.log',
96
+ 'pr-resolver.log',
97
+ ];
98
+ let logsRemoved = 0;
99
+ logFiles.forEach((logFile) => {
100
+ const logPath = path.join(logDir, logFile);
101
+ if (fs.existsSync(logPath)) {
102
+ fs.unlinkSync(logPath);
103
+ logsRemoved++;
104
+ }
105
+ });
106
+ // Try to remove log directory if empty
107
+ try {
108
+ const remainingFiles = fs.readdirSync(logDir);
109
+ if (remainingFiles.length === 0) {
110
+ fs.rmdirSync(logDir);
111
+ }
112
+ }
113
+ catch {
114
+ // Ignore errors removing directory
115
+ }
116
+ if (logsRemoved > 0) {
117
+ console.log();
118
+ dim(`Removed ${logsRemoved} log file(s).`);
119
+ }
120
+ }
121
+ }
122
+ else {
123
+ console.log();
124
+ dim('Log files preserved.');
125
+ }
126
+ success(`Successfully removed ${removedCount} crontab entry/entries.`);
127
+ }
128
+ catch (err) {
129
+ uiError(`Error uninstalling Night Watch: ${err instanceof Error ? err.message : String(err)}`);
130
+ process.exit(1);
131
+ }
132
+ });
133
+ }
134
+ //# sourceMappingURL=uninstall.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"uninstall.js","sourceRoot":"","sources":["../../src/commands/uninstall.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Update command for Night Watch CLI
3
+ * Reinstalls global CLI and refreshes cron entries for one or more projects.
4
+ */
5
+ import { Command } from 'commander';
6
+ export declare const DEFAULT_GLOBAL_SPEC = "@jonit-dev/night-watch-cli@latest";
7
+ export interface IUpdateOptions {
8
+ projects?: string;
9
+ globalSpec: string;
10
+ global?: boolean;
11
+ }
12
+ /**
13
+ * Parse project directories from a comma-separated CLI option.
14
+ * Defaults to current working directory when option is omitted.
15
+ */
16
+ export declare function parseProjectDirs(projects: string | undefined, cwd: string): string[];
17
+ export declare function shouldInstallGlobal(options: Pick<IUpdateOptions, 'global'>): boolean;
18
+ /**
19
+ * Register update command.
20
+ */
21
+ export declare function updateCommand(program: Command): void;
22
+ //# sourceMappingURL=update.d.ts.map
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Update command for Night Watch CLI
3
+ * Reinstalls global CLI and refreshes cron entries for one or more projects.
4
+ */
5
+ import { spawnSync } from 'child_process';
6
+ import * as fs from 'fs';
7
+ import * as path from 'path';
8
+ import { dim, success, error as uiError, warn } from '@night-watch/core';
9
+ export const DEFAULT_GLOBAL_SPEC = '@jonit-dev/night-watch-cli@latest';
10
+ /**
11
+ * Parse project directories from a comma-separated CLI option.
12
+ * Defaults to current working directory when option is omitted.
13
+ */
14
+ export function parseProjectDirs(projects, cwd) {
15
+ if (!projects || projects.trim().length === 0) {
16
+ return [cwd];
17
+ }
18
+ const dirs = projects
19
+ .split(',')
20
+ .map((entry) => entry.trim())
21
+ .filter((entry) => entry.length > 0)
22
+ .map((entry) => path.resolve(cwd, entry));
23
+ return Array.from(new Set(dirs));
24
+ }
25
+ export function shouldInstallGlobal(options) {
26
+ return options.global !== false;
27
+ }
28
+ function runCommand(command, args, cwd) {
29
+ const result = spawnSync(command, args, {
30
+ cwd,
31
+ env: process.env,
32
+ stdio: 'inherit',
33
+ });
34
+ if (result.error) {
35
+ throw result.error;
36
+ }
37
+ if ((result.status ?? 1) !== 0) {
38
+ const location = cwd ? ` in ${cwd}` : '';
39
+ throw new Error(`Command failed${location}: ${command} ${args.join(' ')}`);
40
+ }
41
+ }
42
+ function resolveNightWatchBin() {
43
+ const result = spawnSync('which', ['night-watch'], {
44
+ encoding: 'utf-8',
45
+ env: process.env,
46
+ });
47
+ if (result.status === 0 && typeof result.stdout === 'string' && result.stdout.trim().length > 0) {
48
+ return result.stdout.trim();
49
+ }
50
+ return 'night-watch';
51
+ }
52
+ /**
53
+ * Register update command.
54
+ */
55
+ export function updateCommand(program) {
56
+ program
57
+ .command('update')
58
+ .description('Update global CLI and refresh cron for project(s)')
59
+ .option('--projects <dirs>', 'Comma-separated project directories (default: current directory)')
60
+ .option('--global-spec <spec>', 'npm package spec used for global install', DEFAULT_GLOBAL_SPEC)
61
+ .option('--no-global', 'Skip global npm install and only refresh project cron')
62
+ .action(async (options) => {
63
+ try {
64
+ const cwd = process.cwd();
65
+ const projectDirs = parseProjectDirs(options.projects, cwd);
66
+ if (shouldInstallGlobal(options)) {
67
+ dim(`Updating global install: npm install -g ${options.globalSpec}`);
68
+ runCommand('npm', ['install', '-g', options.globalSpec]);
69
+ success('Global CLI update completed.');
70
+ }
71
+ const nightWatchBin = resolveNightWatchBin();
72
+ for (const projectDir of projectDirs) {
73
+ if (!fs.existsSync(projectDir) || !fs.statSync(projectDir).isDirectory()) {
74
+ warn(`Skipping invalid project directory: ${projectDir}`);
75
+ continue;
76
+ }
77
+ dim(`Refreshing cron in ${projectDir}`);
78
+ runCommand(nightWatchBin, ['uninstall'], projectDir);
79
+ runCommand(nightWatchBin, ['install'], projectDir);
80
+ success(`Refreshed project: ${projectDir}`);
81
+ }
82
+ success('Update completed.');
83
+ }
84
+ catch (err) {
85
+ uiError(`Update failed: ${err instanceof Error ? err.message : String(err)}`);
86
+ process.exit(1);
87
+ }
88
+ });
89
+ }
90
+ //# sourceMappingURL=update.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"update.js","sourceRoot":"","sources":["../../src/commands/update.ts"],"names":[],"mappings":"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"}
@@ -28,6 +28,7 @@ mkdir -p "${LOG_DIR}"
28
28
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
29
29
  # shellcheck source=night-watch-helpers.sh
30
30
  source "${SCRIPT_DIR}/night-watch-helpers.sh"
31
+ skip_if_job_paused "${SCRIPT_TYPE}" "${PROJECT_DIR}"
31
32
 
32
33
  # emit_result helper - must be defined before use
33
34
  emit_result() {
@@ -50,6 +50,7 @@ PROJECT_RUNTIME_KEY=$(project_runtime_key "${PROJECT_DIR}")
50
50
  # NOTE: Lock file path must match executorLockPath() in src/utils/status-data.ts
51
51
  LOCK_FILE="/tmp/night-watch-${PROJECT_RUNTIME_KEY}.lock"
52
52
  SCRIPT_TYPE="executor"
53
+ skip_if_job_paused "${SCRIPT_TYPE}" "${PROJECT_DIR}"
53
54
 
54
55
  emit_result() {
55
56
  local status="${1:?status required}"
@@ -518,28 +519,43 @@ checkpoint_timeout_progress() {
518
519
  local worktree_dir="${1:?worktree_dir required}"
519
520
  local branch_name="${2:?branch_name required}"
520
521
  local prd_file="${3:?prd_file required}"
522
+ CHECKPOINT_STATUS="none"
521
523
 
522
524
  if [ ! -d "${worktree_dir}" ]; then
523
- return 0
525
+ return 1
524
526
  fi
525
527
 
526
528
  if ! git -C "${worktree_dir}" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
527
- return 0
529
+ return 1
528
530
  fi
529
531
 
530
532
  if [ -z "$(git -C "${worktree_dir}" status --porcelain 2>/dev/null)" ]; then
531
533
  log "TIMEOUT: No local changes to checkpoint for ${prd_file}"
532
- return 0
534
+ if executor_branch_has_commits_ahead_of_base; then
535
+ CHECKPOINT_STATUS="available"
536
+ return 0
537
+ fi
538
+ return 1
533
539
  fi
534
540
 
535
541
  log "TIMEOUT: Checkpointing local progress for ${prd_file} on ${branch_name}"
536
542
  if git -C "${worktree_dir}" add -A >/dev/null 2>&1; then
537
543
  if ! git -C "${worktree_dir}" diff --cached --quiet >/dev/null 2>&1; then
538
- git -C "${worktree_dir}" commit --no-verify \
544
+ if git -C "${worktree_dir}" commit --no-verify \
539
545
  -m "chore: checkpoint timed-out progress for ${prd_file}" \
540
- >> "${LOG_FILE}" 2>&1 || true
546
+ >> "${LOG_FILE}" 2>&1; then
547
+ CHECKPOINT_STATUS="created"
548
+ return 0
549
+ fi
541
550
  fi
542
551
  fi
552
+
553
+ if executor_branch_has_commits_ahead_of_base; then
554
+ CHECKPOINT_STATUS="available"
555
+ return 0
556
+ fi
557
+
558
+ return 1
543
559
  }
544
560
 
545
561
  extract_timeout_phase_titles() {
@@ -569,15 +585,29 @@ build_timeout_followup_comment() {
569
585
  local prd_label="${2:?prd_label required}"
570
586
  local branch_name="${3:?branch_name required}"
571
587
  local issue_body="${4:-}"
588
+ local checkpoint_status="${5:-none}"
572
589
  local phase_titles=""
573
590
  local comment=""
591
+ local resume_note=""
574
592
 
575
593
  phase_titles=$(extract_timeout_phase_titles "${issue_body}" || true)
576
594
 
595
+ case "${checkpoint_status}" in
596
+ created)
597
+ resume_note="Progress was checkpointed on branch ${branch_name}, so the next run will resume from the latest checkpoint."
598
+ ;;
599
+ available)
600
+ resume_note="Existing branch progress is available on ${branch_name}, so the next run can resume from that branch state."
601
+ ;;
602
+ *)
603
+ resume_note="No checkpoint was created because the run produced no local changes or branch commits. The next run will start this issue from the current base branch."
604
+ ;;
605
+ esac
606
+
577
607
  comment="Timeout follow-up:
578
608
 
579
609
  Execution hit the ${max_runtime}s runtime limit while processing ${prd_label}.
580
- Progress was checkpointed on branch ${branch_name}, so the next run will resume from the latest checkpoint.
610
+ ${resume_note}
581
611
 
582
612
  Suggested slices for the next runs:"
583
613
 
@@ -661,8 +691,90 @@ if [ -z "${EXECUTOR_PROMPT_PATH}" ]; then
661
691
  fi
662
692
  EXECUTOR_PROMPT_REF=$(instruction_ref_for_prompt "${PROJECT_DIR}" "${EXECUTOR_PROMPT_PATH}")
663
693
  PROGRESS_PUSH_CMD=$(project_git_push_command "${BRANCH_NAME}")
694
+ AUDIT_TRIAGE_RESULT_FILE=".night-watch-audit-triage-result"
695
+ AUDIT_TRIAGE_RESULT_PATH="${WORKTREE_DIR}/${AUDIT_TRIAGE_RESULT_FILE}"
696
+
697
+ is_audit_board_issue() {
698
+ if [ -z "${ISSUE_NUMBER}" ]; then
699
+ return 1
700
+ fi
701
+
702
+ if printf '%s\n%s\n' "${ISSUE_TITLE_RAW}" "${ISSUE_BODY}" \
703
+ | grep -Eiq '(^|[[:space:]])Audit:|Night Watch audit detected|Report:[[:space:]]+`?logs/audit-report\.md`?|Finding:[[:space:]]+[0-9]+'; then
704
+ return 0
705
+ fi
706
+
707
+ return 1
708
+ }
709
+
710
+ is_tiny_audit_board_issue() {
711
+ if ! is_audit_board_issue; then
712
+ return 1
713
+ fi
714
+
715
+ local line_count
716
+ line_count=$(printf '%s\n' "${ISSUE_BODY}" | wc -l | tr -d '[:space:]')
717
+ if [ "${line_count:-0}" -gt 160 ]; then
718
+ return 1
719
+ fi
720
+
721
+ if printf '%s\n' "${ISSUE_BODY}" \
722
+ | grep -Eiq '^[[:space:]]*#{1,4}[[:space:]]*(Phase|Implementation Plan|Acceptance Criteria|Requirements)\b'; then
723
+ return 1
724
+ fi
725
+
726
+ return 0
727
+ }
728
+
729
+ build_audit_triage_prompt() {
730
+ cat <<EOF
731
+ Audit triage/fix for GitHub issue #${ISSUE_NUMBER}: ${ISSUE_TITLE_RAW}
732
+
733
+ ${ISSUE_BODY}
664
734
 
665
- if [ -n "${ISSUE_NUMBER}" ]; then
735
+ ## Setup
736
+ - You are already inside an isolated worktree at: ${WORKTREE_DIR}
737
+ - Current branch is already checked out: ${BRANCH_NAME}
738
+ - Do NOT run git checkout/switch in ${PROJECT_DIR}
739
+ - Do NOT create or remove worktrees; the cron script manages that
740
+
741
+ ## Lean Audit Workflow
742
+ This is an audit finding, not a full PRD. First validate the claim against the current code.
743
+
744
+ If the finding is false positive or the code is already safe:
745
+ 1. Make no code changes and do not commit.
746
+ 2. Write this marker file in the worktree:
747
+ ${AUDIT_TRIAGE_RESULT_FILE}
748
+ 3. Put "false_positive" on the first line and a concise reason on the following line(s).
749
+ 4. Stop immediately after writing the marker.
750
+
751
+ If the finding is actionable:
752
+ 1. Make the smallest correct fix.
753
+ 2. Add or update focused tests only when they materially protect the behavior.
754
+ 3. Run the relevant verification command(s).
755
+ 4. Commit and push the fix to the existing branch:
756
+ ${PROGRESS_PUSH_CMD}
757
+ 5. Stop immediately after the final push.
758
+
759
+ Do NOT read the full PRD executor workflow. Do NOT use PRD phase planning or parallelize this tiny triage task.
760
+ Do NOT process any other issues.
761
+ EOF
762
+ }
763
+
764
+ read_audit_triage_result() {
765
+ if [ ! -f "${AUDIT_TRIAGE_RESULT_PATH}" ]; then
766
+ return 1
767
+ fi
768
+
769
+ head -n 1 "${AUDIT_TRIAGE_RESULT_PATH}" \
770
+ | tr '[:upper:]' '[:lower:]' \
771
+ | sed -E 's/^[[:space:]]+//; s/[[:space:]]+$//'
772
+ }
773
+
774
+ if is_tiny_audit_board_issue; then
775
+ log "PROMPT: Using lean audit triage workflow for issue #${ISSUE_NUMBER}"
776
+ PROMPT="$(build_audit_triage_prompt)"
777
+ elif [ -n "${ISSUE_NUMBER}" ]; then
666
778
  PROMPT="Implement the following PRD (GitHub issue #${ISSUE_NUMBER}: ${ISSUE_TITLE_RAW}):
667
779
 
668
780
  ${ISSUE_BODY}
@@ -724,6 +836,11 @@ Follow all CLAUDE.md conventions (if present).
724
836
  - Do NOT process any other PRDs — only ${ELIGIBLE_PRD}"
725
837
  fi
726
838
 
839
+ if [ -n "${NW_PROJECT_FEEDBACK_PROMPT:-}" ]; then
840
+ PROMPT="${PROMPT}"$'\n\n'"${NW_PROJECT_FEEDBACK_PROMPT}"
841
+ log "INFO: Added project feedback prompt context"
842
+ fi
843
+
727
844
  # Dry-run mode: print diagnostics and exit
728
845
  if [ "${NW_DRY_RUN:-0}" = "1" ]; then
729
846
  log "DRY-RUN: Would process ${ELIGIBLE_PRD}"
@@ -864,14 +981,20 @@ while [ "${ATTEMPT}" -lt "${MAX_RETRIES}" ]; do
864
981
  break
865
982
  fi
866
983
  log "CONTEXT-EXHAUSTED: Session ${ATTEMPT_NUM} hit context limit — checkpointing and resuming (${ATTEMPT}/${MAX_RETRIES})"
867
- checkpoint_timeout_progress "${WORKTREE_DIR}" "${BRANCH_NAME}" "${ELIGIBLE_PRD}"
868
- push_executor_branch "update" >> "${LOG_FILE}" 2>&1 || true
869
- ensure_executor_pr >> "${LOG_FILE}" 2>&1 || true
984
+ CHECKPOINT_STATUS="none"
985
+ checkpoint_timeout_progress "${WORKTREE_DIR}" "${BRANCH_NAME}" "${ELIGIBLE_PRD}" || true
986
+ if [ "${CHECKPOINT_STATUS}" != "none" ]; then
987
+ push_executor_branch "update" >> "${LOG_FILE}" 2>&1 || true
988
+ ensure_executor_pr >> "${LOG_FILE}" 2>&1 || true
989
+ RESUME_PROGRESS_NOTE="Progress is available on branch ${BRANCH_NAME}."
990
+ else
991
+ RESUME_PROGRESS_NOTE="No checkpoint was created because there were no local changes or branch commits."
992
+ fi
870
993
  # Switch prompt to "continue" mode for the next attempt (fresh context)
871
994
  if [ -n "${ISSUE_NUMBER}" ]; then
872
995
  PROMPT="Continue implementing PRD (GitHub issue #${ISSUE_NUMBER}: ${ISSUE_TITLE_RAW}).
873
996
 
874
- The previous session ran out of context window. Progress has been committed on branch ${BRANCH_NAME}.
997
+ The previous session ran out of context window. ${RESUME_PROGRESS_NOTE}
875
998
 
876
999
  ## Your task
877
1000
  1. Review the current state: check git log, existing code changes, and any task list
@@ -901,7 +1024,7 @@ Follow all CLAUDE.md conventions (if present).
901
1024
  else
902
1025
  PROMPT="Continue implementing the PRD at ${PRD_DIR_REL}/${ELIGIBLE_PRD}
903
1026
 
904
- The previous session ran out of context window. Progress has been committed on branch ${BRANCH_NAME}.
1027
+ The previous session ran out of context window. ${RESUME_PROGRESS_NOTE}
905
1028
 
906
1029
  ## Your task
907
1030
  1. Review the current state: check git log, existing code changes, and any task list
@@ -1091,6 +1214,29 @@ if [ "${EXIT_CODE}" -eq 0 ] && grep -qiF 'Exceeded USD budget' "${LOG_FILE}" 2>/
1091
1214
  EXIT_CODE=1
1092
1215
  fi
1093
1216
 
1217
+ if [ ${EXIT_CODE} -eq 0 ] && is_tiny_audit_board_issue; then
1218
+ AUDIT_TRIAGE_RESULT=$(read_audit_triage_result || true)
1219
+ if [ "${AUDIT_TRIAGE_RESULT}" = "false_positive" ]; then
1220
+ AUDIT_TRIAGE_REASON=$(tail -n +2 "${AUDIT_TRIAGE_RESULT_PATH}" 2>/dev/null | sed -E '/^[[:space:]]*$/d' | head -n 5 || true)
1221
+ if [ -z "${AUDIT_TRIAGE_REASON}" ]; then
1222
+ AUDIT_TRIAGE_REASON="Audit finding was validated as a false positive or already safe."
1223
+ fi
1224
+ log "AUDIT-TRIAGE: Issue #${ISSUE_NUMBER} marked false positive; closing without PR"
1225
+ if [ -n "${NW_CLI}" ]; then
1226
+ "${NW_CLI}" board comment "${ISSUE_NUMBER}" \
1227
+ --body "Audit triage found this is a false positive / already safe (via ${EFFECTIVE_PROVIDER_LABEL}).
1228
+
1229
+ ${AUDIT_TRIAGE_REASON}" 2>>"${LOG_FILE}" || true
1230
+ "${NW_CLI}" board close-issue "${ISSUE_NUMBER}" 2>>"${LOG_FILE}" || \
1231
+ "${NW_CLI}" board move-issue "${ISSUE_NUMBER}" --column "Done" 2>>"${LOG_FILE}" || true
1232
+ fi
1233
+ night_watch_history record "${PROJECT_DIR}" "${ELIGIBLE_PRD}" success --exit-code 0 2>/dev/null || true
1234
+ emit_result "success_audit_false_positive" "prd=${ELIGIBLE_PRD}|branch=${BRANCH_NAME}|reason=audit_false_positive|detail=$(sanitize_result_value "${AUDIT_TRIAGE_REASON}")"
1235
+ cleanup_worktrees "${PROJECT_DIR}"
1236
+ exit 0
1237
+ fi
1238
+ fi
1239
+
1094
1240
  if [ ${EXIT_CODE} -eq 0 ]; then
1095
1241
  if ! ensure_executor_pr; then
1096
1242
  log "FAIL: Unable to create or reuse executor PR for ${BRANCH_NAME} after provider completion"
@@ -1159,14 +1305,22 @@ if [ ${EXIT_CODE} -eq 0 ]; then
1159
1305
  fi
1160
1306
  elif [ ${EXIT_CODE} -eq 124 ]; then
1161
1307
  log "TIMEOUT: Session limit hit after ${SESSION_MAX_RUNTIME}s while processing ${ELIGIBLE_PRD}"
1162
- checkpoint_timeout_progress "${WORKTREE_DIR}" "${BRANCH_NAME}" "${ELIGIBLE_PRD}"
1163
- push_executor_branch "update" >> "${LOG_FILE}" 2>&1 || true
1164
- ensure_executor_pr >> "${LOG_FILE}" 2>&1 || true
1308
+ CHECKPOINT_STATUS="none"
1309
+ checkpoint_timeout_progress "${WORKTREE_DIR}" "${BRANCH_NAME}" "${ELIGIBLE_PRD}" || true
1310
+ if [ "${CHECKPOINT_STATUS}" != "none" ]; then
1311
+ push_executor_branch "update" >> "${LOG_FILE}" 2>&1 || true
1312
+ ensure_executor_pr >> "${LOG_FILE}" 2>&1 || true
1313
+ fi
1165
1314
  if [ -n "${ISSUE_NUMBER}" ]; then
1166
1315
  "${NW_CLI}" board move-issue "${ISSUE_NUMBER}" --column "Ready" 2>>"${LOG_FILE}" || true
1167
1316
  if [ "${SESSION_MAX_RUNTIME}" != "${MAX_RUNTIME}" ]; then
1317
+ if [ "${CHECKPOINT_STATUS}" = "none" ]; then
1318
+ SESSION_TIMEOUT_BODY="Session paused after ${SESSION_MAX_RUNTIME}s (via ${EFFECTIVE_PROVIDER_LABEL}). No checkpoint was created because there were no local changes or branch commits. Will retry from the current base branch on the next run."
1319
+ else
1320
+ SESSION_TIMEOUT_BODY="Session paused after ${SESSION_MAX_RUNTIME}s (via ${EFFECTIVE_PROVIDER_LABEL}). Progress is available on branch \`${BRANCH_NAME}\`. Will resume automatically on the next run."
1321
+ fi
1168
1322
  "${NW_CLI}" board comment "${ISSUE_NUMBER}" \
1169
- --body "Session paused after ${SESSION_MAX_RUNTIME}s (via ${EFFECTIVE_PROVIDER_LABEL}). Progress checkpointed on branch \`${BRANCH_NAME}\`. Will resume automatically on the next run." 2>>"${LOG_FILE}" || true
1323
+ --body "${SESSION_TIMEOUT_BODY}" 2>>"${LOG_FILE}" || true
1170
1324
  else
1171
1325
  "${NW_CLI}" board comment "${ISSUE_NUMBER}" \
1172
1326
  --body "Execution timed out after ${SESSION_MAX_RUNTIME}s (via ${EFFECTIVE_PROVIDER_LABEL}). Moved back to Ready for retry." 2>>"${LOG_FILE}" || true
@@ -1174,12 +1328,13 @@ elif [ ${EXIT_CODE} -eq 124 ]; then
1174
1328
  "${SESSION_MAX_RUNTIME}" \
1175
1329
  "${ELIGIBLE_PRD}" \
1176
1330
  "${BRANCH_NAME}" \
1177
- "${ISSUE_BODY}")
1331
+ "${ISSUE_BODY}" \
1332
+ "${CHECKPOINT_STATUS}")
1178
1333
  "${NW_CLI}" board comment "${ISSUE_NUMBER}" --body "${TIMEOUT_FOLLOWUP_COMMENT}" 2>>"${LOG_FILE}" || true
1179
1334
  fi
1180
1335
  fi
1181
1336
  night_watch_history record "${PROJECT_DIR}" "${ELIGIBLE_PRD}" timeout --exit-code 124 2>/dev/null || true
1182
- emit_result "timeout" "prd=${ELIGIBLE_PRD}|branch=${BRANCH_NAME}"
1337
+ emit_result "timeout" "prd=${ELIGIBLE_PRD}|branch=${BRANCH_NAME}|checkpoint=${CHECKPOINT_STATUS}"
1183
1338
  elif [ "${DOUBLE_RATE_LIMITED}" = "1" ]; then
1184
1339
  if [ -n "${ISSUE_NUMBER}" ]; then
1185
1340
  "${NW_CLI}" board move-issue "${ISSUE_NUMBER}" --column "Ready" 2>>"${LOG_FILE}" || true
@@ -1191,16 +1346,24 @@ elif [ "${DOUBLE_RATE_LIMITED}" = "1" ]; then
1191
1346
  elif check_context_exhausted "${LOG_FILE}" "${LOG_LINE_BEFORE}"; then
1192
1347
  # All resume attempts for context exhaustion were used up
1193
1348
  log "FAIL: Context window exhausted after ${MAX_RETRIES} resume attempts for ${ELIGIBLE_PRD}"
1194
- checkpoint_timeout_progress "${WORKTREE_DIR}" "${BRANCH_NAME}" "${ELIGIBLE_PRD}"
1195
- push_executor_branch "update" >> "${LOG_FILE}" 2>&1 || true
1196
- ensure_executor_pr >> "${LOG_FILE}" 2>&1 || true
1349
+ CHECKPOINT_STATUS="none"
1350
+ checkpoint_timeout_progress "${WORKTREE_DIR}" "${BRANCH_NAME}" "${ELIGIBLE_PRD}" || true
1351
+ if [ "${CHECKPOINT_STATUS}" != "none" ]; then
1352
+ push_executor_branch "update" >> "${LOG_FILE}" 2>&1 || true
1353
+ ensure_executor_pr >> "${LOG_FILE}" 2>&1 || true
1354
+ fi
1197
1355
  if [ -n "${ISSUE_NUMBER}" ]; then
1198
1356
  "${NW_CLI}" board move-issue "${ISSUE_NUMBER}" --column "Ready" 2>>"${LOG_FILE}" || true
1357
+ if [ "${CHECKPOINT_STATUS}" = "none" ]; then
1358
+ CONTEXT_COMMENT="Context window exhausted after ${MAX_RETRIES} resume attempts (${TOTAL_ELAPSED}s total, via ${EFFECTIVE_PROVIDER_LABEL}). No checkpoint was created because there were no local changes or branch commits. Will retry from the current base branch."
1359
+ else
1360
+ CONTEXT_COMMENT="Context window exhausted after ${MAX_RETRIES} resume attempts (${TOTAL_ELAPSED}s total, via ${EFFECTIVE_PROVIDER_LABEL}). Progress is available on branch \`${BRANCH_NAME}\`. Will resume on next run."
1361
+ fi
1199
1362
  "${NW_CLI}" board comment "${ISSUE_NUMBER}" \
1200
- --body "Context window exhausted after ${MAX_RETRIES} resume attempts (${TOTAL_ELAPSED}s total, via ${EFFECTIVE_PROVIDER_LABEL}). Progress checkpointed on branch \`${BRANCH_NAME}\`. Will resume on next run." 2>>"${LOG_FILE}" || true
1363
+ --body "${CONTEXT_COMMENT}" 2>>"${LOG_FILE}" || true
1201
1364
  fi
1202
1365
  night_watch_history record "${PROJECT_DIR}" "${ELIGIBLE_PRD}" context_exhausted --exit-code "${EXIT_CODE}" 2>/dev/null || true
1203
- emit_result "failure" "prd=${ELIGIBLE_PRD}|branch=${BRANCH_NAME}|reason=context_exhausted|exit_code=${EXIT_CODE}"
1366
+ emit_result "failure" "prd=${ELIGIBLE_PRD}|branch=${BRANCH_NAME}|reason=context_exhausted|exit_code=${EXIT_CODE}|checkpoint=${CHECKPOINT_STATUS}"
1204
1367
  else
1205
1368
  PROVIDER_ERROR_DETAIL=$(latest_failure_detail "${LOG_FILE}" "${LOG_LINE_BEFORE}")
1206
1369
  log "FAIL: Night watch exited with code ${EXIT_CODE} while processing ${ELIGIBLE_PRD}"