@jonit-dev/night-watch-cli 1.1.4 → 1.1.5

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 (50) hide show
  1. package/README.md +48 -426
  2. package/dist/cli.js +6 -0
  3. package/dist/cli.js.map +1 -1
  4. package/dist/commands/doctor.d.ts +16 -0
  5. package/dist/commands/doctor.d.ts.map +1 -0
  6. package/dist/commands/doctor.js +155 -0
  7. package/dist/commands/doctor.js.map +1 -0
  8. package/dist/commands/init.d.ts.map +1 -1
  9. package/dist/commands/init.js +15 -9
  10. package/dist/commands/init.js.map +1 -1
  11. package/dist/commands/prd.d.ts +24 -0
  12. package/dist/commands/prd.d.ts.map +1 -0
  13. package/dist/commands/prd.js +283 -0
  14. package/dist/commands/prd.js.map +1 -0
  15. package/dist/commands/review.d.ts.map +1 -1
  16. package/dist/commands/review.js +24 -0
  17. package/dist/commands/review.js.map +1 -1
  18. package/dist/commands/run.d.ts +19 -0
  19. package/dist/commands/run.d.ts.map +1 -1
  20. package/dist/commands/run.js +55 -6
  21. package/dist/commands/run.js.map +1 -1
  22. package/dist/commands/status.d.ts.map +1 -1
  23. package/dist/commands/status.js +27 -6
  24. package/dist/commands/status.js.map +1 -1
  25. package/dist/config.d.ts.map +1 -1
  26. package/dist/config.js +40 -1
  27. package/dist/config.js.map +1 -1
  28. package/dist/constants.d.ts +3 -1
  29. package/dist/constants.d.ts.map +1 -1
  30. package/dist/constants.js +3 -0
  31. package/dist/constants.js.map +1 -1
  32. package/dist/templates/prd-template.d.ts +11 -0
  33. package/dist/templates/prd-template.d.ts.map +1 -0
  34. package/dist/templates/prd-template.js +166 -0
  35. package/dist/templates/prd-template.js.map +1 -0
  36. package/dist/types.d.ts +14 -0
  37. package/dist/types.d.ts.map +1 -1
  38. package/dist/utils/github.d.ts +30 -0
  39. package/dist/utils/github.d.ts.map +1 -0
  40. package/dist/utils/github.js +104 -0
  41. package/dist/utils/github.js.map +1 -0
  42. package/dist/utils/notify.d.ts +63 -0
  43. package/dist/utils/notify.d.ts.map +1 -0
  44. package/dist/utils/notify.js +237 -0
  45. package/dist/utils/notify.js.map +1 -0
  46. package/package.json +4 -4
  47. package/scripts/night-watch-cron.sh +8 -1
  48. package/scripts/night-watch-helpers.sh +51 -0
  49. package/scripts/test-helpers.bats +77 -0
  50. package/templates/prd.md +26 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jonit-dev/night-watch-cli",
3
- "version": "1.1.4",
3
+ "version": "1.1.5",
4
4
  "description": "Autonomous PRD execution using AI Provider CLIs + cron",
5
5
  "type": "module",
6
6
  "bin": {
@@ -40,11 +40,11 @@
40
40
  "license": "MIT",
41
41
  "repository": {
42
42
  "type": "git",
43
- "url": "https://github.com/joaopio/night-watch-cli.git"
43
+ "url": "https://github.com/jonit-dev/night-watch-cli.git"
44
44
  },
45
- "homepage": "https://github.com/joaopio/night-watch-cli#readme",
45
+ "homepage": "https://github.com/jonit-dev/night-watch-cli#readme",
46
46
  "bugs": {
47
- "url": "https://github.com/joaopio/night-watch-cli/issues"
47
+ "url": "https://github.com/jonit-dev/night-watch-cli/issues"
48
48
  },
49
49
  "dependencies": {
50
50
  "chalk": "^5.6.2",
@@ -55,13 +55,19 @@ fi
55
55
 
56
56
  cleanup_worktrees "${PROJECT_DIR}"
57
57
 
58
- ELIGIBLE_PRD=$(find_eligible_prd "${PRD_DIR}")
58
+ ELIGIBLE_PRD=$(find_eligible_prd "${PRD_DIR}" "${MAX_RUNTIME}")
59
59
 
60
60
  if [ -z "${ELIGIBLE_PRD}" ]; then
61
61
  log "SKIP: No eligible PRDs (all done, in-progress, or blocked)"
62
62
  exit 0
63
63
  fi
64
64
 
65
+ # Claim the PRD to prevent other runs from selecting it
66
+ claim_prd "${PRD_DIR}" "${ELIGIBLE_PRD}"
67
+
68
+ # Update EXIT trap to also release claim
69
+ trap "rm -f '${LOCK_FILE}'; release_claim '${PRD_DIR}' '${ELIGIBLE_PRD}'" EXIT
70
+
65
71
  PRD_NAME="${ELIGIBLE_PRD%.md}"
66
72
  BRANCH_NAME="night-watch/${PRD_NAME}"
67
73
  if [ -n "${NW_DEFAULT_BRANCH:-}" ]; then
@@ -146,6 +152,7 @@ esac
146
152
  if [ ${EXIT_CODE} -eq 0 ]; then
147
153
  PR_EXISTS=$(gh pr list --state open --json headRefName --jq '.[].headRefName' 2>/dev/null | grep -cF "${BRANCH_NAME}" || echo "0")
148
154
  if [ "${PR_EXISTS}" -gt 0 ]; then
155
+ release_claim "${PRD_DIR}" "${ELIGIBLE_PRD}"
149
156
  mark_prd_done "${PRD_DIR}" "${ELIGIBLE_PRD}"
150
157
  git -C "${PROJECT_DIR}" add -A docs/PRDs/night-watch/
151
158
  git -C "${PROJECT_DIR}" commit -m "chore: mark ${ELIGIBLE_PRD} as done (PR opened on ${BRANCH_NAME})
@@ -118,10 +118,55 @@ detect_default_branch() {
118
118
  echo "main"
119
119
  }
120
120
 
121
+ # ── Claim management ─────────────────────────────────────────────────────────
122
+
123
+ claim_prd() {
124
+ local prd_dir="${1:?prd_dir required}"
125
+ local prd_file="${2:?prd_file required}"
126
+ local claim_file="${prd_dir}/${prd_file}.claim"
127
+
128
+ printf '{"timestamp":%d,"hostname":"%s","pid":%d}\n' \
129
+ "$(date +%s)" "$(hostname)" "$$" > "${claim_file}"
130
+ }
131
+
132
+ release_claim() {
133
+ local prd_dir="${1:?prd_dir required}"
134
+ local prd_file="${2:?prd_file required}"
135
+ local claim_file="${prd_dir}/${prd_file}.claim"
136
+
137
+ rm -f "${claim_file}"
138
+ }
139
+
140
+ is_claimed() {
141
+ local prd_dir="${1:?prd_dir required}"
142
+ local prd_file="${2:?prd_file required}"
143
+ local max_runtime="${3:-7200}"
144
+ local claim_file="${prd_dir}/${prd_file}.claim"
145
+
146
+ if [ ! -f "${claim_file}" ]; then
147
+ return 1
148
+ fi
149
+
150
+ local claim_ts
151
+ claim_ts=$(grep -o '"timestamp":[0-9]*' "${claim_file}" 2>/dev/null | grep -o '[0-9]*' || echo "0")
152
+ local now
153
+ now=$(date +%s)
154
+ local age=$(( now - claim_ts ))
155
+
156
+ if [ "${age}" -lt "${max_runtime}" ]; then
157
+ return 0 # actively claimed
158
+ else
159
+ # Stale claim — remove it
160
+ rm -f "${claim_file}"
161
+ return 1
162
+ fi
163
+ }
164
+
121
165
  # ── Find next eligible PRD ───────────────────────────────────────────────────
122
166
 
123
167
  find_eligible_prd() {
124
168
  local prd_dir="${1:?prd_dir required}"
169
+ local max_runtime="${2:-7200}"
125
170
  local done_dir="${prd_dir}/done"
126
171
 
127
172
  local prd_files
@@ -139,6 +184,12 @@ find_eligible_prd() {
139
184
  prd_file=$(basename "${prd_path}")
140
185
  local prd_name="${prd_file%.md}"
141
186
 
187
+ # Skip if claimed by another process
188
+ if is_claimed "${prd_dir}" "${prd_file}" "${max_runtime}"; then
189
+ log "SKIP-PRD: ${prd_file} — claimed by another process"
190
+ continue
191
+ fi
192
+
142
193
  # Skip if a PR already exists for this PRD
143
194
  if echo "${open_branches}" | grep -qF "${prd_name}"; then
144
195
  log "SKIP-PRD: ${prd_file} — open PR already exists"
@@ -0,0 +1,77 @@
1
+ #!/usr/bin/env bats
2
+
3
+ # Tests for night-watch-helpers.sh claim functions
4
+
5
+ setup() {
6
+ # Source the helpers
7
+ SCRIPT_DIR="$(cd "$(dirname "${BATS_TEST_FILENAME}")" && pwd)"
8
+
9
+ # Set required globals
10
+ export LOG_FILE="/tmp/night-watch-test-$$.log"
11
+
12
+ source "${SCRIPT_DIR}/night-watch-helpers.sh"
13
+
14
+ # Create temp PRD directory
15
+ TEST_PRD_DIR=$(mktemp -d)
16
+ echo "# Test PRD" > "${TEST_PRD_DIR}/01-test-prd.md"
17
+ echo "# Test PRD 2" > "${TEST_PRD_DIR}/02-test-prd.md"
18
+ }
19
+
20
+ teardown() {
21
+ rm -rf "${TEST_PRD_DIR}"
22
+ rm -f "${LOG_FILE}"
23
+ }
24
+
25
+ @test "claim_prd creates .claim file with JSON" {
26
+ claim_prd "${TEST_PRD_DIR}" "01-test-prd.md"
27
+
28
+ [ -f "${TEST_PRD_DIR}/01-test-prd.md.claim" ]
29
+
30
+ local content
31
+ content=$(cat "${TEST_PRD_DIR}/01-test-prd.md.claim")
32
+
33
+ # Check JSON contains expected fields
34
+ echo "${content}" | grep -q '"timestamp":'
35
+ echo "${content}" | grep -q '"hostname":'
36
+ echo "${content}" | grep -q '"pid":'
37
+ }
38
+
39
+ @test "is_claimed returns 0 for active claim" {
40
+ claim_prd "${TEST_PRD_DIR}" "01-test-prd.md"
41
+
42
+ run is_claimed "${TEST_PRD_DIR}" "01-test-prd.md" 7200
43
+ [ "$status" -eq 0 ]
44
+ }
45
+
46
+ @test "is_claimed returns 1 for stale claim" {
47
+ # Write a claim with an old timestamp (1 second)
48
+ printf '{"timestamp":1000000000,"hostname":"test","pid":1}\n' \
49
+ > "${TEST_PRD_DIR}/01-test-prd.md.claim"
50
+
51
+ run is_claimed "${TEST_PRD_DIR}" "01-test-prd.md" 7200
52
+ [ "$status" -eq 1 ]
53
+ }
54
+
55
+ @test "is_claimed returns 1 for no claim" {
56
+ run is_claimed "${TEST_PRD_DIR}" "01-test-prd.md" 7200
57
+ [ "$status" -eq 1 ]
58
+ }
59
+
60
+ @test "release_claim removes .claim file" {
61
+ claim_prd "${TEST_PRD_DIR}" "01-test-prd.md"
62
+ [ -f "${TEST_PRD_DIR}/01-test-prd.md.claim" ]
63
+
64
+ release_claim "${TEST_PRD_DIR}" "01-test-prd.md"
65
+ [ ! -f "${TEST_PRD_DIR}/01-test-prd.md.claim" ]
66
+ }
67
+
68
+ @test "find_eligible_prd skips claimed PRD" {
69
+ # Claim the first PRD
70
+ claim_prd "${TEST_PRD_DIR}" "01-test-prd.md"
71
+
72
+ # find_eligible_prd should skip 01 and return 02
73
+ local result
74
+ result=$(find_eligible_prd "${TEST_PRD_DIR}" 7200)
75
+
76
+ [ "${result}" = "02-test-prd.md" ]
77
+ }
@@ -0,0 +1,26 @@
1
+ # PRD: {{TITLE}}
2
+
3
+ {{DEPENDS_ON}}
4
+
5
+ **Complexity: {{COMPLEXITY_SCORE}} → {{COMPLEXITY_LEVEL}} mode**
6
+ {{COMPLEXITY_BREAKDOWN}}
7
+
8
+ ---
9
+
10
+ ## Problem
11
+
12
+ <!-- What problem does this solve? Describe in 1-2 sentences. -->
13
+
14
+ ## Solution
15
+
16
+ <!-- How will you solve it? 3-5 bullets explaining the approach. -->
17
+
18
+ ## Phases
19
+
20
+ {{PHASES}}
21
+
22
+ ## Acceptance Criteria
23
+
24
+ - [ ] All phases complete
25
+ - [ ] All tests pass
26
+ - [ ] Feature is reachable (not orphaned code)