Skip to content
Back to skills

Goodnight

ASecurity

End-of-day close-out - accountability, route undone items, daily report

  • 5 stars
  • 0 votes
  • 0 copies
  • 3 views
  • Added October 4, 2026
ai-agentspythonrustgoshellbashgit

Works with

  • claude code

Security analysis

A100/100

Pro scans all 2 files and shows the line behind each finding

Scanned October 5, 2026

npx -y skills add OpenCairn/OpenCairn --skill goodnight --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Goodnight?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Goodnight
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/opencairn-goodnight/badge)](https://www.skillsdirectory.com/skills/opencairn-goodnight)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: goodnight
description: End-of-day close-out - accountability, route undone items, daily report
---

# Goodnight - End-of-Day Status Report

**Scoped rule loading:** `_shared-rules-planning.md` before task carry-forward/window maintenance; `_shared-rules-reviewer.md` before audit preparation; `_shared-rules-content.md` before provenance or structural operations. Read the applicable numbered sections at that point, from the same directory as the core `_shared-rules.md`; do not preload unrelated supplements.

You are running the user's end-of-day operational close-out. This is a technical/PM-focused review - think engineering standup for yourself, not therapy session.

## Philosophy

The goal is clean handoff to tomorrow-you:
- **Inventory** - what's the state of everything?
- **Accountability** - what got done?
- **Context** - what would tomorrow-you need to know to hit the ground running?

This is the complement to `$morning` - morning surfaces the landscape, goodnight closes the books.

## Instructions

**Write mechanism — applies to every step below.** All mutations of `This Week.md`, `Tickler.md`, and project/area hub files in this skill go through `locked-edit.sh`, never a raw edit (see `_shared-rules.md` §5 — incl. the `write-tickler.sh`-vs-`locked-edit.sh` split for Tickler and exit-code handling). For This Week.md day-section edits (item moves in Step 9), use `--replace` — NOT `--append`, which adds at EOF outside any day section. Step 10's collapses use `--show-section` then `--delete-section`.

### 0. Resolve Vault Path

Determine the vault base path. Run:

```bash
"${VAULT_PATH:?VAULT_PATH not set}/.claude/scripts/resolve-vault.sh"
"$VAULT_PATH/.claude/scripts/check-archive-layout.sh" --enforce "$VAULT_PATH"
```

If error, abort. Read `~/.codex/skills/_shared-rules.md` and apply its rules throughout this skill. All code below uses `{VAULT}` as a placeholder — substitute the resolved vault path.

**Mode selection:** For a `$morning` catch-up invocation, continue at **Catch-up mode** below and return after it. For a deferred debrief invocation, run C3 and return. Otherwise continue with the nightly steps.

### 1. Check current date/time and concurrent sessions

```bash
date +"%A, %d %b %Y — %H:%M %Z"  # friendly display with time and timezone
date +"%Y-%m-%d"                   # for file paths and session timestamp
TODAY="$(date +%F)"
SESSION_DIR="{VAULT}/06 Archive/OpenCairn/Session Logs"
SESSION_LOG="$SESSION_DIR/$TODAY.md"
[ "$(dirname "$SESSION_LOG")" = "$SESSION_DIR" ] || { echo "ERROR: session log escaped current-log directory: $SESSION_LOG" >&2; exit 1; }
printf 'Session log path: %s\n' "$SESSION_LOG"
```

**Path invariant:** today's active log is directly under `Session Logs/`, never its `YYYY/` archival subfolder. Use the exact displayed path throughout this run. Shell variables do not persist between tool calls, so re-derive and assert `SESSION_LOG` inside each later session-log call rather than reconstructing it from an archived path.

**Concurrent session check:** Count active Claude Code instances plus other Codex instances (excluding this Codex process):

```bash
command -v pgrep >/dev/null || exit 0
ACTIVE_CLAUDE=$(pgrep -c -x claude || true)
ACTIVE_CODEX=$(pgrep -c -x codex || true)
OTHER_AGENTS=$(( ACTIVE_CLAUDE + ACTIVE_CODEX - 1 ))
[ "$OTHER_AGENTS" -lt 0 ] && OTHER_AGENTS=0
echo "Other active agent sessions: $OTHER_AGENTS"
```

If `pgrep` is unavailable (e.g. Git Bash on Windows), skip this check silently. If `OTHER_AGENTS > 0`, display a non-blocking notice and proceed:

```
ℹ N other active agent session(s). Concurrent writes are safe (locked per _shared-rules.md §5), and sessions parked after this point are caught by Step 14's post-write reconciliation. Parking them first keeps the daily report most coherent — say so now if you'd rather do that.
```

Do not block waiting for a reply — the reconciliation machinery (Steps 2 and 14) exists precisely so goodnight can proceed safely. Pause only if the user actually interjects.

### 2. Gather Today's Activity (auto)

Read and compile:
- **This Week.md:** Read `{VAULT}/01 Now/This Week.md` (if it exists and today falls within the date range) — find today's day section. Checked items (`[x]`) are completed, unchecked (`[ ]`) are open. This is the richest single source for what was planned vs what happened
- **Today's sessions:** Check `{VAULT}/06 Archive/OpenCairn/Session Logs/YYYY-MM-DD.md` for today's date
- **Project states:** Read the `03 Projects/` root docs directly (root = active; folder location is status)
- **Session outcomes:** Note what each session accomplished (for the Sessions list)
- **Candidate open loops:** Extract unchecked items (`- [ ]`) from today's day section in This Week.md, plus due items from Tickler.md. Session files are historical records — open loops were routed to SSOT at park time
- **Blockers:** Read `## Blockers` from the newest earlier daily report and apply Step 8's carry rule to it now, so Step 5 presents the list Step 8 will write

**Capture session-file baseline** (load-bearing for Step 14's post-write concurrent-session reconciliation — do NOT rely on remembering this later):

```bash
TODAY="$(date +%F)"
SESSION_DIR="{VAULT}/06 Archive/OpenCairn/Session Logs"
SESSION_LOG="$SESSION_DIR/$TODAY.md"
[ "$(dirname "$SESSION_LOG")" = "$SESSION_DIR" ] || { echo "ERROR: session log escaped current-log directory: $SESSION_LOG" >&2; exit 1; }
STEP2_NEXT=$("{VAULT}/.claude/scripts/next-session-number.sh" "$SESSION_LOG")
STEP2_LAST_N=$((STEP2_NEXT - 1))
echo "Step 2 baseline: $STEP2_LAST_N session(s) present (next would be $STEP2_NEXT)"
```

**⛔ CHECKPOINT:** Display the literal line `Step 2 baseline: N session(s) present (next would be M)` in your response. Step 14's reconciliation compares against this number — if it's not on the page, the comparison is being done from internal memory rather than observable state. Shell variables don't persist between shell calls; the displayed number is the source of truth for Step 14.

**Important:** Store the rest of the gathered data in working memory - it's a DRAFT inventory, not ground truth.

### 3. Pre-Verification Debrief (BEFORE presenting report)

**This step happens BEFORE presenting the status report.** The goal is to update your working model with reality before presenting outdated information.

Ask:
> "Before I show today's report, let me check: did you complete anything outside parked agent sessions today? Any tasks from earlier sessions that are now done?"

Wait for response.

**If the user provides completions:**
1. Update your working memory (mark those items as completed in your draft)
2. **Update SSOT files immediately** (see Step 4) — This Week.md, Tickler, project files
3. Then proceed to Step 5 with the corrected data

**If the user says "no" or "nothing":** Proceed to Step 5 with original data.

### 4. Update SSOT Files for Completed Loops

When the user reports a loop is complete, update the SSOT files (not session docs — those are historical records with plain bullets):

1. **Update This Week.md:** If This Week.md is current and the completed item appears as unchecked in today's day section, mark it `[x]` via `locked-edit.sh --replace` (never a raw edit — This Week.md is a shared planning file, see `_shared-rules.md` §5).
2. **Update Tickler.md:** If the completed item appears in Tickler, delete the line.
3. **Update project file:** If the item is scoped to a project, mark it complete in the project file.
4. **Confirm the update:** Display brief acknowledgement:
   ```
   ✓ Marked complete: "LOOP_TEXT" (in This Week.md / Tickler / Project)
   ```

**Why update immediately:** Prevents the same loop from appearing as open in future `$morning`, `$goodnight`, `$pickup`, or `$weekly-review` runs.

### 5. Present Status Report

**Now** display the report using your corrected working memory:

```
## Today's Report — [Day], [DD] [Mon] [YYYY] — [HH:MM TZ]

**Projects touched:** [list]
**Sessions:** [count]

### Sessions
1. [Topic] — [outcome]
2. [Topic] — [outcome]
...

### Blockers
- [The carried list from Step 2, earlier lines verbatim — not rebuilt from memory]
```

### 6. Mid-Flow Corrections

**If the user corrects you during the report** ("actually that's done", "I finished that earlier"):

1. **Acknowledge immediately:** "Got it, marking that complete."
2. **Update SSOT files** (same process as Step 4) — This Week.md, Tickler, project files
3. **Update your working memory** - do NOT re-read session files (you'll get stale data)
4. **Continue with corrected state** - don't re-display the whole report

**Critical:** Once the user tells you something is done, treat it as done for the rest of this session. Do not pull from files again.

### 7. Additional Captures (brief, optional)

Ask:
> "Anything else not captured? New blockers, decisions made, or items to add?"

- If yes: add to inventory. Don't add to session files — captures route as follows:
  - Activity outside parked agent sessions (wander/exercise/social/admin/errands) → daily report's `## Outside-Agent` section
  - New blockers → daily report's `## Blockers` section
  - New decisions / actionable items → relevant SSOT (This Week.md day section, project file, or Tickler) — NOT the daily report (the daily report is archival; SSOT files surface in `$morning` and `$pickup`)
- If no: proceed

### 7b. Stranded Claude-internal work product (day-scope net)

`$park` Step 4 catches these per session; this step is the last net of the day, for sessions that ended without a park. Run `find ~/.claude/plans -type f -mtime -1` and Read each hit that hasn't already been migrated — a sub-agent's output (`*-agent-*.md`) is a separate document from its parent plan and gets its own verdict, per that step. Migrate standalone reference material to its semantic vault home; leave spent execution plans.

Output: `✓ No stranded work product` or `🔧 Migrated N item(s): [paths]`.

### 8. Generate Daily Report

Create file at `{VAULT}/06 Archive/OpenCairn/Daily Reports/YYYY-MM-DD.md`.

**Include the goodnight session itself as the last numbered entry in the Sessions list.** The goodnight session log entry is written later (Step 14), but the Daily Report is the user-facing day index and should enumerate every session — including the goodnight close-out itself. Plan the topic/outcome line at Step 8 to match the entry you'll write at Step 14 so the two stay in sync. The reorder constraint (Step 8 before Step 14) is non-negotiable because Step 14's `### Files Created` references the Daily Report path — the Daily Report must exist on disk first.

```markdown
# Daily Report - [Day], [Date]

## Today's Plan

[Include today's day section from This Week.md here (the `## [Day] [DD] [Mon]` heading and all items under it). Convert any `- [ ]` to plain `- ` bullets and any `- [x]` to `- ✓` — the daily report is an archival record, not a task SSOT. Keep checkboxes on live task surfaces, not in this archival copy. If This Week.md doesn't exist or today falls outside the date range, omit this section.]

## Sessions
1. [Topic] — [outcome]
2. [Topic] — [outcome]
...
N. **Goodnight: [Brief Topic Summary]** — [one-line outcome matching the Step 14 session entry]

## Outside-Agent
- [Off-Claude activity surfaced in Step 3 (pre-verification debrief) or Step 7 (additional captures) — wander/exercise/social/admin/errands done outside the assistant. Useful end-of-day signal that wouldn't otherwise be recorded anywhere. Omit section entirely if nothing to record.]

## Blockers
- [Item] — waiting on [what]

---
*Links:*
- Sessions: [[06 Archive/OpenCairn/Session Logs/YYYY-MM-DD]]
- Previous: [[06 Archive/OpenCairn/Daily Reports/YYYY-MM-DD]] (yesterday if exists)
```

**`## Blockers` is carried, not re-derived.** Start from the `## Blockers` list of the newest daily report dated before this one (by the date in its filename, never mtime) and copy each line byte-identical — a reworded blocker reads as a new one and loses its history. Delete a line only when this day's session log or the user shows it closed; add only blockers this day's session log or Step 7 surfaced. With no earlier report, build the list from those sources alone.

Ensure directory exists first:
```bash
mkdir -p "{VAULT}/06 Archive/OpenCairn/Daily Reports"
```

### 9. Route undone items from past day sections

**Carry-forward entry point / source of truth.** Accept source day sections and a destination day from the caller. For nightly close-out, default to today’s and earlier verbose sections, with tomorrow as the destination. Catch-up callers supply the missed day’s section and today as the destination.

Before collapsing a source section, move its unfinished tasks intact into the destination day section of This Week.md. Their presence in the plan is already a commitment; priority, age, workload or lack of a deadline is not a reason to remove them.

Do not route these tasks to area/project backlogs, Working Memory, Tickler or Whimsy, and do not add a routine deferral-confirmation batch. If the same intact task is already in a future day, keep that scheduled copy and remove only the duplicate. Preserve explicit deadlines and full task blocks. A direct user instruction changing a particular task still takes precedence.

For a missing destination day, use only shared §9’s **Extend the window** subsection; do not invoke its trimming pass from this step. New captures use their capture rules; they do not change this carry-forward rule.

**Critical: carry items forward intact.** Move the full item text, sub-items, checklists, and surrounding context exactly as they appear. A multi-line checklist (e.g. a sprint with Tier 1/Tier 2 items) is an active working artefact — move the entire block, not a summary. Never summarise, condense, or strip items during routing — including `[x]` items. Completed items within a block are progress context.

**Block boundary rule:** A section header (bold text line like `**Sprint Tier 1:**`) and all items beneath it until the next section header form a block. If *any* `- [ ]` item remains in a block, move the entire block (header + all items + `[x]` items). The destination should be a copy of the source block, not a reconstruction.

Preserve existing project/area links when moving items. If an item lacks a link, add one (`→ [[03 Projects/...]]` or `→ [[04 Areas/...]]`). No link for items with no project context.

Also check for sub-sections scoped to a future day. Move the entire sub-section to the appropriate future day before collapsing.

### 10. Collapse past day sections

After all undone items have been routed, collapse today's section — and any earlier days in This Week.md that are still verbose — to a one-line summary + link:

```markdown
## [emoji] [Day] [Date] — [Theme] ✅
[One sentence: what happened, what didn't, key outcome.] [[06 Archive/OpenCairn/Daily Reports/YYYY-MM-DD|Full report]]
```

**⛔ Inbound locators — capture before the rewrite, repoint after.** A collapse changes the heading text, so links to that day's section stop resolving — silently, because heading anchors sit outside the file-level link index that `obsidian unresolved` reads. For each day being collapsed, before writing the new heading, run this with `H` set to the old heading line as it stands in the file, minus its leading `## `:

```bash
H='OLD HEADING TEXT'
TW="{VAULT}/01 Now/This Week.md"
[ "$(rg -c -F -x -- "## $H" "$TW")" = 1 ] || { echo "STOP: \"## $H\" is not exactly one line of This Week.md" >&2; exit 1; }
rg -n -F -t md -g '!**/06 Archive/**' -g '!**/07 System/.Provenance/**' -- "$H" "{VAULT}"
```

`STOP` means the search did not run — fix `H`; it is never "nothing found". A run that worked always lists the heading line itself. `06 Archive` and `07 System/.Provenance` are excluded because both are frozen records (session logs, daily reports and attested snapshots keep the heading as it stood): never edit them. **Hits are candidates.** A locator is a line that links to this section — `[[…This Week#…]]`, or `[[#…]]` inside This Week.md only, since in any other note that form points at the note's own heading — or that names it in prose as a section of This Week. Never edit a line that merely mentions the date. After the collapse, repoint each locator with `locked-edit.sh --replace`, changing only the target (a link keeps its `|alias`): a reference to a task Step 9 carried forward targets the heading of the day that task is now under, copied from the file — which can be a future day, or one that already held a scheduled copy; anything else targets the day's daily report at file level (`[[06 Archive/OpenCairn/Daily Reports/YYYY-MM-DD]]`) — never the collapsed heading, which the rolling window deletes within days. Emit `Inbound locators: [old heading] — [N] hits, [K] locators, [R] repointed, [L] left (not locators)`, where N = K + L and K = R; if K ≠ R, stop and name the locators not repointed. Each repointed file goes in the session entry's Files Updated.

**⛔ Any count in that heading is read, not estimated — run the commands and show their output before writing the line.** An eyeballed tally over a long day section of near-identical bullets is the recurring defect here, and it is a one-way one: once the section is collapsed, the heading is the only surviving claim in This Week and nothing in that file can falsify it. Fuzzing the number ("several", "many") does not fix this — it still requires a judgement at write time, and it discards the volume signal that is the only reason to state a number at all.

```bash
# Items — derive from the daily report's Today's Plan block, NOT the day section. Step 8 wrote that
# block before Step 9 routed anything, so it is the pre-routing snapshot; by the time this step runs
# the day section no longer holds the items that were carried forward.
# Bound the block on `## Sessions`, NOT on a bare `^## ` — the block legitimately contains the
# copied `## [Day] [DD] [Mon]` heading, which closes a naive bound immediately and silently yields 0.
REPORT="{VAULT}/06 Archive/OpenCairn/Daily Reports/YYYY-MM-DD.md"
awk '/^## Today.s Plan/{f=1;next} /^## Sessions/{f=0} f&&/^- /'  "$REPORT" | wc -l   # M — total planned
awk '/^## Today.s Plan/{f=1;next} /^## Sessions/{f=0} f&&/^- ✓/' "$REPORT" | wc -l   # N — closed
# Sessions — the close-out entry is not appended until Step 14, so this counts the day's work
# sessions, which is what the heading means. The daily report's Sessions list carries one more.
rg -c '^## Session ' "{VAULT}/06 Archive/OpenCairn/Session Logs/YYYY-MM-DD.md" || echo 0
```

Then phrase by the result:
- **N == M** → `All planned items closed across [S] sessions`. Drop the item total: `All` is the whole assertion, and the number adds nothing a reader acts on while still needing maintenance.
- **N < M** → `[N] of [M] closed`, naming what carried. Here the ratio *is* the signal, so both numbers stay.

A count that changes because the underlying fact changed (an item confirmed done after the fact) is a correct edit, not a defect — re-derive and rewrite. The defect is a number that was never read in the first place.

**⛔ Required output — emit the derived counts before writing the heading.** Format:

```
Collapse counts: [Day] [DD] [Mon] — items [N]/[M] (source: daily report Today's Plan) · sessions [S] (source: session log, pre-close-out) ✓
```

This is the load-bearing half. The instruction to count accurately has always been implicit, so what recurs is estimating without noticing — a collapse heading with no emitted count line beside it is the failure signature, and after the collapse nothing in This Week can contradict the number.

Sweep all past days, not just today. Earlier days may still be verbose if a previous `$goodnight` run predates this step or was interrupted. Any day before tomorrow should be a one-liner.

Nothing with `- [ ]` should remain in any collapsed section. If it does, something was missed in step 9 — route it before collapsing.

**Then collapse — last, once the locators are captured and any counts are derived and shown.** Two calls, so nothing you have not read is removed. Pass the day's current heading line exactly as it stands in the file:

```bash
TW="{VAULT}/01 Now/This Week.md"; LE="{VAULT}/.claude/scripts/locked-edit.sh"
"$LE" "$TW" --show-section '## OLD HEADING LINE'   # prints the section; stderr ends "Section sha256: <hash>"
cat << 'EOF' | "$LE" "$TW" --delete-section '## OLD HEADING LINE' <hash>
## [emoji] [Day] [Date] — [Theme] ✅
[One sentence.] [[06 Archive/OpenCairn/Daily Reports/YYYY-MM-DD|Full report]]
EOF
```

Read what `--show-section` printed before the second call: a `- [ ]` in it means Step 9 missed an item, so route that item first and show the section again. Exit 2 on `--delete-section` means the section changed after you read it (or a code fence in the file is never closed): nothing was written, so show it again and redo the call with the new hash.

### 11. Maintain rolling 7-day horizon

Run the This Week.md Rolling Window Maintenance procedure (see `_shared-rules-planning.md` Section 9). This ensures the file always extends 7 days ahead, preventing it from shrinking as days get collapsed.

### 12. Log Goodnight Session (deferred to Step 14)

The goodnight session entry is written at Step 14 via `write-session.sh --auto-number` — nothing is written at this step. (The step is kept as a stub so cross-references to Steps 13–18 stay stable.)

### 13. Concurrent-Session Detection (folded into Step 14)

Pre-write probing is superseded: the goodnight session's number is resolved atomically at write time in Step 14 via `--auto-number`, and Step 14's **post-write reconciliation** compares the assigned N against Step 2's displayed baseline — which detects every session added since Step 2, with no probe-to-write blind window and no cross-Bash-call shell-variable hazard (the old probe's `$SESSION_FILE` was never defined in its own block, so it silently false-passed). Proceed to Step 14.

### 14. Append Goodnight Session Entry

**Use the write-session script with `--auto-number`** — same as `$park`. This avoids the permission system corruption bug from inline flock commands AND resolves the session number atomically inside the file lock (eliminates collision against parallel $park or $morning catch-up invocations).

**Note on section structure:** The goodnight session log intentionally omits the `### Next Steps / Open Loops` section used in `$park` session entries. For goodnight specifically, the Pickup Context line below already names tomorrow's anchors (the entire purpose of goodnight is forward-routing), so the two sections would duplicate. Files Created and Files Updated are split per session-log convention — a freshly created Daily Report belongs under Created, not Updated.

**Body only — no `## Session N - …` heading.** The script prepends the heading inside the lock. (The script will reject stdin starting with a `## Session N` heading.)

```bash
TODAY="$(date +%F)"
SESSION_DIR="{VAULT}/06 Archive/OpenCairn/Session Logs"
SESSION_LOG="$SESSION_DIR/$TODAY.md"
[ "$(dirname "$SESSION_LOG")" = "$SESSION_DIR" ] || { echo "ERROR: session log escaped current-log directory: $SESSION_LOG" >&2; exit 1; }
cat << 'EOF' | "{VAULT}/.claude/scripts/write-session.sh" "$SESSION_LOG" --auto-number "Goodnight: [Brief Topic Summary]" "HH:MMam/pm"
### Summary
[2-3 sentences covering what was reviewed/decided/updated during goodnight]

### Key Insights / Decisions
- [Any significant decisions made during close-out]

### Files Created
- [Daily Report path — typically the only newly-created file. Omit section if no new files.]

### Files Updated
- [List any files modified during goodnight]

### Files Deleted
- [Any vault files deleted during goodnight — path + why removed. Omit section if none.]

### Pickup Context
**For next session:** [One sentence for tomorrow morning]
EOF
```

**Capture N from stdout.** The script's final line is `Session number assigned: N`. This N is the canonical source for downstream steps (15c sub-agent brief, completion message in Step 18). Display it explicitly:

```
Session number assigned: N
```

**Post-write reconciliation (the concurrent-session check):** Compare the assigned N against `STEP2_LAST_N` — the baseline displayed at Step 2's checkpoint (use the literal value from the page, not internal memory). If `N − 1 > STEP2_LAST_N`, sessions `STEP2_LAST_N + 1` through `N − 1` were added by parallel instances since Step 2. For each: read its summary from the session file (the only exception to the write-only-after-initial-read rule — read just the new session blocks, not the whole file), update your working memory, and patch the daily report's Sessions list (Step 8 output, already on disk) to include it — insert and renumber so the goodnight entry stays last. If one of them demonstrably closes a blocker, delete that line from the report's `## Blockers` (delete, never reword). Display: `Reconciliation: baseline X, assigned N → M missed session(s) patched` (or `→ no missed sessions`).

**First session of the day** (no session file exists yet): no special flag needed — the same invocation lays down the `# Claude Session - DATE` header automatically when the file is absent/empty, assigns N=1, and writes atomically inside the lock.

### 14a. Update project docs

If a project's state materially changed today, update that project's doc in `03 Projects/` — rewrite its existing current-state/action content to match reality, preserving the document's structure, via `locked-edit.sh` (see `_shared-rules.md` §5). No change, no edit.

**Also propagate state changes disclosed during goodnight conversation, not just session-file outcomes.** State updates revealed in the Pre-Verification Debrief (Step 3) or Additional Captures (Step 7) are authoritative even when session logs don't yet reflect them. When the user discloses that a waited-on event has landed (a reply received, a decision made, an external action completed), rewrite the affected current-state/action content in the relevant project doc *and* any matching area hub in `04 Areas/`. A stale "awaiting X" line will mislead the next `$morning` or `$pickup` into expecting something that has already happened.

**Why this step is here (not at the end):** project-doc edits ahead of the audit means the audit's Layer 3 propagation check covers them, and provenance hashing in Step 17 captures the final state.

### 14b. Value check (SOURCE)

Before delegating the audit, run **`_shared-rules.md` §19** (already in context — Step 0 reads it in full) over every file this goodnight wrote: Daily Report, This Week.md, Tickler, project files, session entry. §19 is the single source of truth for what the check covers and for its required output line — read it there rather than working from any restatement, including one here.

### 15. Delegate $audit to a fresh sub-agent

The audit runs in a fresh context via `collaboration.spawn_agent`, NOT inline. $goodnight is 18 steps with heavy state propagation; inline audit at step 15 suffers cognitive load, recency bias on just-edited files, and enumeration scoping limited to "what $goodnight directly edited" rather than "what world-state changes the day caused." Sub-agent delegation eliminates all three. The same applies here a fortiori — $goodnight does heavier state propagation than $park.

**(a) Enumerate identifiers IN MAIN SESSION (required checkpoint).** The enumeration discipline must stay in the main session — it's the load-bearing defence against silent miss-pattern. List every identifier as `old → new` pairs, plus any *new* named state introduced, before dispatching the sub-agent.

$goodnight's distinctive Layer 3 substrate (use as a checklist when enumerating):
- Completed-loop identifiers (text marked `[x]` in This Week.md, deleted from Tickler, or marked done in a project file via Step 4 / Step 6)
- Items moved between day sections in Step 9 (old day → new day)
- Day-section collapses in Step 10 (verbose section → one-liner; the outbound `[[…|Full report]]` link must resolve to a real file) and their inbound-locator repoints (old target → new target, per file)
- New day sections added by rolling-window maintenance in Step 11
- Project-doc Current Objective / Next Actions changes from Step 14a (especially propagation-from-debrief edits)
- NEW: today's daily report file at `06 Archive/OpenCairn/Daily Reports/YYYY-MM-DD.md`
- NEW: the goodnight session entry itself at Session N in today's session log

```
Identifiers in scope:
- "old value" → "new value" (where: file path)
- NEW: "value" introduced (where: file path)
[OR (nil case, formatted as enumerated checklist, not bare assertion):]
- Completed-loop status flips: none
- Day-section moves (Step 9): none
- Day-section collapses (Step 10): none / Daily-report wikilink targets: none broken / Inbound-locator repoints: none
- Rolling-window day additions (Step 11): none
- Project-doc Current Objective/Next Actions changes: none
- New named state introduced (daily report, goodnight session entry): always at least these two — list them
- Commits pushed today (each hash is its own identifier — see the push-side rule below): none
→ All Layer 3 substrate enumerated; propagation check follows in sub-agent.
```

**Commits pushed are their own identifier class** — enumerate each hash from the day's sessions and check the repository's hub per **`_shared-rules.md` §17** (already in context — Step 0 reads it in full). A pushed commit has no textual footprint in the vault, so no content grep reaches the hub that is supposed to record it.

**The nil case is not a free pass.** "Nothing changed" is a positive claim requiring the explicit checklist. **New state introduced is the most-missed category** — every $goodnight introduces at minimum a daily report file and a goodnight session entry, so a "nothing introduced" claim is always wrong by construction.

**(b) Identifier count integrity check before despatch.**

Count the identifiers in your enumeration block. When you write the sub-agent brief in (c), embed the enumeration verbatim — do NOT re-construct from memory (typed-from-memory lists silently drop entries). Display:

```
Identifier count check: enumeration N → brief N ✓
```

If mismatch, regenerate the prompt from the enumeration block, not from memory.

**(c) Spawn the sub-agent** via `collaboration.spawn_agent`. **Collect its report before any later step runs** — gate on collection, not on despatch mode: whether the seat runs in the foreground is a harness detail you do not control, so "collected before the next step" holds either way where "despatch it in the foreground" silently doesn't. `collaboration.wait_agent`'s return is the only signal it has finished; never substitute a proxy that cannot tell "running" from "finished".

The prompt must be **self-contained** — the sub-agent has zero $goodnight context. Include verbatim:

- **Vault path** (resolved from Step 0): the absolute path, not `{VAULT}`
- **Session log path**: `<vault>/06 Archive/OpenCairn/Session Logs/YYYY-MM-DD.md` and the session number N (from Step 14's `Session number assigned: N` stdout)
- **Session id** (this session's, from `echo $CODEX_THREAD_ID`, resolved before despatch): instruct the sub-agent to prefix every `locked-edit.sh` call with `env OPENCAIRN_SESSION_ID=<id>` so its edits ledger under this session
- **Daily report path**: `<vault>/06 Archive/OpenCairn/Daily Reports/YYYY-MM-DD.md`
- **File list** — every file $goodnight touched, embedded as a newline-separated list inline in the prompt. Build it from two sources, not from memory (typed-from-memory lists silently drop entries): (1) verbatim from Session N's `### Files Created` and `### Files Updated` sections as written at Step 14, plus (2) the named post-Step-14 edits — any Step 14a project-doc writes and any daily-report patches from Step 14's reconciliation. Display the count before despatch:
  ```
  File list: C from Session N + P post-Step-14 → F files in brief ✓
  ```
- **One-paragraph summary** of what $goodnight accomplished today (loops marked complete, items routed, day-sections collapsed, project-doc propagation done)
- **Enumerated identifiers** verbatim from (a)
- **⛔ Session-boundary attribution** — read **`_shared-rules-reviewer.md` §20** before preparing the audit, then embed it in the brief. The file list above is the attribution boundary; the vault's auto-save commit window is not. Without it a sub-agent reads a concurrent session's file out of a shared commit and writes a self-consistent record of work this run never did.
- **⛔ Pre-state authority** — where a finding would turn on what a file looked like *before* this run, the authoritative pre-state is the main session's own Read, not the repository's history. When a status, tick, count, or "was something dropped" claim is in scope (routing verdicts and day-section collapses put one in scope on most nights), embed the relevant pre-edit content in the brief, and instruct: "Do NOT reconstruct pre-state from the vault's auto-save git. Commit boundaries there are arbitrary and unrelated to session boundaries, so a state that existed only between two commits appears in neither — an item the user changed shortly before this run can read as unchanged in the last commit and be gone from the next. Any git-derived 'silently dropped' or data-loss finding must be verified against the specific commit's diff content and timing, and against the pre-state embedded above, before you remediate it." Without this clause an auditor can produce a confident, fully-remediated finding about work that was never lost — and the remediation itself then corrupts an accurate record. Sibling of `_shared-rules-reviewer.md` §20: that governs *attribution* from auto-save git (who did what), this governs *pre-state* (what it was before).
- **Script paths** the sub-agent will need (substitute resolved vault):
  - `<vault>/.claude/scripts/update-session-section.sh` — for session-log section edits (preserves flock concurrency safety)
  - `<vault>/.claude/scripts/backfill-files-updated.sh` — to record any remediation edits into Session N's Files Updated section. **⛔ `### Files Updated` belongs to the main session — the seat appends to it and NEVER `--replace`s it.** The seat's view of that section is frozen at its own read, while the main session keeps writing to it after despatch (Step 15(f)'s backfill is mandatory and runs *after* this audit); a `--replace` from the stale view silently drops every row added since, and nothing downstream detects it. **Caveat the append path cannot serve:** the script dedups by normalised path and *discards* the new text for a file already listed (it prints the unwritten row and exits 3 when nothing was written), so extending an already-listed entry's description is impossible by appending — **report it in your findings and let the main session rewrite the section.** Do not reach for `--replace` to work around the dedup; that is the exact move this rule forbids
  - `<vault>/.claude/scripts/write-tickler.sh` — if routing surfaces an open loop
  - `<vault>/.claude/scripts/locked-edit.sh` — for remediation edits to planning/hub files (This Week, Tickler, project/area hubs)
- **Locking constraint:** "For session-log edits, use `update-session-section.sh`. For planning/hub files (This Week, Tickler, `03 Projects/`, `04 Areas/` hubs), use `locked-edit.sh` (see `_shared-rules.md` §5). For other vault files, a direct write is acceptable. A lockless write races with concurrent parks/goodnights on planning files and silently clobbers."
- **Audit protocol pointer**: read `~/.codex/skills/audit/SKILL.md` Phase 2 (Layers 1-5 — Layer 0 deliberately out of scope: this bookkeeping audit's frame is fixed by the brief) first; do NOT recall the protocol from memory.
- **Special-focus instruction** (the empirically-missed Layer 3 category): "**Phase/status framings rendered historical by session actions** is the category prior inline audits silently miss. For each project/area hub touched by today's session work, search for prose like 'upcoming', 'planned', 'pending', 'will', 'next', 'forthcoming', 'awaiting' near references to work completed today; flag every hit. This includes goodnight's OWN `Pickup Context` / Daily Report describing goodnight's already-run steps (this audit, item routing) as forthcoming — written before this audit fired, so a 'the audit will…' framing is already stale; reframe to completed/current tense. Do NOT flag legitimate forward-routing to tomorrow's anchors — Pickup Context naming tomorrow's work is its purpose, not staleness. Also: read each touched project/area hub IN FULL, not just the subsection that was edited."
- **Read-coverage backstop**: "If your file list contains more than 5 project/area hubs to read in full, split the audit across multiple passes rather than truncating any read. Report bytes-read per hub in your final report so the main session can verify plausible coverage."
- **Layer 5 specifics for $goodnight**: "Trace tomorrow's $morning against current This Week.md / Tickler / project docs. Will it surface the right items, or is anything we marked complete still showing as open? Will collapsed-day one-liners' `[[…|Full report]]` links resolve? Will rolling-window day additions be visible to $morning's Step 6 maintenance pass without conflict?"
- **Authority**: remediate inline using Edit / Bash / scripts. Re-audit after every fix; iterate until clean.
- **Report format expected back**: per-layer findings (or nil-case statements that explicitly name what was checked — generic affirmations like "approach is sound" are not acceptable); list of remediation edits with file paths + summary of change; bytes-read-per-hub for read-coverage verification; final clean-pass confirmation OR "could not clean — N issues remain because X" if iteration stalled.

**(d) Receive the sub-agent's report.** Display its summary in your response. Do NOT re-run the audit yourself; trust the sub-agent's report (the whole point is that you don't have the fresh-context advantage).

**(e) Process the report.** If the sub-agent made remediation edits, verify it called `backfill-files-updated.sh` for them. If not, run the backfill yourself using the file list it reported. If read-coverage report shows any hub at suspicious-low byte count (well below the file's actual size), re-despatch a focused sub-agent on that hub.

- **Sanity-check remediation edits that change a factual claim, before accepting them.** "Trust the report" (step d) means don't re-run the full audit — it does *not* mean accept a remediation blind. If the sub-agent edited a file to "correct" a numeric/ordinal/identifier claim (counts, FIFO trims, dates, link targets — the categories it's documented to drift on), verify the correction against the **live file** (and your own in-session grep evidence) before letting it stand. The vault `.git` is auto-save with arbitrary commit boundaries, so a sub-agent's `git diff` does **not** reliably reconstruct pre-goodnight state — a partial-commit diff can flag a real edit as "fabricated." On a confirmed false positive, revert the sub-agent's edit and restore the accurate text. **Caught 2026-06-03** (in `$park`, which shares this audit-delegation design): an audit sub-agent misread an auto-save `git diff` and rewrote an accurate FIFO-trim line as a fabrication.

**(f) Mandatory Files-Updated backfill (runs every goodnight, not just when the audit finds something).** Step 14 wrote the session log *before* the Step 14a project-doc edits, the Step 14 daily-report reconciliation patches, and this audit. Those edits are therefore absent from the session entry's `### Files Updated` unless backfilled. Reconcile now, unconditionally:

1. List every file goodnight touched at Step 11 onward (project docs from 14a, daily-report patches from Step 14's reconciliation, any audit remediation from (e)).
2. Diff that list against the `### Files Updated` already in Session N's entry.
3. Pipe the missing lines through `backfill-files-updated.sh "<session-file>" N`.

This is the F4 fix: the session log's footprint record must match what goodnight actually changed, even on a clean audit. Skipping it because "the audit was clean" is the failure mode — a clean audit says nothing about whether the post-Step-14 edits were recorded.

**⛔ CHECKPOINT — required outputs:**
1. Identifier enumeration block (real or nil-case checklist) in main session response
2. Identifier count check line (`enumeration N → brief N ✓`)
3. `collaboration.spawn_agent` invocation
4. Display of sub-agent's report
5. One of: `✓ Audit: clean pass` OR `🔧 Audit: N findings fixed and re-audited clean — see [paths]`
6. `✓ Files Updated backfilled: M post-Step-14 file(s) reconciled into Session N` (M may be 0 only if the entry already listed every post-Step-14 edit)

You cannot proceed to Step 16 without all six. If you find yourself walking the audit layers yourself in $goodnight's main response, STOP — that's the inline-audit failure mode this step exists to prevent. Spawn the sub-agent.

**Why auto-run + delegate:** $goodnight propagates state across This Week.md, Tickler, project docs, and area hubs (Steps 4, 9, 10, 11, 14a). Layer 3 propagation gaps are the highest-risk failure mode — a "marked complete" loop that didn't propagate to a project hub will silently mislead tomorrow's $morning or $pickup. Inline audits empirically rubber-stamp these gaps; sub-agent delegation eliminates the three mechanisms causing the rubber-stamp (enumeration scoping, cognitive load, recency bias). 

### 16. Export Session Transcripts

Export today's verbatim session transcripts to the vault. Claude Code auto-deletes JSONL session files after `cleanupPeriodDays` (30 by default) — this preserves them as searchable markdown. Takes <1 second. The exporter covers Claude Code sessions and Codex rollouts alike (`codex-<id>` sections).

```bash
python3 "{VAULT}/.claude/scripts/export-session-transcripts.py" "{VAULT}" --days 7 --all-projects
```

**Use `--all-projects` (cwd-independent).** The transcript file is date-canonical (one `YYYY-MM-DD.md` per day) and the script overwrites it wholesale. A single-project export (the old `cd <launch dir>` + cwd-keyed default) regenerates that file from only the launch project's sessions, silently dropping any same-day sessions from *other* project directories — so on a multi-project day Step 17 would then hash an incomplete transcript as if it were the final daily record. `--all-projects` sweeps every project and merges the day, so it's complete and cwd-independent (no `cd` needed; do NOT use `--fallback-any-project`).

**`--days 7`, not `--days 1` (boundary-day completeness).** The script dates each transcript file by JSONL **mtime**, then regenerates every date touched within the window. A narrow `--days 1` window regenerates *yesterday* (when a session ran late) from only the in-window subset of its sessions — a *partial* overwrite of a complete, possibly provenance-stamped file, which Step 17 would then hash. A 7-day window regenerates each touched day **completely**, so no partial clobber. Matches C1.h + `$weekly-hygiene`; re-exporting 7 days costs <1s.

**Re-export unconditionally, even if $park already exported today.** The script regenerates the whole day file in <1 second, and Step 17 hashes the transcript as *final* — a skip-if-exists here would OTS-stamp a file missing everything since the last park, unconditionally including this goodnight conversation itself.

Output goes to `{VAULT}/06 Archive/OpenCairn/.Session Transcripts/YYYY-MM-DD.md`. Report the count in the close message.

### 17. Process Provenance Flags

Check for provenance flag files created during today's sessions:
```bash
# Substitute the literal YYYY-MM-DD from Step 1 — do NOT re-derive the date here. A shell variable
# set in a prior shell call does not survive to this one (an empty expansion would glob ALL
# pending flags and stamp/delete the wrong days' artefacts), and an inline `date` re-derivation in a
# run that has crossed local midnight globs tomorrow's prefix, matches nothing, and strands the flag.
ls "{VAULT}/07 System/.Provenance/pending/YYYY-MM-DD"*.md 2>/dev/null
```

**⚠ Ordering dependency:** Steps 14-16 MUST complete before this step. The session log and transcript must be in their final state before hashing — if you hash then append, the hash is immediately invalid and the OTS stamp covers the wrong content. This is the single most common execution error in this skill. The audit step (15) and transcript export (16) both potentially modify the session log and transcript, so both must finish before provenance hashing.

If any flags exist, process each one:
1. Read the flag's tag and work product list. Compare any "Hashed Immediately" hash with current bytes; announce a changed work product and pass the old hash as `--supersedes`.
2. Use `$provenance` Step 5's `provenance-write.py attest` for every required target: each work product, the exported transcript, and the final session log. Bind the literal date from Step 1. The writer retains exact snapshots for all of them, stamps the staged bytes, and appends validated rows under the log's canonical lock. Never directly append log rows or rewrite earlier attestations.
3. Run Step 5's `provenance-finish.sh` for each flag. It holds the flag's canonical lock and validates same-tag rows, current digests, retained snapshots and any pending/confirmed proofs for **all** required targets before removing the flag. Any failure keeps it in `pending/` for `$weekly-hygiene`; report partial processing.

If no flags exist, skip silently. See `$provenance` for flag file format and full hashing instructions.

### 18. Close

```
✓ Report saved: 06 Archive/OpenCairn/Daily Reports/YYYY-MM-DD.md
✓ Session logged: 06 Archive/OpenCairn/Session Logs/YYYY-MM-DD.md (Session N)
✓ Audit: clean pass [OR "🔧 Audit: N findings fixed and re-audited clean — see [paths]"]
✓ Transcripts exported: N sessions → 06 Archive/OpenCairn/.Session Transcripts/YYYY-MM-DD.md
✓ Provenance: N files hashed [OR "no flags"]
Goodnight.
```

The nightly run ends here. Enter the following sections only through the mode selection after Step 0.

## Catch-up mode

Run Step 0 first. Use the caller’s verified current date as the destination date; if absent, read `date +%F`. Scan the recent dates in C1, reconcile their late sessions in C2, then return to the caller. Do not run the normal interactive close-out or close today’s section. C1 supplies the past-day overrides to the shared numbered steps; those steps remain authoritative for report format, carry-forward, collapse, audit, export and provenance.

Return the caught-up dates with their report paths, any incomplete audit/provenance work, and the dates awaiting C3’s debrief. Keep this handoff in the calling session; no extra tracking file. The caller resumes its landscape before invoking C3. C2 also checks days whose close-out already existed.

### C1. Catch up missed close-outs

Scan backwards from yesterday up to 3 days (to catch multi-day gaps from travel/offline). For each day, in chronological order:

**Catch-up path invariant:** each of these recent days still writes directly to `Session Logs/YYYY-MM-DD.md`, not `Session Logs/YYYY/YYYY-MM-DD.md`. Derive the path from the loop's day value and assert its parent inside the write call; never reuse a year-subfolder path found while reading older history.

1. Check if a daily report exists: `{VAULT}/06 Archive/OpenCairn/Daily Reports/YYYY-MM-DD.md`
2. Check if a session log exists: `{VAULT}/06 Archive/OpenCairn/Session Logs/YYYY-MM-DD.md`
3. If no session log or no sessions → skip silently (nothing to close out).
4. **Skip the day only if it is closed out on both surfaces** — the daily report exists **and** the session log carries a close-out entry (`^## Session [0-9]+ - Goodnight:`, `Goodnight catch-up via /morning`, or `Goodnight catch-up via $morning`). Accept both catch-up invocation forms so pre-port days are not repeated. The report is written at C1.b but the session entry, audit and provenance come at C1.e/g/h, so a report-only test marks an interrupted catch-up permanently closed and nothing downstream repairs it — C2 skips days with no close-out session, and only provenance has its own net.
5. Otherwise → **/goodnight was missed, or a previous catch-up did not finish.** Run a lightweight catch-up, skipping any sub-step whose artefact is already present and complete (do not duplicate an existing daily report — verify and extend it instead):

   a. Read the day's session log and its day section in This Week.md. **Also sweep for stranded Claude-internal work product from that day** — `$park` Step 4's third check, day-scoped, since a day reaching catch-up is by definition a day no park or goodnight closed out. Bound the window with two explicit dates (`+1 day` inside `-newermt` is not portable; derive the upper bound with `date -d "<day> +1 day" +%F`):

      ```bash
      find ~/.claude/plans -maxdepth 1 -type f -newermt "<day>" ! -newermt "<next day>"
      ```

      Read each hit that hasn't already been migrated — a sub-agent's output (`*-agent-*.md`) is a separate document from its parent plan and gets its own verdict, per that step. Migrate standalone reference material to its semantic vault home and list what you migrated in C1.e's Files Created; leave spent execution plans.
   b. **Execute Step 8** for the caught-up date, using that date’s pre-routing day section and session log. Verify and extend an existing report instead of overwriting it. Use `Goodnight catch-up via $morning` as the close-out topic, matching C1.e. Step 8's Blockers carry rule holds here: the source is the report before the caught-up date and the evidence is that day's session log, never what is known today; an existing report's `## Blockers` is checked against that rule and corrected only where it departs from it. **Omit `## Outside-Agent` until C3 supplies the deferred debrief.**
   c. **Execute Step 9** with source = this caught-up day’s section and destination = today’s section in This Week.md. Read and execute that step; do not reproduce its routing logic here.
   d. **Collapse the day section** to a one-liner + daily report link — execute Step 10, **including its inbound-locator capture and repoint and its count-derivation rule**: any item or session count in the heading is read off the commands that step gives, never estimated, and the derived values are shown. Only collapse this iteration’s day, after C1.c has preserved its open tasks. Other days are handled by their own iteration; do not apply Step 10’s nightly default to uncaught days.
   e. **Log a catch-up session** to the day's session file via write-session.sh with `--auto-number` (resolves N atomically inside the file lock — eliminates collision against parallel $park or $goodnight invocations):

      ```bash
      CATCHUP_DAY="YYYY-MM-DD"  # replace with this loop iteration's actual day
      case "$CATCHUP_DAY" in [0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]) ;; *) echo "ERROR: unsubstituted/invalid catch-up day: $CATCHUP_DAY" >&2; exit 1 ;; esac
      SESSION_DIR="{VAULT}/06 Archive/OpenCairn/Session Logs"
      SESSION_LOG="$SESSION_DIR/$CATCHUP_DAY.md"
      [ "$(dirname "$SESSION_LOG")" = "$SESSION_DIR" ] || { echo "ERROR: catch-up log escaped current-log directory: $SESSION_LOG" >&2; exit 1; }
      cat << 'EOF' | "{VAULT}/.claude/scripts/write-session.sh" "$SESSION_LOG" --auto-number 'Goodnight catch-up via $morning' "HH:MMam/pm"
      ### Summary
      [Brief summary of what was generated/routed]

      ### Files Created
      - 06 Archive/OpenCairn/Daily Reports/YYYY-MM-DD.md

      ### Files Updated
      - [Items routed by C1.c/d; files repointed by Step 10's inbound-locator rule]

      ### Pickup Context
      **For next session:** [pickup pointer for today]
      EOF
      ```

      Body only — no `## Session N` heading; the script prepends it. Capture the assigned N from the `Session number assigned: N` stdout line for the audit brief in C1.g.
   f. **Skip** (not part of catch-up): the debrief prompt — $morning invokes C3 for each caught-up day. (Provenance processing is **not** skipped — see C1.h, which runs after the audit.)
   g. **Run $audit via a fresh sub-agent on the catch-up** — apply $goodnight Step 15 sub-steps (a)–(f) verbatim (including the (f) Files-Updated backfill, scoped to the catch-up session entry). Catch-up performs the same state-propagating actions as $goodnight (status flips on items the user may have already marked, day-section collapses, item routing, session-log writes) and is subject to the same Layer 3 failure mode (stale phase/status framings in project hubs and area files that referenced the caught-up day's now-historical state). Inline audit suffers the same cognitive-load / enumeration-scoping / recency-bias mechanisms $park's audit step names. **Unconditional, not gated on substrate size:** even a "trivial" catch-up writes a NEW daily report file + NEW catch-up session entry, so the enumeration floor is never zero — same logic as $goodnight Step 15.

      Substrate-specific notes (when applying Step 15's checklist to the catch-up):
      - Identifier enumeration (15a) substrate is: completed-loop flips on `[x]` items the day section already had (rare — C1 generally inherits state, doesn't flip), day-section moves from C1.c, day-section collapses and inbound-locator repoints from C1.d, NEW daily report file from C1.b, NEW catch-up session entry from C1.e. The last two are always present.
      - Sub-agent brief (15c) must include: vault path (absolute), the caught-up day's daily report path, the catch-up session log path with session number (from C1.e's `Session number assigned: N` stdout), the file list (different from $goodnight's — assemble from C1.b/c/d/e edits), one-paragraph summary of what the catch-up did, the enumerated identifiers verbatim, script paths (`update-session-section.sh`, `backfill-files-updated.sh`, `write-tickler.sh`), the locking constraint, the audit-protocol pointer (`~/.codex/skills/audit/SKILL.md` Phase 2 Layers 1–5 — Layer 0 deliberately out of scope for this bookkeeping audit), the special-focus instruction on phase/status framings rendered historical, the read-coverage backstop with bytes-read reporting, authority to remediate inline and iterate until clean, and the expected report format. **Layer 5 specific for the catch-up:** trace $morning’s landscape pass (its Step 3) against the post-catch-up SSOT state — anything the catch-up just routed should surface correctly when $morning Step 3 runs.
      - **Pre-state authority in the audit brief:** the authoritative pre-catch-up state is the main session's own Read of This Week.md at catch-up time — embed the relevant pre-collapse content in the brief if the auditor needs it. Do NOT direct the sub-agent to reconstruct pre-state from vault auto-save git: commit boundaries are arbitrary, and a morning-window commit typically captures the *user's own* pre-/morning edits, so its diff mis-attributes user actions to the catch-up. Any git-derived data-loss finding must be verified against the specific commit's diff content and timing before remediation.
      - After the sub-agent returns: verify any remediation edits were backfilled to the catch-up session entry via `backfill-files-updated.sh`. If not, run the backfill from the file list the sub-agent reported.
   h. **Process this day's provenance flag(s), if any.** Glob `{VAULT}/07 System/.Provenance/pending/` for files whose name begins with **this caught-up day's own date** (`YYYY-MM-DD` — the loop's current day, never today, and never an empty/unsubstituted date, which would match every pending flag and process the wrong days). `$provenance` writes one flag per tag, so a day may have **more than one** — process **each** matching flag. If none match, skip silently. A match means `$provenance` ran that day but `$goodnight` never processed it — process it now per `$goodnight` Step 17 (mechanism) and `$weekly-hygiene` Step 13a (past-day adaptation), with these specifics:
      - **Must run after C1.g completes — including its post-return Files-Updated backfill — never before.** Same strict ordering as $goodnight Steps 15→16→17: the audit can still modify the caught-up day's session log, and the hash must cover its final state. Hashing before the audit produces an immediately-invalid proof — the single most common provenance execution error.
      - **Export that day's transcript first, then confirm the file landed.** A missed-goodnight day may never have been exported (only $park and $goodnight export transcripts; the exporter covers Claude Code sessions and Codex rollouts alike). Run `python3 "{VAULT}/.claude/scripts/export-session-transcripts.py" "{VAULT}" --days 7 --all-projects` — `--all-projects` **unconditionally** (morning has no `cd` to the day's launch directory, and which project a past session launched from is unknowable during catch-up; the flag is cwd-independent), and `--days 7` because the script windows and dates each transcript file by the JSONL's **mtime**, not the logical session date — a tight window can miss or mis-date the boundary day, so clear it with margin. **Then verify `06 Archive/OpenCairn/.Session Transcripts/YYYY-MM-DD.md` exists for the caught-up date.** If it does not, do **not** hash a missing/empty file and do **not** delete the flag — hash what you can, leave the flag in `pending/` for `$weekly-hygiene`, and report it as partially processed.
      - **Attest every required target with the shared writer:** the listed work products (recompare immediate hashes; on change, use $provenance Step 5's superseding re-hash); that date's exported transcript; that date's final session log (`06 Archive/OpenCairn/Session Logs/YYYY-MM-DD.md`, after C1.e + C1.g). `provenance-write.py attest` retains their exact preimages, so later export/link-healing does not make proof recovery depend on source JSONL retention. `provenance-finish.sh` is the **sole flag-removal gate**: it validates every current digest, retained snapshot and required selected proof under the flag lock. Any failure leaves the flag pending for $weekly-hygiene; no direct log append, manual snapshot copy or row-only deletion check. See $provenance for the commands.
   i. Display:
      ```
      ⚠ $goodnight was not run for [Day DD Mon] — caught up:
      ✓ Daily report generated: 06 Archive/OpenCairn/Daily Reports/YYYY-MM-DD.md
      ✓ [N] undone items routed from [Day]'s section
      ✓ [Day] section collapsed
      ✓ Audit: clean pass [OR "🔧 Audit: N findings fixed and re-audited clean — see [paths]"]
      ✓ Provenance: N files hashed [OR "⚠ Provenance: partially hashed — flag retained for $weekly-hygiene" if a required target was missing; omit the line entirely if no flag existed]
      ```

### C2. Reconcile post-goodnight sessions

For each date in C1’s lookback window that now has a daily report (whether from $goodnight or C1), check for late sessions:

1. Read the day's session log.
2. Find the goodnight session — match pattern `^## Session [0-9]+ - Goodnight:` (colon required to distinguish from sessions that happen to mention "goodnight" in their topic). Also match both `Goodnight catch-up via /morning` and `Goodnight catch-up via $morning` for catch-up sessions.
3. If no goodnight session found → skip.
4. Extract the goodnight session number. Check if any sessions exist with a higher number.
5. If no post-goodnight sessions → skip silently.
6. If post-goodnight sessions found:
   - Read each post-goodnight session block.
   - **Drop the ones already reconciled.** Read the daily report's `## Sessions` list and skip any late session whose number or topic already appears there. C2 re-runs over every day that has a report — a second $morning the same day, or tomorrow's pass over yesterday, hits the same sessions again, and the session-log test at step 4 stays true forever. If every candidate is already present, skip the day silently (no append, no note, no display).
   - Add the remaining late session(s) *inside* the daily report's `## Sessions` section. Re-read from that heading through the next H1/H2 (or EOF), compose the complete old and replacement blocks, and apply one `locked-edit.sh --replace` call under §5. **Never use `--append`:** it writes at EOF, while `## Sessions` is followed by other sections, so the rows would land outside the list while still fooling a grep-based success check. Any completions they contain live in the SSOT files (This Week.md, Tickler, project files), not the report.
   - **`## Blockers`: delete a line that a late session demonstrably closes** — from this report and from any later report already carrying it — with `locked-edit.sh --replace`. Never reword a line; without that evidence leave the list alone. The next close-out carries from this list, so a closed blocker left here is carried indefinitely.
   - Do NOT modify `## Today's Plan` or `## Outside-Agent` — they were set deliberately at close-out and remain valid.
   - Add a note at the end of the Sessions section naming only the sessions this pass appended: `*Sessions N–M added by $morning (ran after close-out)*`. If an earlier pass already left such a note, extend its range rather than adding a second one.
   - **Refresh the collapsed day's one-liner in This Week.md.** $goodnight Step 10 collapsed the day to `## [emoji] [Day] [Date] — [Theme] ✅` + one sentence + report link. If the late sessions change what the day amounted to, extend that sentence to mention them; otherwise leave it alone. (The collapse format carries no session count — do not invent or parse one.)
   - Display:
     ```
     ✓ Reconciled: [N] post-goodnight session(s) added to [Day DD Mon]'s daily report
       - Session M: [Topic] - [outcome]
     ✓ This Week.md collapsed-day summary extended (only if the late sessions changed the day's shape)
     ```

### C3. Complete a deferred catch-up debrief

Accept the caught-up dates and report paths returned by catch-up mode. When `$morning` reaches Open Space, ask for each pending day:
> "Before we move on — did you do anything outside parked agent sessions on [Day DD]? (exercise, errands, social, admin) It won't have been captured at close-out."

Wait for the answer. If an answer names no day and several caught-up dates are possible, clarify the day before writing. For supplied activity, insert or extend `## Outside-Agent` in that date’s daily report, after `## Sessions` (or `## Today's Plan` if no Sessions) and before `## Blockers` (or the `---` links footer). Use a bounded `locked-edit.sh --replace`, preserve existing entries and avoid duplicates. Write before asking the next question and show the capture receipt. If the answer supplies no activity, leave the report unchanged. Return any other captures to `$morning` Step 5. If activity arrives later in the morning conversation, this same entry point accepts the answer without repeating the prompt.

## Guidelines

- **Technical, not emotional:** Focus on state and status, not feelings
- **Accountability:** Each session line should clearly state what was accomplished — the Sessions list is the record of what got done
- **Quick:** This should take 3-5 minutes unless there's a lot to capture
- **No guilt:** If it was a low-output day, just note the status honestly
- **Always resolve vault path first:** Step 0 confirms `VAULT_PATH` is set and points at a real directory. If it doesn't resolve, abort rather than silently fail.
- **File locking is mandatory — via the dedicated scripts** (`write-session.sh`, `update-session-section.sh`, `backfill-files-updated.sh`, `locked-edit.sh`), never inline `flock` and never a raw write on shared files (see `_shared-rules.md` §5).

### Working Memory Model (Critical — nightly interactive flow)

**Session files are inputs, not ground truth.** Once you read them in Step 2, work from your working memory for the rest of the command. This prevents the bug where:
1. the user says "that's done"
2. You acknowledge it
3. You re-read the session file (which still shows it open)
4. You present it as open again

**The flow:**
1. Read session files once (Step 2) - populate working memory
2. Ask about completions BEFORE presenting (Step 3) - update working memory AND files
3. Present from working memory (Step 5) - never re-read files mid-flow
4. Handle mid-flow corrections (Step 6) - update working memory AND files
5. Generate daily report from working memory (Step 8)

**Session files are write-only after initial read.** When the user marks something done you update the SSOT files (This Week.md, Tickler, project files — see Step 4) so future runs see correct state; session logs stay as the historical record, and you don't re-read them within this session.

## Triggers

This command should trigger when the user says:
- "goodnight"
- "end of day"
- "close out"
- "wrap up the day"
- "done for the day"

## Integration

- **Reads from:** This Week.md, today's agent session log, project docs
- **Creates:** Daily Reports
- **Updates:** agent session log (adds goodnight session), This Week.md (marks completed items `[x]`, collapses today's section, carries undone items forward within This Week.md), Tickler.md (deletes completed items), Project docs (marks complete, Current Objective / Next Actions if needed)
- **Complements:** `$morning` (start of day), `$park` (end of session), `$afternoon` (mid-day)
- **Auto-runs:** `$audit` on the just-completed goodnight (Step 15) — same protocol as `$park`'s Step 9
- **Replaces:** the deprecated daily-review workflow

Files in this skill

  • SKILL.md65.7 KB
  • agents/openai.yaml226 B

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.