Skip to content

feat: nightshift#83

Merged
satyaborg merged 4 commits into
mainfrom
feat/nightshift
Jul 6, 2026
Merged

feat: nightshift#83
satyaborg merged 4 commits into
mainfrom
feat/nightshift

Conversation

@satyaborg

Copy link
Copy Markdown
Owner

Devloop Nightshift Mode

Problem

During waking hours the user drives agents interactively to ship focused work. Overnight that leverage is idle: the machine sits unused while a queue of high-value, well-scoped improvements goes unstarted. Today the only way to get autonomous overnight work is to hand-write specs the night before and manually launch devloop on each, which defeats the point. There is no headless path that both decides what to work on and executes it without a human at the terminal, and no single artifact to review in the morning.

Outcome

A scheduled, non-interactive devloop nightshift run selects the 2-3 highest-leverage improvements in a target repository, drives each through the existing coder/reviewer loop in its own worktree, opens a draft PR per accepted change, and writes one digest.md summarizing every run's verdict and PR link. The user installs the overnight trigger once with devloop nightshift --install-schedule 02:00, wakes up to a set of review-ready draft PRs plus a digest, and never touches a terminal to make it happen. Both Claude Code and Codex run headlessly across the night (survey/review by one, implementation by the other).

Acceptance Criteria

AC1: devloop nightshift is dispatched from main() and documented in plain help, TUI help, and the README command table.
AC2: devloop nightshift and every scheduled invocation force non-interactive behavior: plain output, no worktree shell, and no automatic update prompt, even when stdin/stdout are TTYs.
AC3: The survey step builds a prompt that references the installed devloop-spec skill, the requested count, the current date, the .devloop/nightshift/<date>/specs write directory, and an explicit "write specs only, do not implement" constraint, and launches the survey agent through the existing headless run_with_prompt path (not the interactive spec launcher).
AC4: Given a survey agent that writes N specs, nightshift runs each valid spec exactly once, in sorted order, through the --create-pr run path with the resolved coder, reviewer, per-spec max passes, and timeout.
AC5: Per-spec runs are sequential: run K+1 does not start until run K's process exits.
AC6: A run that fails, stalls, is unclear, or times out is recorded in the digest and does not prevent subsequent specs from running.
AC7: .devloop/nightshift/<date>/digest.md is created with one row per spec containing its title, terminal status, branch, draft PR link (or a clear "no PR" marker), and report path, plus the survey rationale, and .devloop/nightshift/latest points at the newest run.
AC8: --dry-run writes specs and a digest but invokes no per-spec run and opens no PR.
AC9: --status prints the path to the latest digest, or a clear message when none exists.
AC10: nightshift_repos, nightshift_count, nightshift_coder, nightshift_reviewer, nightshift_survey_agent, nightshift_max_passes, and nightshift_schedule are readable and writable through the config helpers with local-overrides-global precedence; the config key whitelist rejects unknown keys; nightshift_count is clamped to 1-5 and agent keys are normalized to codex/claude.
AC11: --install-schedule HH:MM writes a sh.devloop.nightshift plist under ${DEVLOOP_LAUNCH_AGENTS_DIR:-$HOME/Library/LaunchAgents} whose StartCalendarInterval encodes the parsed hour and minute, whose ProgramArguments invoke the resolved absolute devloop path with nightshift and the resolved repos, and whose EnvironmentVariables.PATH, StandardOutPath, and StandardErrorPath are populated; it then loads the agent via ${DEVLOOP_LAUNCHCTL:-launchctl} and persists nightshift_schedule.
AC12: --install-schedule never invokes sudo; it prints the exact pmset wake command for the user to run and the written plist path.
AC13: --uninstall-schedule unloads via ${DEVLOOP_LAUNCHCTL:-launchctl}, removes the plist, and clears nightshift_schedule, exiting 0 whether or not one existed.
AC14: An invalid --install-schedule time and an unresolvable repo set each exit nonzero with a specific error and write no plist.
AC15: scripts/devloop_test.sh covers: dispatch/help/README presence, headless invariants, the survey prompt builder, orchestration over a stubbed DEVLOOP_RUN_CMD including a mid-list failure, digest and latest generation, --dry-run, --status, every new config key (set/get/remove, precedence, clamping, whitelist rejection), plist generation contents, install load via a stubbed DEVLOOP_LAUNCHCTL, uninstall removal, and the two nonzero error paths.
AC16: The project function coverage gate in scripts/devloop_test.sh stays at 100% for every new function.

Review Trail

Review rounds and the final report are posted as PR comments below.


Generated by devloop.sh

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 5, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
devloop 2fa3843 Commit Preview URL

Branch Preview URL
Jul 06 2026, 12:11 AM

@satyaborg

Copy link
Copy Markdown
Owner Author

Devloop Review Round 1

  • Verdict: REJECT

Review 1

Verdict: REJECT

Acceptance matrix

Criterion Status Implementation evidence Test evidence
AC1 PASS main() dispatch devloop:107-111; plain help "Common commands" devloop:201-202; welcome_tui rows devloop:242-243; README table README.md:41 devloop_test.sh asserts $help, $tui_help, $readme_text each contain devloop nightshift
AC2 PASS nightshift_force_headless sets USE_TUI=false/ENTER_WORKTREE=false/DEVLOOP_UPDATE_PROMPTED=true devloop:2023-2027; children run --plain --no-shell devloop:2195; maybe_prompt_update early-returns on the flag devloop:449 "nightshift dispatch forces headless mode" asserts false false true from main nightshift with a guard maybe_prompt_update override
AC3 PASS nightshift_survey_prompt embeds skill line, count, date, Write specs only to: <specs_dir>, "write specs only, do not implement", bundled skill path devloop:2037-2074; launched via run_with_prompt devloop:2111-2131 "nightshift survey prompt" checks all fields; "nightshift survey uses run_with_prompt" asserts captured id/args `nightshift-survey
AC4 PASS nightshift_run_repo iterates find|sort, one nightshift_run_spec per valid spec devloop:2277-2295; runner cmd --plain --no-shell --create-pr --coder --reviewer --timeout-minutes <spec> <max_passes> devloop:2190-2196; trailing positional → MAX devloop:2816-2837 invocation count ==3; headless-flags string; 01-first.md 4 max-passes assertion
AC5 PASS Specs run in a synchronous command-substitution loop (no &) devloop:2284-2286 start/end log equals interleaved start 01…end 01…start 02…end 02…start 03…end 03
AC6 PASS No set -e (set -uo pipefail devloop:2); nightshift_run_spec captures code=$? and returns 0 after printing the row; status parsed from output devloop:2190-2205 02-second stub exits 1/"stalled" yet Third row present and digest records stalled
AC7 PASS nightshift_write_digest table + rationale devloop:2208-2226; no PR/no report markers devloop:2203-2205; nightshift_update_latest symlink devloop:2228-2234; parser matches real print_result devloop:4978-4989 three digest rows incl accepted/stalled + no PR, rationale line, latest symlink → 2026-07-05
AC8 PASS dry-run branch appends dry-run row, skips nightshift_run_spec devloop:2281-2282 dry_log empty (! -s); row | First | dry-run | no branch | no PR | not run |
AC9 PASS nightshift_status_command prints latest digest path or no nightshift digest found >&2 / return 1 devloop:2329-2369; nightshift_latest_digest devloop:2308-2327 status prints digest path; empty repo prints message and returns nonzero
AC10 PASS whitelist extended devloop:562,601; agents via normalize_agent, count/passes via normalize_nightshift_* devloop:570-590; readers use devloop_config_value/devloop_agent_setting local-first devloop:540-554,850-861 set/get/remove + local-over-global for repos; count clamp hi/lo + non-numeric reject + default; agent normalize + invalid reject + fallback; max_passes clamp; schedule read/remove; unknown-key rejection
AC11 PASS nightshift_plist_path under DEVLOOP_LAUNCH_AGENTS_DIR devloop:2371-2373; nightshift_write_plist Hour/Minute integers, ProgramArguments=[$ROOT_DIR/devloop,nightshift,--repo,], EnvironmentVariables.PATH, StandardOut/ErrPath devloop:2380-2433; nightshift_load_schedule bootout→bootstrap→load -w via DEVLOOP_LAUNCHCTL devloop:2435-2451; persists schedule devloop:2464 plist contents (Hour 2 / Minute 30, devloop path, --repo, PATH, stdout/stderr), bootstrap gui/ logged, schedule persisted 02:30
AC12 PASS No sudo/pmset executed; prints wrote plist: and wake command: sudo pmset repeat wake MTWRFSU <time> devloop:2465-2466; grep confirms sudo/pmset only in that printed string schedule output contains plist path + exact wake command
AC13 PASS nightshift_uninstall_schedule unload (bootout/unload), rm -f plist, remove_config_value global+local with || true → exit 0 in both branches devloop:2475-2487 removed message + bootout gui/ logged + config cleared; second run prints no nightshift schedule installed
AC14 PASS install parses time first (return 2 invalid nightshift schedule time), resolves repos next (return 2 not resolvable), both before write devloop:2453-2461 9pm → invalid msg + no plist; missing repo → not-resolvable + no plist
AC15 PASS Test block devloop_test.sh covers dispatch/help/README, headless invariant, survey prompt builder, orchestration w/ mid-list failure, digest+latest, dry-run, status, all config keys, plist contents, stubbed-launchctl install, uninstall, both nonzero error paths full suite green
AC16 PASS Coverage gate auto-enumerates every name(){ and DEBUG-traps calls devloop_test.sh:369-404 re-ran bash scripts/devloop_test.sh: ok - 100% project function coverage, exit 0

Engineering quality matrix

Area Status Evidence
Correctness PASS Digest parser (nightshift_result_status/_field) verified against the real print_result format devloop:4978-4989; sequential loop; failures isolated (no set -e); nounset-safe ${arr[@]+"${arr[@]}"} expansions; XML escaping; no sudo/pmset execution.
Test quality PASS Outcome-based assertions: digest contents, sequential order via start/end log, plist contents, launchctl load/unload, config precedence/clamp/whitelist, both nonzero error paths. One untested behavioral branch noted below.
Maintainability PASS Thin orchestrator reusing canonical helpers; small named functions. One hygiene defect (finding 1).
Architecture boundaries PASS Reuses run_with_prompt, lint_spec_file, config helpers, and the --create-pr child path; keeps devloop a single Bash CLI per the spec constraint; external effects injectable (DEVLOOP_RUN_CMD/DEVLOOP_LAUNCHCTL/DEVLOOP_LAUNCH_AGENTS_DIR).
Simplicity PASS Direct control flow. normalize_nightshift_count/_max_passes are near-duplicates but mirror the existing normalize_timeout_minutes convention, so acceptable.
Security PASS plist_escape guards XML injection from repo paths and PATH; agent bypass flags are the established headless pattern; launchctl injectable; no privilege escalation.
Operational safety PASS Sequential (not brittle parallel); idempotent install (bootout→bootstrap→load fallback); uninstall exits 0 either way; per-repo digest/latest update; per-spec failures recorded and non-fatal.

Review flags

  • Silent decision: absent - Both tradeoffs (survey nonzero-exit discards any partial specs; markdown-only digest, no digest.html) are recorded under the track's "design tradeoffs".
  • Scope drift: absent - Diff touches only README.md, devloop, scripts/devloop_test.sh; no generated .devloop/ artifacts committed.
  • Missing test: present - The nightshift_result_status fallback (timeout/run-error when a child exits without printing a devloop <status> line) is entered only by the coverage trap, never asserted.

Findings

  1. [low] devloop:2262 / devloop:2284 - nightshift_run_repo declares local today run_dir specs_dir rows digest survey spec title valid_count survey_ok but the inner IFS=$'\t' read -r title status branch pr report also assigns status, branch, pr, report, which are not in that list. Root cause: incomplete local declaration. Impact: bash dynamic scoping leaks these names into the caller and global scope; nightshift_command's local status flag is silently clobbered, and branch/pr/report become globals. It is benign only because the status flag is already consumed before the repo loop and no later code reads a global branch/pr/report - i.e. correct-by-luck in a 4600-line, frequently-refactored monolith where every other function scopes its locals. Principle: explicit scoping / least surprise.
  2. [low] devloop:2147-2157 - nightshift_result_status's fallback (code==124 → timeout, else run-error) only fires when a child exits without emitting a devloop <status> line (SIGKILL/OOM/external timeout), a status the spec's edge-cases explicitly enumerate. Every orchestration stub prints a status line, so this branch is reached by the function-coverage trap but its output is never asserted. Root cause: test exercises only the status-present path. Impact: a crashed or externally-killed overnight run's digest status derivation is unverified. Principle: test the branch, not just the function.

Missing tests

  • A per-spec DEVLOOP_RUN_CMD stub that prints no devloop <status> line and exits 124 (assert the digest row status is timeout), plus a sibling stub exiting nonzero with no status line (assert run-error).

Fix instructions

  1. Add status branch pr report to the local declaration at devloop:2262 so nightshift_run_repo no longer leaks them into the caller or global scope.
  2. Extend the orchestration test with a spec whose runner stub emits no devloop <status> line and exits 124, asserting the digest records timeout; add a sibling case exiting nonzero without a status line asserting run-error.

Notes

  • Scope: all 16 acceptance criteria pass with concrete code and test evidence; the full suite is green (66 ok / 0 not-ok) and "100% project function coverage" was reproduced by re-running bash scripts/devloop_test.sh (exit 0). This is a strong, near-acceptance implementation - the two low-severity findings above are the only blockers.
  • Verified (not assumed): the digest parser matches the real print_result output (devloop:4978-4989), so the stub-driven tests reflect production output rather than only the stub; --no-shell and the <spec> [max] positional are real devloop contracts (devloop:2805, devloop:264/2816-2837); ROOT_DIR resolves through symlinks to an absolute path (devloop:24).
  • Confirm intent before final acceptance: digest.html is not generated (markdown-only). AC7 does not require it, and the track records the omission, but the Behavior narrative (step 6) mentions it - flag only so the maintainer confirms the reduction is deliberate.

@satyaborg

Copy link
Copy Markdown
Owner Author

Devloop Review Round 2

  • Verdict: ACCEPT

Review 2

Verdict: ACCEPT

Acceptance matrix

Criterion Status Implementation evidence Test evidence
AC1 PASS main() dispatch devloop:107-111; plain help devloop:201-202; welcome_tui rows devloop:242-243; README table README.md:41 $help, $tui_help, $readme_text each asserted to contain devloop nightshift
AC2 PASS nightshift_force_headless sets USE_TUI=false/ENTER_WORKTREE=false/DEVLOOP_UPDATE_PROMPTED=true devloop:2023-2027; children run --plain --no-shell devloop:2195; maybe_prompt_update early-returns on the flag devloop:449 "nightshift dispatch forces headless mode" asserts false false true with a guard maybe_prompt_update override
AC3 PASS nightshift_survey_prompt embeds skill line, count, date, Write specs only to:, "write specs only, do not implement", skill path devloop:2037-2074; launched via run_with_prompt devloop:2111-2131 "nightshift survey prompt" checks fields; "nightshift survey uses run_with_prompt" asserts nightshift-survey|claude -p
AC4 PASS nightshift_run_repo runs one nightshift_run_spec per valid spec in find|sort order devloop:2277-2295; runner cmd --plain --no-shell --create-pr --coder --reviewer --timeout-minutes <spec> <max_passes> devloop:2190-2196 invocation count ==5; headless-flags string; 01-first.md 4 max-passes assertion
AC5 PASS Synchronous command-substitution loop (no &) devloop:2284-2286 start/end log interleaved across all 5 specs (start 01…end 01…start 05…end 05)
AC6 PASS set -uo pipefail (no -e); nightshift_run_spec captures code=$?, returns 0 after printing the row devloop:2180-2205 02-second (stalled), 04-timeout, 05-crash all fail yet every later spec still runs and is recorded
AC7 PASS nightshift_write_digest table + rationale devloop:2208-2226; no PR/no report markers devloop:2203-2205; nightshift_update_latest symlink devloop:2228-2234; parser matches real print_result devloop:4978-4989 accepted/stalled/timeout/run-error rows, rationale line, latest symlink → 2026-07-05
AC8 PASS dry-run branch appends dry-run row, skips runner devloop:2281-2282 dry_log empty (! -s); row | First | dry-run | no branch | no PR | not run |
AC9 PASS nightshift_status_command prints latest digest path or no nightshift digest found>&2/return 1 devloop:2329-2369 status prints digest path; empty repo prints message + nonzero
AC10 PASS whitelist devloop:562,601; agents via normalize_agent, count/passes via normalize_nightshift_* devloop:570-590; readers local-first devloop:540-554,850-861 set/get/remove + local-over-global; count clamp hi/lo + non-numeric reject + default; agent normalize + invalid reject; max_passes clamp; schedule read/remove; unknown-key rejection
AC11 PASS plist path under DEVLOOP_LAUNCH_AGENTS_DIR devloop:2371-2373; nightshift_write_plist Hour/Minute integers, ProgramArguments=[$ROOT_DIR/devloop,nightshift,--repo,], EnvironmentVariables.PATH, StandardOut/ErrPath devloop:2380-2433; load bootout→bootstrap→load -w via DEVLOOP_LAUNCHCTL devloop:2435-2451; persists schedule devloop:2464 plist contents (Hour 2/Minute 30, devloop path, --repo, PATH, stdout/stderr), bootstrap gui/ logged, schedule persisted 02:30
AC12 PASS No sudo/pmset executed; prints wrote plist: + wake command: sudo pmset repeat wake MTWRFSU <time> devloop:2465-2466 output contains plist path + exact wake command
AC13 PASS uninstall unload, rm -f plist, remove_config_value global+local with || true→exit 0 in both branches devloop:2475-2487 removed message + bootout gui/ logged + config cleared; second run no nightshift schedule installed
AC14 PASS install parses time first (return 2), resolves repos next (return 2), both before write devloop:2453-2461 9pm → invalid msg + no plist; missing repo → not-resolvable + no plist
AC15 PASS Test block covers dispatch/help/README, headless invariant, survey prompt builder, orchestration w/ mid-list failure + timeout + crash, digest+latest, dry-run, status, all config keys, plist contents, stubbed-launchctl install, uninstall, both nonzero error paths full suite green
AC16 PASS Coverage gate auto-enumerates every name(){ + DEBUG trap devloop_test.sh:369-404 re-ran bash scripts/devloop_test.sh: ok - 100% project function coverage, exit 0

Engineering quality matrix

Area Status Evidence
Correctness PASS r1 scoping leak resolved: nightshift_run_repo now declares status branch pr report local (devloop:2262), so the digest-row read no longer clobbers caller/global names. Digest parser still matches real print_result (devloop:4978-4989); sequential; failures isolated; nounset-safe expansions; XML escaping; no sudo/pmset.
Test quality PASS r1 missing-test closed: two non-vacuous fallback cases (04-timeout exits 124 with no status line → timeout; 05-crash exits 2 with no status line → run-error) assert digest rows (devloop_test.sh:946-947), plus a scoping regression (devloop_test.sh:925-942) proven to fail pre-fix per the track's red evidence. Assertions verify outcomes (digest content, order, plist, launchctl, config precedence/clamp/whitelist).
Maintainability PASS Fix restores devloop's uniform local-declaration convention; thin orchestrator reusing canonical helpers; small named functions.
Architecture boundaries PASS Reuses run_with_prompt, lint_spec_file, config helpers, --create-pr child path; single Bash CLI per constraint; effects injectable (DEVLOOP_RUN_CMD/DEVLOOP_LAUNCHCTL/DEVLOOP_LAUNCH_AGENTS_DIR).
Simplicity PASS Direct control flow; normalize_nightshift_count/_max_passes mirror the existing normalize_timeout_minutes convention.
Security PASS plist_escape guards XML injection from repo paths and PATH; agent bypass flags are the established headless pattern; launchctl injectable; no privilege escalation.
Operational safety PASS Sequential (not brittle parallel); idempotent install (bootout→bootstrap→load fallback); uninstall exits 0 either way; per-spec failures recorded and non-fatal, and crash/timeout status derivation is now verified.

Review flags

  • Silent decision: absent - The two tradeoffs (survey nonzero-exit discards partial specs; markdown-only digest, no digest.html) remain recorded under the track's "design tradeoffs"; Pass 2 introduces no new undocumented decision.
  • Scope drift: absent - Diff touches only README.md, devloop, scripts/devloop_test.sh; the Pass 2 devloop change is a single in-place local-declaration edit (insertion count unchanged at 683), the rest is test-only.
  • Missing test: absent - The r1 fallback-status gap is now covered by the timeout and run-error cases.

Findings

None.

Missing tests

  • None.

Fix instructions

None.

Notes

  • Both Pass 1 findings are fixed and independently verified: (1) the variable-scope leak is gone and guarded by a red-proven regression test whose pre-fix failure (got [unknown|no PR|no report]) matches the last spec's parsed values, confirming the test is non-vacuous; (2) the timeout/run-error fallbacks are exercised by stubs that genuinely omit the devloop <status> line, not merely touched by the coverage trap.
  • All 16 acceptance criteria pass with concrete code and test evidence; full suite reproduced green (66 ok / 0 not-ok) with ok - 100% project function coverage (exit 0).
  • Acceptable, documented scope reduction (not a blocker): digest.html is not generated even though the track's report-format is html and the Behavior narrative (step 6) mentions it. AC7 requires only digest.md, and the track records the markdown-only choice. If parity with the narrative is wanted, an HTML digest is a reasonable follow-up PR, out of scope for these acceptance criteria.

@satyaborg

Copy link
Copy Markdown
Owner Author

Devloop Final Report

Field Value
Final status accepted
Pass count 2 / 5
Final verdict ACCEPT
PR URL #83
Branch feat/nightshift

Acceptance Matrix Summary

Acceptance matrix

Criterion Status Implementation evidence Test evidence
AC1 PASS main() dispatch devloop:107-111; plain help devloop:201-202; welcome_tui rows devloop:242-243; README table README.md:41 $help, $tui_help, $readme_text each asserted to contain devloop nightshift
AC2 PASS nightshift_force_headless sets USE_TUI=false/ENTER_WORKTREE=false/DEVLOOP_UPDATE_PROMPTED=true devloop:2023-2027; children run --plain --no-shell devloop:2195; maybe_prompt_update early-returns on the flag devloop:449 "nightshift dispatch forces headless mode" asserts false false true with a guard maybe_prompt_update override
AC3 PASS nightshift_survey_prompt embeds skill line, count, date, Write specs only to:, "write specs only, do not implement", skill path devloop:2037-2074; launched via run_with_prompt devloop:2111-2131 "nightshift survey prompt" checks fields; "nightshift survey uses run_with_prompt" asserts nightshift-survey|claude -p
AC4 PASS nightshift_run_repo runs one nightshift_run_spec per valid spec in find|sort order devloop:2277-2295; runner cmd --plain --no-shell --create-pr --coder --reviewer --timeout-minutes <spec> <max_passes> devloop:2190-2196 invocation count ==5; headless-flags string; 01-first.md 4 max-passes assertion
AC5 PASS Synchronous command-substitution loop (no &) devloop:2284-2286 start/end log interleaved across all 5 specs (start 01…end 01…start 05…end 05)
AC6 PASS set -uo pipefail (no -e); nightshift_run_spec captures code=$?, returns 0 after printing the row devloop:2180-2205 02-second (stalled), 04-timeout, 05-crash all fail yet every later spec still runs and is recorded
AC7 PASS nightshift_write_digest table + rationale devloop:2208-2226; no PR/no report markers devloop:2203-2205; nightshift_update_latest symlink devloop:2228-2234; parser matches real print_result devloop:4978-4989 accepted/stalled/timeout/run-error rows, rationale line, latest symlink → 2026-07-05
AC8 PASS dry-run branch appends dry-run row, skips runner devloop:2281-2282 dry_log empty (! -s); row | First | dry-run | no branch | no PR | not run |
AC9 PASS nightshift_status_command prints latest digest path or no nightshift digest found>&2/return 1 devloop:2329-2369 status prints digest path; empty repo prints message + nonzero
AC10 PASS whitelist devloop:562,601; agents via normalize_agent, count/passes via normalize_nightshift_* devloop:570-590; readers local-first devloop:540-554,850-861 set/get/remove + local-over-global; count clamp hi/lo + non-numeric reject + default; agent normalize + invalid reject; max_passes clamp; schedule read/remove; unknown-key rejection
AC11 PASS plist path under DEVLOOP_LAUNCH_AGENTS_DIR devloop:2371-2373; nightshift_write_plist Hour/Minute integers, ProgramArguments=[$ROOT_DIR/devloop,nightshift,--repo,], EnvironmentVariables.PATH, StandardOut/ErrPath devloop:2380-2433; load bootout→bootstrap→load -w via DEVLOOP_LAUNCHCTL devloop:2435-2451; persists schedule devloop:2464 plist contents (Hour 2/Minute 30, devloop path, --repo, PATH, stdout/stderr), bootstrap gui/ logged, schedule persisted 02:30
AC12 PASS No sudo/pmset executed; prints wrote plist: + wake command: sudo pmset repeat wake MTWRFSU <time> devloop:2465-2466 output contains plist path + exact wake command
AC13 PASS uninstall unload, rm -f plist, remove_config_value global+local with || true→exit 0 in both branches devloop:2475-2487 removed message + bootout gui/ logged + config cleared; second run no nightshift schedule installed
AC14 PASS install parses time first (return 2), resolves repos next (return 2), both before write devloop:2453-2461 9pm → invalid msg + no plist; missing repo → not-resolvable + no plist
AC15 PASS Test block covers dispatch/help/README, headless invariant, survey prompt builder, orchestration w/ mid-list failure + timeout + crash, digest+latest, dry-run, status, all config keys, plist contents, stubbed-launchctl install, uninstall, both nonzero error paths full suite green
AC16 PASS Coverage gate auto-enumerates every name(){ + DEBUG trap devloop_test.sh:369-404 re-ran bash scripts/devloop_test.sh: ok - 100% project function coverage, exit 0

Engineering Quality Summary

Engineering quality matrix

Area Status Evidence
Correctness PASS r1 scoping leak resolved: nightshift_run_repo now declares status branch pr report local (devloop:2262), so the digest-row read no longer clobbers caller/global names. Digest parser still matches real print_result (devloop:4978-4989); sequential; failures isolated; nounset-safe expansions; XML escaping; no sudo/pmset.
Test quality PASS r1 missing-test closed: two non-vacuous fallback cases (04-timeout exits 124 with no status line → timeout; 05-crash exits 2 with no status line → run-error) assert digest rows (devloop_test.sh:946-947), plus a scoping regression (devloop_test.sh:925-942) proven to fail pre-fix per the track's red evidence. Assertions verify outcomes (digest content, order, plist, launchctl, config precedence/clamp/whitelist).
Maintainability PASS Fix restores devloop's uniform local-declaration convention; thin orchestrator reusing canonical helpers; small named functions.
Architecture boundaries PASS Reuses run_with_prompt, lint_spec_file, config helpers, --create-pr child path; single Bash CLI per constraint; effects injectable (DEVLOOP_RUN_CMD/DEVLOOP_LAUNCHCTL/DEVLOOP_LAUNCH_AGENTS_DIR).
Simplicity PASS Direct control flow; normalize_nightshift_count/_max_passes mirror the existing normalize_timeout_minutes convention.
Security PASS plist_escape guards XML injection from repo paths and PATH; agent bypass flags are the established headless pattern; launchctl injectable; no privilege escalation.
Operational safety PASS Sequential (not brittle parallel); idempotent install (bootout→bootstrap→load fallback); uninstall exits 0 either way; per-spec failures recorded and non-fatal, and crash/timeout status derivation is now verified.

Implementation Summary

  • Final branch: feat/nightshift
  • Final commit: e4ae581
  • Commit message: fix: nightshift

Commit References

  • pass 1 0798df9 feat: nightshift (README.md, devloop, scripts/devloop_test.sh)
  • pass 2 e4ae581 fix: nightshift (devloop, scripts/devloop_test.sh)

Tests Run

  • Verification hook log: not configured
  • Review test evidence: see the acceptance matrix summary above.

Residual Risk

  • No blocking residual risk was recorded by the final review.

@satyaborg satyaborg marked this pull request as ready for review July 6, 2026 01:30
@satyaborg satyaborg merged commit cf142a2 into main Jul 6, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant