create-battle-plan 1.3.0 → 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-battle-plan",
3
- "version": "1.3.0",
3
+ "version": "1.4.1",
4
4
  "description": "Scaffold a Battle Plan project — a markdown-based context system for LLM-powered project management",
5
5
  "bin": {
6
6
  "create-battle-plan": "./bin/cli.js"
@@ -72,6 +72,18 @@ Run these in parallel:
72
72
 
73
73
  The battle plan is your orientation layer — read it on demand, not by default. `docs/today.md` is what the user sees, so lead with that.
74
74
 
75
+ ## Step 1.6: Events context-debt warning (silent unless overdue)
76
+
77
+ `events.yml` carries past events that haven't been gated yet (transcript / spawned-tasks / insight not captured). Wrap-up's Step 4.5d handles these — but if the user skipped it or no wrap-up ran, they accumulate as context-debt.
78
+
79
+ Run `node tools/events/due-for-gate.js --json` and check whether any returned event has `end || start < (now - 2 days)`. If so, surface in the briefing:
80
+
81
+ > ⚠️ **Context-debt: N past event(s) ungated >2d** — EVT-{id} {title} ({start date}). Run `/wrap-up` Step 4.5d to capture transcript + insights.
82
+
83
+ Don't walk the gate during good-morning — wrap-up is the right time for that. Just flag it so the user knows.
84
+
85
+ If no events are returned, or none are >2d old, this step is silent.
86
+
75
87
  ## Step 2: Present the Briefing
76
88
 
77
89
  Print a compact morning report with these sections:
@@ -97,6 +109,7 @@ Pull all defined metrics from `metrics.yml`. If targets are defined in the battl
97
109
 
98
110
  End with 2-3 short questions:
99
111
  - "Anything happen since we last talked? Replies, updates, new info?"
112
+ - "Any new events to schedule? Calls, demos, meetings I should add to `events.yml`?" — if they name anything, call `node tools/events/add.js --title "..." --start "ISO" [--lead-id <id-if-any>] --source manual-chat`.
100
113
  - If there are stale items (no progress for 2+ days), ask about them specifically
101
114
  - If a key deliverable is outstanding, ask about it
102
115
 
@@ -64,6 +64,42 @@ Run `node tools/tasks/render-today.js --quiet` so today's surface reflects any c
64
64
 
65
65
  If 4.5a/4.5b changed anything, list it in Step 5 ("Task hygiene: N closed via git-drift, M archived").
66
66
 
67
+ **4.5d — Events gate (REQUIRED when past events exist):**
68
+
69
+ `events.yml` is the single source of truth for time-based events (calls, demos, meetings). Past events that haven't been "gated" (transcript + insights + spawned tasks captured) accumulate as context-debt. This step cycles the user through each one so context lands in the right docs before the event is archived.
70
+
71
+ Run `node tools/events/due-for-gate.js --json` and parse. If empty, this step is silent — skip to Step 5.
72
+
73
+ For each event returned, walk the user through them one-by-one (ONE at a time, sequential — do NOT batch):
74
+
75
+ ```
76
+ Gate EVT-{id} — {title} ({start HH:MM}, type={type})
77
+ [a] Transcript path? (drag/paste a file path, or "none" for no recording)
78
+ [b] Hypothesis impacts? (comma-separated like "H47, H49", or "none" — skip if your project has no hypothesis tracker)
79
+ [c] Tasks spawned? (free-text bullets; each becomes a `tools/tasks/add.js` call)
80
+ [d] Insight worth saving? (yes → append to the relevant cascade-target doc; no/skip otherwise)
81
+ [skip] ← leave gate open, will resurface tomorrow
82
+ ```
83
+
84
+ **Apply each answer immediately:**
85
+
86
+ - (a) → If a path was provided, copy/symlink the raw transcript to `docs/archive/transcripts/<slug>-YYYY-MM-DD.<ext>` (save raw verbatim FIRST, then distill). Store the final path on the event's `transcript_path` field via the events lib:
87
+ ```js
88
+ const E = require('./tools/events/lib/events');
89
+ const state = E.load();
90
+ E.upsert(state, { id: <id>, transcript_path: '<path>' });
91
+ E.save(state);
92
+ ```
93
+ - (b) → For each `H{n}`, append a short `> **[UPDATE YYYY-MM-DD · Source: EVT-{id} {title}]**` block to `docs/validation/hypotheses.md` (or your equivalent) near the relevant hypothesis. Store the list in the event's `hypothesis_impacts` field via the events lib. Skip if no hypothesis tracker exists.
94
+ - (c) → For each task description, run `node tools/tasks/add.js "<title>" [--due ...] [--lane ...] [--priority ...]`. Capture returned `TASK-N` ids into the event's `spawned_tasks` field.
95
+ - (d) → Append a dated section to the relevant cascade-target doc (e.g. `docs/validation/external-insights.md` for conversation findings) using its `Compression: chronological` format: `## YYYY-MM-DD — <title>` heading.
96
+ - After ALL non-skipped answers applied → stamp `gate_completed_at` (ISO now) on the event via the events lib.
97
+ - If the user picked `[skip]` → leave `gate_completed_at` null; the event resurfaces in tomorrow's wrap-up.
98
+
99
+ **Outreach add-on (Profile B only):** if `outreach/leads.csv` exists, also ask `[e] Lead status change? (e.g. call_done → verbal, or call_done → dead; "none" if unchanged)`. Update `leads.csv` via the Node load-mutate-save pattern in `tools/outreach/lib/leads.js`, then `node tools/outreach/sync-metrics.js`.
100
+
101
+ **Finally:** run `node tools/events/archive.js`. Moves gated events (terminal status + `gate_completed_at` set) → `events-archive.yml`. Idempotent.
102
+
67
103
  ## Step 5: Report
68
104
 
69
105
  Print:
@@ -1,214 +1,171 @@
1
- # Battle Plan — System Prompt
1
+ # Battle Plan — Project Instructions
2
2
 
3
- You are helping manage an interconnected documentation system. Every document stays in sync through a cascade protocol. Follow these rules exactly.
3
+ ## How this project works read this first
4
4
 
5
- ---
6
-
7
- ## Two-View Model — Read This First
8
-
9
- **The cascade is your orientation layer. `docs/today.md` is the user's operating surface. The chat is the user's only UI.**
10
-
11
- The user should never have to look at the cascade, the battle plan, `tasks.yml`, or any internal markdown to operate the system. The chat with you is the UI; the cascade is your memory; `docs/today.md` is a thin clickable surface in their editor for ticking through the day. Everything else exists for *you*, not them.
12
-
13
- - **Your view (the cascade):** `docs/battle-plan.md` at the top, source docs below it, `metrics.yml` as numeric truth, `tasks.yml` as the structured task log. Narrative, deep, linked. You read and write this freely — it is how you reconstruct project state and cascade new information.
14
- - **User's view:** `docs/today.md`, generated by `tools/tasks/render-today.js` from `tasks.yml`. Rendered in Obsidian Tasks plugin format — query blocks project pill-styled lists over a raw `## Task data` section at the bottom (lane-grouped within each priority bucket). The user checks boxes in Obsidian; `tools/tasks/flush-today.js` reconciles those edits back into `tasks.yml`. When they want deep context, they ask you in chat — you traverse the cascade on their behalf.
15
-
16
- **Rules:**
17
- - Never grow the battle plan's TL;DR into a wall of prose. Keep header blocks terse; append Daily Log entries chronologically.
18
- - When the user drops new tasks, add them via `node tools/tasks/add.js "..." [--due YYYY-MM-DD] [--tag X] [--priority 1|2|3] [--lane LANE] [--implication PATH]`. Don't bury tasks in battle plan prose.
19
- - After any task mutation (add, complete, snooze, triage), run `node tools/tasks/render-today.js` so `docs/today.md` stays fresh.
20
- - `verify-cascade.sh` Check 6 confirms `today.md` is not stale relative to `tasks.yml`.
21
-
22
- ---
23
-
24
- ## Task Lanes — what goes where
5
+ Two views of the same project, and Claude operates across both.
25
6
 
26
- Every task has a `lane` (groups by *primary action*, not topic). The default vocabulary:
7
+ **Claude's view (the cascade):** `docs/battle-plan.md` at the top, source docs below it (under `docs/`), `metrics.yml` as numeric truth, `events.yml` for time-based events, `tasks.yml` for strategic tasks. Narrative, deep, linked. Claude reads and writes this freely — it's how project state is reconstructed across sessions.
27
8
 
28
- | Lane | Primary action |
29
- |---|---|
30
- | `build` | Build, design, or document the product itself (MVP, demos, architecture, integrations) |
31
- | `outreach` | Cold DMs / InMails / posts / templates / cold-email infra-as-pipeline |
32
- | `discovery` | Chase or nurture a *named human relationship* — warm intros, follow-ups, scheduling specific calls |
33
- | `infra` | Plumbing — DNS, GCP/AWS, deploy, env, secrets, cold-email domain warm-up |
34
- | `fundraising` | Apply to or maintain relationships with accelerators / VCs / angels |
35
- | `meta` | Doc/process work with no other natural lane (default fallback) |
36
-
37
- **Adapt this set to your project.** Lanes are configurable in `tools/tasks/lib/tasks.js` (`VALID_LANES`) — when you change them, also update `tools/tasks/migrate-lanes.js` keyword buckets and the `LANE_DISPLAY` / `LANE_ORDER` tables in `tools/tasks/render-today.js`.
38
-
39
- **Personalities don't get their own lane.** A specific advisor or co-founder's input flows into all the action lanes. A task like "ask <advisor> about X" is `discovery` (relationship), `build` (product feedback), or `meta` (process), depending on what *closing* the task produces.
9
+ **User's view:** `docs/today.md` generated by `tools/tasks/render-today.js` from `tasks.yml`, rendered in Obsidian Tasks plugin format (query blocks projecting pill-styled lists over a raw `## Task data` section at the bottom, lane-grouped within each priority bucket). The user checks boxes in Obsidian; `tools/tasks/flush-today.js` reconciles those edits back into `tasks.yml`. Their other primary surface is this chat — when they want deep context, they ask, and Claude traverses the cascade on their behalf.
40
10
 
41
- ### Strategic vs routine what belongs in `tasks.yml`
42
-
43
- `tasks.yml` is for **ad-hoc strategic / build / discovery-protocol / high-stakes individual conversations.** Examples: a milestone call with a specific stakeholder where multiple people join; a piece of pitch copy that needs to be written by a specific date; a load-bearing architecture decision that gates other work.
44
-
45
- Routine lead-by-name follow-ups ("X accepted, send DM") belong in the **outreach blitz pipeline**, not in `tasks.yml`. The blitz already surfaces those through `daily-targets.js` + `leads.csv` flags + accepted-not-replied detection on timers. Don't duplicate that work as tasks.
46
-
47
- When uncertain, default to the blitz. `tasks.yml` is a journal of strategic intent, not a worklist.
11
+ **Core principle:** the user should never have to look at the cascade, the battle plan, `tasks.yml`, `events.yml`, or any internal markdown to operate the system. The chat with Claude is the UI; the cascade is Claude's memory; `docs/today.md` is a thin clickable surface for ticking through the day. Everything else exists for Claude, not them.
48
12
 
49
13
  ---
50
14
 
51
- ## Weekly Triage`/weekly-triage`
52
-
53
- Run weekly to keep `tasks.yml` honest. The skill (`.claude/commands/weekly-triage.md`) walks the user through every open task one at a time using `AskUserQuestion`'s arrow-key UI. Each decision (`done` / `snooze N` / `demote` / `merge X` / `delete` / `lane LANE` / `priority N` / `keep`) is applied to `tasks.yml` immediately — no batching.
15
+ ## The Cascadethe core protocol
54
16
 
55
- Two automation layers support this:
17
+ **Trigger:** any information shared in chat (or surfaced from tool output) that affects project state. This includes:
18
+ - Conversations, calls, or meetings (any signal — positive, negative, ambiguous)
19
+ - Research findings (desk research, web search, competitor intel)
20
+ - New targets, contacts, or refinements
21
+ - Metric changes
22
+ - Information that validates, invalidates, or adds nuance to a hypothesis
23
+ - New time-based events (calls, demos, meetings, anything with a start datetime + counterparty)
24
+ - Outreach activity (if the outreach add-on is installed — Profile B)
56
25
 
57
- - **`tools/tasks/triage.js`** read-only data layer. Surfaces overdue/stale tasks, recent commits mentioning each task ID (signals "this is probably done"), and *implications drift* (when a task's linked doc hasn't been modified since the task was created — meaning the cascade hasn't actually reached the doc). Output as Markdown by default, JSON via `--json` for programmatic consumers.
58
- - **`tools/tasks/triage-due.js`** — lightweight SessionStart-hook nudge. Silent unless one of three thresholds trips: time-based (≥7d since last triage), stale-task (≥20 open tasks ≥14d old), or volume (≥60 open). Wired in `.claude/settings.json`. The skill stamps `last_triage_at` in `tasks.yml` on completion to suppress the nudge until the next cycle.
26
+ When triggered, update top-down in this exact order. Each step depends on the one above it. Never skip.
59
27
 
60
- When you see the SessionStart nudge fire, mention it in chat ("📋 Last triage was 9d ago — want to run `/weekly-triage`?") but never auto-invoke. The user decides.
61
-
62
- ### Implications field
63
-
64
- Tasks may carry `implications: [docs/path-a.md, docs/path-b.md]` — a list of docs that should change when the task closes. `triage.js` flags drift when a linked doc's last git commit predates the task: the status flip happened, but the cascaded doc-update didn't. When you create a task whose closure should mutate a specific doc, pass `--implication path/to/doc.md` to `add.js` so triage can hold you accountable later.
65
-
66
- ### `blocked_by` field
28
+ ```
29
+ Step 0: raw state changes → metrics.yml / tasks.yml / events.yml / (leads.csv if Profile B)
30
+
31
+ Step 1: docs/battle-plan.md ← TL;DR + Key Metrics table + Daily Log entry
32
+
33
+ Step 2: source docs (only the relevant ones):
34
+ ├── docs/validation/external-insights.md ← conversations & meetings (chronological)
35
+ ├── docs/validation/hypotheses.md ← validation/invalidation (amended)
36
+ ├── docs/market/ ← persona / strategy / competitive intel
37
+ ├── docs/research/ ← foundational knowledge
38
+ └── (any other domain docs you've added)
39
+
40
+ Step 3: tools/touch-date.sh ← stamp Last Updated on every modified file
41
+
42
+ Step 4: tools/verify-cascade.sh ← verify TL;DR / metrics / dates / today.md staleness
43
+ ```
67
44
 
68
- Tasks may also carry `blocked_by: [TASK-IDs]` an array of TASK-IDs that must close before this task is actionable. Set via `add.js --blocked-by N` (repeatable; comma-separated also accepted; each ID validated against existing rows). When at least one blocker is still open:
45
+ If `outreach/leads.csv` exists (Profile Boutreach add-on), Step 0 also covers leads.csv mutations, and `sync-metrics.js` auto-chains from every `flush-*.js` script (it derives `metrics.yml` from the CSV never hand-edit metrics directly).
69
46
 
70
- - `triage.js` shows a `🚧 Blocked by:` line listing each blocker's id, status, and title (open ones flagged 🚧, closed ones ✅).
71
- - The stale-flag and snooze-or-demote suggestion are **suppressed** a task waiting on a deliberate blocker shouldn't be penalized for not progressing.
72
- - The replacement suggestion becomes "blocked chase blocker(s) or demote".
73
- - Stats gain a `Blocked by another open task: N` line.
74
- - `render-today.js` emits a `🚧 blocked-by:TASK-N,TASK-M` token on the task's line but only for *still-open* blockers, so the token disappears once the blocker closes.
47
+ **Sources of truth one per concept, no parallel stores:**
48
+ - `metrics.yml`every metric (derived from leads.csv when outreach is installed; hand-edited otherwise)
49
+ - `events.yml` / `events-archive.yml`every time-based event
50
+ - `tasks.yml` every strategic / build / milestone task
51
+ - `docs/battle-plan.md` — current state ("where are we right now")
52
+ - `outreach/leads.csv` (Profile B only) — every lead
75
53
 
76
- When the user describes a task that genuinely depends on another, set the blocker explicitly (`--blocked-by N`) instead of letting the dependency live in prose. Closure of the blocker doesn't auto-close the dependent — the user picks the action during the next triage.
54
+ Never put state in markdown tables that lives somewhere structured. Never create a parallel store.
77
55
 
78
56
  ---
79
57
 
80
- ## Task archive`node tools/tasks/archive.js`
81
-
82
- `tasks.yml` is append-only by design (audit trail), but it shouldn't grow forever. The archive script moves any `status: done|cancelled` row with `done_at < today - 14d` into `tasks-archive.yaml` (created on first run, same schema, sorted by `done_at` ascending).
83
-
84
- - **Default retention:** 14 days. Pass `--days N` to override, or `--all` to archive every closed row regardless of age.
85
- - **Idempotent:** dedups by `id` against the existing archive. Safe to run on every `/wrap-up`.
86
- - **Wired into `/wrap-up` Step 4.5b** — runs daily as part of end-of-day routine.
87
- - **Backfill safety:** closed rows missing `done_at` get stamped today and kept one cycle (so a date-less row doesn't get archived without chronological position).
88
- - **Re-importing a task:** intentional friction. Manually move the row from `tasks-archive.yaml` → `tasks.yml` and set `status: open`.
89
-
90
- ---
58
+ ## Behavioral ruleswhen X happens, do Y
91
59
 
92
- ## The Cascade Protocol
60
+ **When the user mentions a time-based event** ("demo with X Thursday 2pm", "advisor call Friday 10am", "dentist Monday 9am"): call `node tools/events/add.js --title "..." --start "ISO" [--lead-id <id>] [--attendee "X"] [--type ...]`. Two criteria for an event (both required): a start datetime AND a counterparty/attendee. Anything failing either stays in `tasks.yml`.
93
61
 
94
- **Trigger:** Any incoming information that relates to the project calls, messages, research, signals, status changes, decisions.
62
+ **When the user drops a transcript** (paste, file path, attachment): save the raw verbatim file FIRST to `docs/archive/transcripts/<slug>-YYYY-MM-DD.<ext>`. Do not rewrite, summarize, or trim. THEN distill into the appropriate cascade-target doc. Always link back: `_Raw transcript: [path](...)._` Never delete transcripts — they're immutable audit trail.
95
63
 
96
- When triggered, update in this exact order:
64
+ **When the user drops new tasks:** add via `node tools/tasks/add.js "..." --lane LANE [--due ...] [--tag ...] [--priority 1|2|3] [--implication PATH] [--blocked-by N]`. `--lane` is required (one of: `build` / `outreach` / `discovery` / `infra` / `fundraising` / `meta` — customize in `tools/tasks/lib/tasks.js`). After any task mutation, run `node tools/tasks/render-today.js` so `docs/today.md` stays fresh.
97
65
 
98
- ### Step 0: Update `metrics.yml`
99
- If any key metric changed, update `metrics.yml` first. This is the numeric source of truth.
66
+ **When a number from one doc appears in another:** annotate as a markdown link.
67
+ - Tier 1 numbers in `metrics.yml`: `[**42**](metrics.yml#outreach_sent)`
68
+ - Tier 2 — numbers from another doc: `[**60%**](external-insights.md#session-2) of time on evidence`
100
69
 
101
- ### Step 1: Update Battle Plan (`docs/battle-plan.md`)
102
- - Update the **TL;DR** with current status
103
- - Update the **Key Metrics** table (numbers reference metrics.yml)
104
- - Update **Today's Priorities** if relevant
105
- - Append to **Daily Log** for today
70
+ Numbers native to a doc (where they originate) have no annotation. Verified by `tools/check-metrics.sh`.
106
71
 
107
- ### Step 2: Update Cascade Docs
108
- Update only the docs relevant to the new information. Route new info to the appropriate domain doc under `docs/`. Common patterns:
72
+ **When stating a claim:** mark its confidence — `Unvalidated` / `Soft signal` / `Practitioner-validated` / `Data-validated`. Include the source.
109
73
 
110
- | Info type | Route to... |
111
- |-----------|------------|
112
- | Conversation, call, or meeting | `docs/external-insights.md` — append as new dated session |
113
- | Evidence for/against a hypothesis | The relevant domain doc — amend the claim with an `[UPDATE]` block |
114
- | Outreach sent/received | The relevant market or sales doc — update tracking tables |
115
- | Competitor intel | The relevant strategy or market doc |
116
- | New foundational knowledge | The relevant research or domain doc |
74
+ **When `verify-cascade.sh` reports an issue:** fix it before ending the turn. Don't ship a known-stale state.
117
75
 
118
- If no doc exists for the info, append it to the closest domain overview doc. Only create a new file if the info doesn't fit anywhere.
76
+ ### Outreach add-on (Profile B only when `outreach/leads.csv` exists)
119
77
 
120
- ### Step 3: Update Dates
121
- Run `tools/touch-date.sh` on every file you modified in this session:
78
+ **When the user mentions a lead** (reply, accept, call booked, status change, new contact, dead): update `leads.csv` — do not just acknowledge in chat. Options: drop a line into `outreach/inbox/updates.md` + `flush-updates.js` (Haiku-powered), or a Node one-liner. Then run the cascade.
122
79
 
123
- ```bash
124
- tools/touch-date.sh docs/battle-plan.md docs/validation/hypotheses.md [etc.]
125
- ```
80
+ **When the user reports a routine lead-by-name follow-up** ("chase X", "second follow-up to Y"): do NOT add to `tasks.yml`. The blitz pipeline (`daily-targets.js`) rotates these automatically from `leads.csv` flags. `tasks.yml` is for strategic / milestone / multi-party work.
126
81
 
127
- ### Step 4: Verify
128
- Run `tools/verify-cascade.sh` and fix any issues it reports:
82
+ **When the user drops names** ("did we already contact X?"): run `node tools/outreach/lookup.js "Name1, Name2, ..."` FIRST. Fuzzy-matches against `leads.csv` — prevents duplicates.
129
83
 
130
- ```bash
131
- tools/verify-cascade.sh
132
- ```
84
+ **When the user asks for numbers** ("how many in X bucket?", "what's the funnel?"): run `node tools/outreach/stats.js` first. Live tally with template performance, weekly breakdown, channel split, FU template breakdown.
133
85
 
134
86
  ---
135
87
 
136
- ## Source Reference Rules
137
-
138
- ### Registry Metrics (Tier 1 — deterministic)
139
- Numbers defined in `metrics.yml`. Reference as: `[**N**](metrics.yml#field_name)`
140
-
141
- This renders as a bold clickable number. Example: `[**42**](metrics.yml#outreach_sent)`
142
-
143
- These are verified by exact numeric comparison via `tools/check-metrics.sh`.
88
+ ## Schemas & format
144
89
 
145
- ### Inline Metrics (Tier 2 LLM-verified)
146
- Less common numbers from another doc. Reference as: `[**N**](source-doc.md#section-slug)`
147
-
148
- Example: `60% of time on evidence [**60**](external-insights.md#session-2-key-insights)`
149
-
150
- **Rule:** Every number referenced from another document MUST include a source annotation. Only numbers native to a doc (where they originate) have no annotation.
151
-
152
- ---
153
-
154
- ## Document Format
155
-
156
- Every doc in `docs/` must have this frontmatter:
90
+ ### Document frontmatter (every doc in `docs/`)
157
91
 
158
92
  ```markdown
159
93
  # Document Title
160
94
 
161
- **Last Updated:** 2026-04-07
95
+ **Last Updated:** YYYY-MM-DD
162
96
  **Status:** Active | Draft | Archived
163
97
  **Role:** source-of-truth | cascade-target
164
98
  **Compression:** chronological | amended | none
165
99
 
166
- **TL;DR:** One paragraph summary with key numbers and source references.
167
-
168
- ---
100
+ **TL;DR:** One paragraph with key numbers and source references.
169
101
  ```
170
102
 
171
- - **Last Updated** must match today's date on any file modified in the current session.
172
- - **Status:** `Active` = live, `Draft` = WIP, `Archived` = excluded from cascade.
173
- - **Role:** `source-of-truth` = authoritative for its numbers. `cascade-target` = references numbers from elsewhere.
174
- - **Compression:** required field. One of `chronological`, `amended`, or `none` (see Compression Modes section below).
175
- - **TL;DR** must exist and contain all key metrics that appear in the doc.
176
-
177
- ---
178
-
179
- ## Compression Modes & Timestamping Rules
180
-
181
- Every doc declares a `Compression:` mode in frontmatter. This tells the `/distill` command (and humans) how new info gets added to the doc and how old info gets compressed when it grows too long. The mode IS the timestamping rule for new info.
182
-
183
- ### `Compression: chronological`
184
- The doc is an append-only log of dated entries. Each new piece of info goes in a new dated section.
103
+ - `Last Updated` must match today's date on any file modified in the current session.
104
+ - `Status` `Active` = live; `Draft` = WIP; `Archived` = excluded from cascade.
105
+ - `Role` `source-of-truth` = authoritative for its numbers. `cascade-target` = references numbers from elsewhere.
106
+ - `TL;DR` contains all key metrics that appear in the doc.
185
107
 
186
- - **Timestamping rule:** every new entry MUST start with a dated heading: `## Session N (YYYY-MM-DD) — <title>`, `## YYYY-MM-DD — <title>`, or `## DD Month YYYY — <title>`. No exceptions.
187
- - **Examples:** `docs/battle-plan.md` (daily log), `docs/validation/external-insights.md` (conversation journal).
188
- - **`/distill` behavior:** keeps the N most recent dated sections verbatim, archives the rest into `docs/archive/<same-path>`, replaces them with a thorough summary.
108
+ ### Compression modes (drives `/distill`)
189
109
 
190
- ### `Compression: amended`
191
- The doc is a living reference. Claims are amended in place over time.
110
+ | Mode | Doc type | Timestamp rule for new entries |
111
+ |---|---|---|
112
+ | `chronological` | append-only log (battle-plan.md, external-insights.md) | every entry MUST start with a dated heading: `## YYYY-MM-DD — <title>` or `## Session N (YYYY-MM-DD) — <title>` |
113
+ | `amended` | living reference (hypotheses.md, icp-and-targets.md, competitive-landscape.md) | every revision MUST be a `> **[UPDATE YYYY-MM-DD · Source: ...]**` block placed immediately above the claim it modifies |
114
+ | `none` | static thesis (product-thesis.md, domain-101.md) | none — rewrite, don't amend. Git history is the timeline. `/distill` refuses to run. |
192
115
 
193
- - **Timestamping rule:** every new finding that revises an existing claim MUST be added as an inline `> **[UPDATE YYYY-MM-DD · Source: ...]**` block placed immediately above the claim it modifies. Brand-new claims with no prior version don't need a stamp; they're stamped implicitly by the doc's `Last Updated` date and git history.
194
- - **Examples:** `docs/validation/hypotheses.md`, `docs/market/icp-and-targets.md`, `docs/market/competitive-landscape.md`.
195
- - **`/distill` behavior:** collapses old `[UPDATE]` blocks into the body text (preserving their content as integrated current-state), archives the raw blocks verbatim. Keeps the N most recent amendments per section inline.
116
+ Skipping the timestamp on a new entry in a `chronological` or `amended` doc is a bug it gets silently absorbed into the wrong era during distillation.
196
117
 
197
- ### `Compression: none`
198
- The doc is a static thesis or reference. It gets rewritten, not amended. Git history is the timeline.
118
+ ### `tasks.yml` — task lanes
199
119
 
200
- - **Timestamping rule:** none. Just edit the doc and let `Last Updated` + git track changes.
201
- - **Examples:** `docs/strategy/product-thesis.md`, `docs/research/domain-101.md`.
202
- - **`/distill` behavior:** refuses to run. If a `none` doc has grown unwieldy, rewrite it manually or change its `Compression:` mode first.
120
+ Every task has a `lane` (groups by *primary action*, not topic). Default vocabulary (customize in `tools/tasks/lib/tasks.js`):
203
121
 
204
- ### Why this matters
205
- The TL;DR is current state, not history. It can't tell `/distill` what's new vs old. The `Compression:` mode + timestamping rule is the only mechanism that makes distillation deterministic. Skipping the timestamp on a new entry in a `chronological` or `amended` doc is a bug; it will get silently absorbed into the wrong era during distillation.
122
+ | Lane | Primary action |
123
+ |---|---|
124
+ | `build` | Build, design, or document the product itself |
125
+ | `outreach` | Cold DMs / InMails / templates / cold-email infra |
126
+ | `discovery` | Chase or nurture a named human relationship |
127
+ | `infra` | Plumbing — DNS, GCP/AWS, deploy, env, secrets |
128
+ | `fundraising` | Accelerator / VC / angel relationships |
129
+ | `meta` | Doc/process work with no other natural lane (default fallback) |
206
130
 
207
- When in doubt about which mode a new doc should use: chronological logs choose `chronological`, claim trackers choose `amended`, everything else is `none`.
131
+ Tasks may carry:
132
+ - `due: YYYY-MM-DD` — deadline (sorted to top by date).
133
+ - `priority: 1|2|3` — Today / This week / Backlog.
134
+ - `implications: [docs/path-a.md, ...]` — docs that should change when the task closes. `triage.js` flags drift when a linked doc's last commit predates the task.
135
+ - `blocked_by: [TASK-IDs]` — tasks that must close first. `render-today.js` emits the marker only for *still-open* blockers.
136
+
137
+ **Strategic vs routine:** `tasks.yml` is for strategic / multi-party / load-bearing work. Routine name-by-name follow-ups belong in the outreach blitz (Profile B), not here.
138
+
139
+ ### `events.yml` — time-based events
140
+
141
+ Single source of truth for calls, demos, meetings, advisor sessions, dentist appointments, anything with a start datetime + counterparty.
142
+
143
+ - `events.yml` — future scheduled events.
144
+ - `events-archive.yml` — terminal events after the wrap-up gate has captured transcript / spawned tasks / insights.
145
+
146
+ ```yaml
147
+ - id: 1
148
+ title: Demo v1 walkthrough — Counterparty / Company
149
+ start: "2026-05-14T14:00:00+02:00" # ISO 8601 with TZ
150
+ end: "2026-05-14T14:45:00+02:00"
151
+ type: demo # demo|discovery|investor|admin|personal|unspecified|other
152
+ status: scheduled # scheduled|in_progress|done|cancelled|no_show|rescheduled
153
+ attendees: [Counterparty, Me]
154
+ lead_id: https://www.linkedin.com/in/example # nullable (links to leads.csv if Profile B)
155
+ location: Google Meet
156
+ notes: free-form
157
+ source: manual-chat # manual-chat|flush-updates|migrate-from-csv|hand-edit
158
+ created_at: "2026-05-11T10:38:00.372Z"
159
+ # populated by the wrap-up gate after the event ends:
160
+ transcript_path: docs/archive/transcripts/...
161
+ spawned_tasks: [TASK-86, TASK-87]
162
+ hypothesis_impacts: [H47, H49]
163
+ gate_completed_at: "2026-05-14T16:30:00+02:00"
164
+ ```
208
165
 
209
- ---
166
+ Lifecycle: `scheduled` → `done` → `gated` (wrap-up Step 4.5d fills `gate_completed_at`) → archived (`tools/events/archive.js` moves to events-archive.yml). Personal events (gym, dentist) can carry `type: personal` and skip the gate.
210
167
 
211
- ## Vault Rules
168
+ ### Vault rules
212
169
 
213
170
  1. **Update, don't duplicate.** Amend with `> **[UPDATE YYYY-MM-DD · Source: ...]**`
214
171
  2. **Cross-link everything.** Claims reference their source doc.
@@ -216,95 +173,33 @@ When in doubt about which mode a new doc should use: chronological logs choose `
216
173
  4. **Source everything.** Who said it, when, confidence level.
217
174
  5. **Minimize file count.** Append, don't create new files.
218
175
 
219
- ---
220
-
221
- ## The `/wrap-up` Protocol
222
-
223
- When the user says `/wrap-up`, run this end-of-day sequence:
224
-
225
- **Step 1 — Scan:** Read the battle plan. Identify all tasks for today. Categorize: done, partially done, not started, new.
176
+ ### Outreach `leads.csv` — Profile B only
226
177
 
227
- **Step 2 Present:** Show the user: "Here's today's status: [list]. Does this look right?"
178
+ If `outreach/leads.csv` exists in the project, the outreach add-on is active. See `outreach/README.md` for setup. `leads.csv` is the single source of truth for outreach metrics — `metrics.yml` is derived from it via `sync-metrics.js` (auto-chained from every `flush-*.js`).
228
179
 
229
- **Step 3 Prompt:** Ask: "Anything else happen today? Even small things a reply, an accept, a thought, a link. Everything counts."
180
+ Key columns (full HEADERS list in `tools/outreach/lib/leads.js`): `linkedin_url`, `first_name`, `last_name`, `title`, `company`, `country`, `email`, `source`, `tags`, `status`, `priority`, `contacted_at`, `replied_at`, `followed_up_at`, `channel`, `template`, `inmail_template`, `followup_template`, `notes`.
230
181
 
231
- **Step 4 Cascade:** With all info gathered, run the full cascade (Steps 0-4 above).
182
+ Status flow: `new dm_sent replied call_booked call_done verbal loi → paying`. Branches: `dead`, `withdrawn`.
232
183
 
233
- **Step 5Report:** Print:
234
- - Metrics changed today (before → after)
235
- - Docs updated
236
- - Verification warnings (if any)
237
- - Tomorrow's top priorities
238
-
239
- **Step 6 — Commit:** Ask: "Want me to commit today's updates?" If yes, commit with message: `eod YYYY-MM-DD: [summary]`
184
+ Tags grow forward with `, ` separator. Notes grow forward with `| ` separator never overwrite.
240
185
 
241
186
  ---
242
187
 
243
- ## Outreach System (Add-on)
244
-
245
- **Trigger:** If `outreach/leads.csv` exists in the project, the outreach system is active.
246
-
247
- ### Overview
248
-
249
- The outreach system tracks a LinkedIn (or any channel) outreach pipeline through `outreach/leads.csv`. This CSV is the **single source of truth** for all outreach metrics — `metrics.yml` is derived from it, never edited directly for outreach numbers.
250
-
251
- ### First-Time Setup
252
-
253
- If `outreach/leads.csv` exists but `.outreach-initialized` does NOT exist, read `outreach/README.md` and follow the Interactive Setup instructions to onboard the user.
254
-
255
- ### Daily Workflow Integration
256
-
257
- The outreach system plugs into the cascade protocol:
258
-
259
- 1. User runs `node tools/outreach/daily-targets.js` → generates blitz checklist
260
- 2. User sends messages, ticks checkboxes
261
- 3. User runs `node tools/outreach/flush-targets.js` → updates leads.csv
262
- 4. `flush-targets.js` calls `sync-metrics.js` → derives metrics.yml from CSV
263
- 5. `sync-metrics.js` calls `update-dashboard.js` → regenerates mermaid dashboard
264
- 6. The cascade protocol takes over: metrics.yml → battle-plan.md → domain docs
265
-
266
- ### Scripts Reference
267
-
268
- | Script | Purpose |
269
- |--------|---------|
270
- | `tools/outreach/daily-targets.js [N]` | Generate daily blitz checklist |
271
- | `tools/outreach/flush-targets.js` | Process checked items from blitz |
272
- | `tools/outreach/flush-updates.js` | Parse free-form natural language updates |
273
- | `tools/outreach/flush-accepts.js` | Batch-process connection accepts |
274
- | `tools/outreach/flush-inbox.js` | Add leads from manual URL list |
275
- | `tools/outreach/sync-metrics.js` | Derive metrics.yml from leads.csv |
276
- | `tools/outreach/update-dashboard.js` | Regenerate mermaid conversion dashboard |
277
- | `tools/outreach/stats.js` | Print pipeline summary |
278
- | `tools/outreach/lookup.js "Name"` | Fuzzy-search leads |
279
-
280
- ### Metrics Derivation
281
-
282
- These metrics in `metrics.yml` are **derived** from leads.csv (never hand-edit):
283
-
284
- - `outreach_sent` = leads with status past `new` or `contacted_at` set
285
- - `responses` = `replied_at` set or status past `replied`
286
- - `invitations_accepted` = leads tagged `accepted`
287
- - `discovery_calls` = `call_at` in the past or status `call_done`
288
- - `calls_booked` = status `call_booked` (snapshot)
289
- - `verbal_commitments` = status `verbal`, `loi`, or `paying`
290
-
291
- ### Template System
292
-
293
- Message templates live in `tools/outreach/templates.json`. The daily blitz assigns templates based on the `country_template_map` field (or round-robin if no mapping). Template performance (sent/accepted/replied/calls) is tracked automatically and displayed in the blitz checklist.
294
-
295
- ### Mermaid Dashboard
188
+ ## Pointers
296
189
 
297
- `docs/analysis/icp-conversion.md` is auto-generated — never hand-edit. It contains:
298
- - Overall funnel chart (contacted accepted replied call → verbal)
299
- - Conversion breakdown by role, company size, country, company type
300
- - Template A/B comparison
301
- - Kill/Keep/Scale verdicts per segment
190
+ **Daily scripts:**
191
+ - `tools/tasks/` task lifecycle: `add.js`, `render-today.js`, `flush-today.js`, `triage.js`, `archive.js`, `migrate-lanes.js`
192
+ - `tools/events/` events lifecycle: `add.js`, `upcoming.js`, `due-for-gate.js`, `archive.js`, `migrate-from-csv.js`
193
+ - `tools/touch-date.sh` — stamp Last Updated in frontmatter
194
+ - `tools/verify-cascade.sh` — verify TL;DR / metrics / dates / today.md staleness
195
+ - `tools/check-metrics.sh` — verify Tier 1 metric link references
302
196
 
303
- View in any mermaid-capable renderer (GitHub, VS Code preview, etc.).
197
+ **Outreach scripts (Profile B):** `tools/outreach/` — `daily-targets.js`, `flush-targets.js`, `flush-updates.js`, `flush-accepts.js`, `flush-inbox.js`, `sync-metrics.js`, `update-dashboard.js`, `stats.js`, `lookup.js`, `stale-invitations.js`.
304
198
 
305
- ### Adapting the System
199
+ **Skills (`.claude/commands/`):**
200
+ - `good-morning` — morning standup (metrics + today.md + events context-debt check)
201
+ - `wrap-up` — end-of-day reconciliation + task hygiene + events gate
202
+ - `weekly-triage` — task-pile sweep
203
+ - `distill` — compress chronological / amended doc history
306
204
 
307
- - **Different metrics:** Edit the derivation rules in `tools/outreach/sync-metrics.js`
308
- - **Different time horizon:** The system tracks weekly breakdowns — adjust `daily-targets.js` count parameter
309
- - **Different channels:** The `channel` column supports any value (connection, inmail, email, etc.)
310
- - **Different statuses:** Add to `VALID_STATUS` in `tools/outreach/lib/leads.js` and update derivation rules
205
+ **Auto-nudges:** `tools/tasks/triage-due.js` runs on every session start. When it prints a nudge, mention `/weekly-triage` to the user do not auto-invoke.
@@ -0,0 +1,5 @@
1
+ # events-archive.yml — past/completed events. Mirrors events.yml schema.
2
+ # Wrap-up gate moves events here after transcript + insights captured.
3
+ last_updated: 2026-05-11
4
+ next_id: 1
5
+ events:
@@ -0,0 +1,5 @@
1
+ # events.yml — single source of truth for future time-based events (calls, demos, meetings).
2
+ # Past/completed events live in events-archive.yml. Tasks (deadline-only, no time-span) live in tasks.yml.
3
+ last_updated: 2026-05-11
4
+ next_id: 1
5
+ events:
@@ -0,0 +1,64 @@
1
+ #!/usr/bin/env node
2
+ // tools/events/add.js — add or update a future event in events.yml.
3
+ //
4
+ // Usage:
5
+ // node tools/events/add.js --title "Demo v1 — Acme/CIO" \
6
+ // --start "2026-05-14T14:00:00+02:00" [--end "..."] [--duration-min 30] \
7
+ // [--type demo|discovery|investor|admin|personal|unspecified|other] \
8
+ // [--status scheduled|in_progress|done|cancelled|no_show|rescheduled] \
9
+ // [--lead-id <linkedin_url_or_company_key>] [--attendee "Name"] (repeatable) \
10
+ // [--location "..."] [--notes "..."] [--source flush-updates|manual-chat|hand-edit] \
11
+ // [--id <existing EVT id>] # update an existing event
12
+ //
13
+ // Idempotent — if (lead_id + start) matches an existing row, that row is updated.
14
+ // Default: type=unspecified, status=scheduled, duration=30 minutes.
15
+
16
+ const E = require('./lib/events');
17
+
18
+ function parseArgs(argv) {
19
+ const out = { attendees: [] };
20
+ for (let i = 2; i < argv.length; i++) {
21
+ const k = argv[i];
22
+ const v = argv[i + 1];
23
+ if (k === '--attendee') { out.attendees.push(v); i++; continue; }
24
+ if (k.startsWith('--')) { out[k.slice(2).replace(/-/g, '_')] = v; i++; }
25
+ }
26
+ return out;
27
+ }
28
+
29
+ function main() {
30
+ const a = parseArgs(process.argv);
31
+ if (!a.title && !a.id) {
32
+ console.error('Usage: node tools/events/add.js --title "..." --start "ISO" [--type ...] [--lead-id ...]');
33
+ process.exit(1);
34
+ }
35
+ if (!a.start && !a.id) {
36
+ console.error('--start required (ISO 8601, e.g. "2026-05-14T14:00:00+02:00").');
37
+ process.exit(1);
38
+ }
39
+
40
+ const state = E.load();
41
+ const event = {};
42
+ if (a.id) event.id = parseInt(a.id, 10);
43
+ if (a.title) event.title = a.title;
44
+ if (a.start) event.start = a.start;
45
+ if (a.end) event.end = a.end;
46
+ else if (a.start) {
47
+ const d = parseInt(a.duration_min || '30', 10);
48
+ event.end = E.defaultEnd(a.start, d);
49
+ }
50
+ event.type = a.type && E.VALID_TYPE.has(a.type) ? a.type : (a.type || 'unspecified');
51
+ event.status = a.status && E.VALID_STATUS.has(a.status) ? a.status : 'scheduled';
52
+ event.attendees = a.attendees;
53
+ event.lead_id = a.lead_id || null;
54
+ event.location = a.location || null;
55
+ event.notes = a.notes || null;
56
+ event.source = a.source || 'manual-chat';
57
+
58
+ const result = E.upsert(state, event);
59
+ E.save(state);
60
+ console.log(`${result.action}: EVT-${result.event.id} · ${result.event.title} · ${result.event.start}`);
61
+ }
62
+
63
+ if (require.main === module) main();
64
+ module.exports = { parseArgs };