fdeops 3.9.5 → 3.9.8

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.
@@ -0,0 +1,188 @@
1
+ 'use strict'
2
+
3
+ function createTrustApi(deps) {
4
+ const {
5
+ fs, path, readClean, readEng, parseMdTable, sectionBody, SIGNAL_LEDGER, memoryDirtyManual,
6
+ } = deps
7
+
8
+ // phase / trust / top risk / freshness - identical heuristic for status + dashboard.
9
+ // Trust resolution: structured [signal:red|amber|green] tokens in stakeholders.md
10
+ // (written by `fde log contact --signal` and `fde debrief`) win - the latest dated
11
+ // one. Older than 21 days → stale: shown with a "?" marker + age so a forgotten
12
+ // signal never silently drives triage. The keyword grep survives only as the
13
+ // zero-effort floor when NO token exists anywhere - prose like "escalated to CTO,
14
+ // resolved amicably" must not flip a client amber forever.
15
+ function stakeholdersMemoryHealth(eng) {
16
+ // Hostile handoff: binary / unparseable stakeholders must not read as healthy green.
17
+ let buf
18
+ try { buf = fs.readFileSync(path.join(eng, 'stakeholders.md')) } catch (_) {
19
+ return { ok: true, warn: '' }
20
+ }
21
+ if (buf.includes(0)) {
22
+ return { ok: false, warn: 'memory unreadable - verify (binary data in stakeholders.md)' }
23
+ }
24
+ const md = buf.toString('utf8')
25
+ const ledger = readEng(eng, SIGNAL_LEDGER)
26
+ if (/\[signal:(red|amber|green)\]/i.test(md + '\n' + ledger)) {
27
+ return { ok: true, warn: '' }
28
+ }
29
+ const trustLine = md.match(/\*\*Trust:\*\*\s*([A-Za-z?]+)/i)
30
+ if (trustLine && !/^(red|amber|green)$/i.test(trustLine[1])) {
31
+ return { ok: false, warn: 'memory unreadable - verify (invalid trust value)' }
32
+ }
33
+ const table = parseMdTable(md)
34
+ const meaningful = md.split('\n').filter(l => {
35
+ const t = l.trim()
36
+ return t && !t.startsWith('#') && !t.startsWith('<!--') && !/^\|?\s*:?-{3,}/.test(t)
37
+ }).length
38
+ // Content present but no table and no structured signal → do not invent "green"
39
+ if (meaningful >= 3 && !table) {
40
+ return { ok: false, warn: 'memory unreadable - verify (stakeholders.md unparseable)' }
41
+ }
42
+ return { ok: true, warn: '' }
43
+ }
44
+
45
+ // Subject key for a signal-history line - first real name word (same spirit as
46
+ // extractStakeholders). A green about Randy must not clear an amber about Denise.
47
+ // Strip author tags [@email-local] so attribution never becomes the subject key.
48
+ function signalSubjectKey(text) {
49
+ const cleaned = String(text).replace(/\[@[^\]]+\]/g, '').replace(/\([^)]*\)/g, '')
50
+ const words = cleaned.split(/\s+/).filter(w => w && !/^(dr|mr|mrs|ms)\.?$/i.test(w))
51
+ const frag = (words[0] || '').replace(/[^a-z0-9]/gi, '').toLowerCase()
52
+ return frag.length >= 3 ? frag : ('anon:' + cleaned.slice(0, 48).toLowerCase())
53
+ }
54
+
55
+ function parsePhase(ctx) {
56
+ // Template ships "**Phase:** land | discover | ..." - that is UNSET, not land.
57
+ const m = ctx.match(/\*\*Phase:\*\*\s*(.+)/i) || ctx.match(/^phase[:\s*]+(.+)$/im)
58
+ if (!m) return '?'
59
+ const raw = m[1].replace(/\*/g, '').trim()
60
+ if (!raw || /\|/.test(raw) || /^unset$/i.test(raw) || /^[\[(]/.test(raw)) return '?'
61
+ const one = raw.toLowerCase().match(/^(land|discover|plan|build|ship|close)\b/)
62
+ return one ? one[1] : '?'
63
+ }
64
+
65
+ function countOpenRisks(eng) {
66
+ const md = readClean(eng, 'risks.md')
67
+ const body = md.split(/^#{1,6}\s+Retired\b/im)[0] || md
68
+ let n = 0
69
+ for (const raw of body.split('\n')) {
70
+ const t = raw.trim()
71
+ if (!t || t.startsWith('<!--') || /^#{1,6}\s/.test(t)) continue
72
+ if (/risk\s*\|\s*status|mitigation/i.test(t) || /^\|?[\s|:-]+$/.test(t)) continue
73
+ if (/^[-*]/.test(t) || (/^\|/.test(t) && t.length > 12)) n++
74
+ }
75
+ return n
76
+ }
77
+
78
+ function nextActionLine(ctx) {
79
+ const body = sectionBody(ctx, 'Next action')
80
+ for (const raw of body.split('\n')) {
81
+ const t = raw.trim().replace(/^[-*]\s+/, '')
82
+ if (t) return t.slice(0, 120)
83
+ }
84
+ return ''
85
+ }
86
+
87
+ function computeSignals(eng) {
88
+ // readClean, not readEng: status/dashboard echo topRisk and stakeholder lines
89
+ // to the terminal and the rendered HTML - a <private> risk must never surface.
90
+ const ctx = readClean(eng, 'context.md'); const stake = readClean(eng, 'stakeholders.md'); const risks = readClean(eng, 'risks.md')
91
+ // Prefer structured tokens from stakeholders + CLI ledger (ledger survives wipes)
92
+ const signalText = stake + '\n' + readClean(eng, SIGNAL_LEDGER)
93
+ const phase = parsePhase(ctx)
94
+ // Latest signal PER stakeholder, then worst-of those actives.
95
+ // Global "latest wins" let a green from person B hide a sponsor crisis on A.
96
+ const byPerson = new Map()
97
+ for (const l of signalText.split('\n')) {
98
+ const sm = l.match(/\[signal:(red|amber|green)\]/i)
99
+ if (!sm) continue
100
+ const date = (l.match(/\[(\d{4}-\d{2}-\d{2})\]/) || [])[1] || ''
101
+ const text = l.replace(/^\s*-\s*/, '')
102
+ .replace(/\[signal:(red|amber|green)\]/i, '')
103
+ .replace(/\[\d{4}-\d{2}-\d{2}\]/, '')
104
+ .replace(/\[@[^\]]+\]/g, '')
105
+ .trim()
106
+ const key = signalSubjectKey(text)
107
+ const prev = byPerson.get(key)
108
+ if (!prev || date >= prev.date) byPerson.set(key, { date, sig: sm[1].toLowerCase(), text })
109
+ }
110
+ const RANK = { red: 0, amber: 1, green: 2 }
111
+ let worst = null
112
+ for (const s of byPerson.values()) {
113
+ if (!worst || RANK[s.sig] < RANK[worst.sig] || (RANK[s.sig] === RANK[worst.sig] && s.date >= worst.date)) {
114
+ worst = s
115
+ }
116
+ }
117
+ const mem = stakeholdersMemoryHealth(eng)
118
+ let trust, signalAge = null, stale = false, trustReason = ''
119
+ if (!mem.ok && !worst) {
120
+ trust = 'amber'
121
+ trustReason = mem.warn
122
+ } else if (worst) {
123
+ trust = worst.sig === 'red' ? 'RED' : worst.sig
124
+ trustReason = (worst.text || '').slice(0, 80)
125
+ if (worst.date) {
126
+ signalAge = Math.max(0, Math.floor((Date.now() - Date.parse(worst.date)) / 86400000))
127
+ stale = signalAge > 21
128
+ }
129
+ } else {
130
+ const sLines = stake.split('\n').filter(l => !(/green/i.test(l) && /red|amber/i.test(l)))
131
+ trust = sLines.some(l => /\bred\b/i.test(l)) ? 'RED'
132
+ : sLines.some(l => /amber|gone quiet|routing around|escalat/i.test(l)) ? 'amber' : 'green'
133
+ }
134
+ const topRisk = (risks.split('\n').find(l => {
135
+ const t = l.trim()
136
+ return /^[-|]/.test(t) && t.length > 20 && !/^\|?[-\s|]+$/.test(t) &&
137
+ !/risk\s*\|\s*status|mitigation/i.test(t) && !t.startsWith('<!--')
138
+ }) || '').replace(/\|/g, ' ').replace(/\s+/g, ' ').trim().slice(0, 80)
139
+ // Prefer trust trigger / memory warn over a random risk line; always keep mem.warn available
140
+ const reason = (trustReason || mem.warn) ? (trustReason || mem.warn) : topRisk
141
+ const openRisks = countOpenRisks(eng)
142
+ const nextAction = nextActionLine(ctx)
143
+ let updated = 'never', ageDays = Infinity
144
+ try {
145
+ ageDays = Math.floor((Date.now() - fs.statSync(path.join(eng, 'context.md')).mtimeMs) / 86400000)
146
+ updated = ageDays === 0 ? 'today' : `${ageDays}d ago`
147
+ } catch (_) {}
148
+ const dirty = memoryDirtyManual(eng)
149
+ return {
150
+ phase, trust, signalAge, stale, topRisk, reason, memoryWarn: mem.warn,
151
+ dirtyFiles: dirty, openRisks, nextAction, updated, ageDays,
152
+ }
153
+ }
154
+
155
+ function resumeTriage(eng) {
156
+ const s = computeSignals(eng)
157
+ const label = s.trust + (s.stale ? '?' : '')
158
+ const phase = s.phase === '?' ? 'unset' : s.phase
159
+ const lines = [
160
+ `TRIAGE [${label.padEnd(6)}] phase:${phase} updated:${s.updated} open risks:${s.openRisks}`,
161
+ ]
162
+ if (s.reason) {
163
+ const age = s.signalAge != null ? ` (${s.signalAge}d old${s.stale ? ', STALE - reconfirm' : ''})` : ''
164
+ lines.push(` trust: ${s.reason}${age}`)
165
+ }
166
+ // Always surface corruption / unreadable memory - even when trust still reads green
167
+ if (s.memoryWarn) lines.push(` memory: ${s.memoryWarn}`)
168
+ if (s.dirtyFiles && s.dirtyFiles.length) {
169
+ lines.push(` ⚠ memory dirty (uncommitted manual edits): ${s.dirtyFiles.slice(0, 5).join(', ')}${s.dirtyFiles.length > 5 ? '…' : ''}`)
170
+ lines.push(' review before relying on the ledger - fde writes will not auto-commit these')
171
+ }
172
+ if (s.nextAction) lines.push(` next: ${s.nextAction}`)
173
+ else lines.push(' next: (none set - add under ## Next action in context.md)')
174
+ return lines.join('\n')
175
+ }
176
+
177
+ return {
178
+ stakeholdersMemoryHealth,
179
+ signalSubjectKey,
180
+ parsePhase,
181
+ countOpenRisks,
182
+ nextActionLine,
183
+ computeSignals,
184
+ resumeTriage,
185
+ }
186
+ }
187
+
188
+ module.exports = { createTrustApi }
@@ -136,7 +136,7 @@ bounded_context() {
136
136
  CONTENT=""
137
137
 
138
138
  # Lean pointer only - never cat SKILL.md. Methods load on @fde / skill trigger.
139
- CONTENT="${CONTENT}fdeops: engagement fieldbook active. For field methods, invoke @fde (loads skills/fde/SKILL.md + one references/*.md). Plumbing is zero-token CLI: fde triage | resume | debrief | prep | receipts | status.\n\n"
139
+ CONTENT="${CONTENT}fdeops: engagement fieldbook active. Human speaks plain language with @fde - you (the agent) run the local fde CLI for memory plumbing; never ask the human to type fde commands. Load skills/fde/SKILL.md when @fde triggers.\n\n"
140
140
 
141
141
  # Same TRIAGE block as `fde resume` / `fde triage` - Monday morning must not
142
142
  # depend on the model remembering to run a CLI command. Prefer the installed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fdeops",
3
- "version": "3.9.5",
3
+ "version": "3.9.8",
4
4
  "description": "Field kit for engineers embedded in client work - a real CLI (recon, memory, portfolio), one @fde skill with field judgment on top, and hooks that make it automatic. Claude Code plugin and any agent that loads skills.",
5
5
  "bin": {
6
6
  "fdeops": "bin/install.js",
@@ -1,17 +1,26 @@
1
1
  ---
2
2
  name: fde
3
- description: Second brain for Forward Deployed Engineers. One @fde router over field methods plus a local CLI for scan, memory, and receipts. Tell it your situation it routes, does the work, and writes the engagement fieldbook.
3
+ description: Second brain for Forward Deployed Engineers. The human describes the situation in plain language with @fde - you route, run the local fde CLI for memory plumbing, and write the fieldbook. Never ask the human to type fde commands.
4
4
  ---
5
5
 
6
6
  # @fde
7
7
 
8
8
  ## Audience (read this first)
9
9
 
10
- - **FDE** = the **human** who types `@fde` in the chat.
10
+ - **FDE** = the **human** who types `@fde` (or plain language) in the chat.
11
11
  - **You (the model)** = the **AI coding agent** running this skill - not a human colleague, not the client's staff.
12
12
 
13
13
  When this skill says "ask the FDE," it means the human. When it says "write to `.fde/`," you (the AI) write the files.
14
14
 
15
+ ## Human surface vs agent plumbing (non-negotiable)
16
+
17
+ | Who | Interface |
18
+ |-----|-----------|
19
+ | **FDE (human)** | `@fde` + natural language. Examples: "debrief these notes", "prep me for tomorrow's sponsor meeting", "when did we agree to drop that?", "draft the sponsor update". |
20
+ | **You (agent)** | Run the local `fde` CLI for deterministic memory work. Never tell the FDE to type `fde …` (except if setup is missing - then **you** run `fde resume --init <name>` after one clarifying question). |
21
+
22
+ If you catch yourself saying "run `fde debrief --smart notes.txt`" to the human - **stop**. Run it yourself (or write a temp notes file and run it), then show the human the result in plain language for confirm/reject.
23
+
15
24
  ## Purpose
16
25
 
17
26
  The single entry point for an entire client engagement. Field methods cover the FDE lifecycle (land through close, plus daily verbs and overlays). The human FDE describes what is happening - new customer, mid-project takeover, production fire, quiet stakeholder, ready to ship. You read the engagement memory, route to the right method, **do the work**, and leave the memory updated so the next session starts where this one ended.
@@ -36,6 +45,7 @@ These stop confident fiction. They are not optional soft tips.
36
45
 
37
46
  | Temptation | Gate |
38
47
  |------------|------|
48
+ | Tell the FDE to run `fde debrief` / `fde prep` / `fde receipts` themselves | **Stop.** You run the CLI; they confirm results in plain language. |
39
49
  | Invent a stakeholder, meeting, or quote to make the narrative rich | **Stop.** Write `unknown - ask: <question>`. One fake name poisons every real citation. |
40
50
  | Route to a phase because it "feels senior" while the signal is muddy | **Stop.** Playback + one natural question, or name the ambiguity ("discover or rescue — leaning X because…"). |
41
51
  | Fill `success.md` / `terrain.md` with plausible defaults when the brief is thin | **Stop.** Run **brief interrogation** in land/discover (one Q + GUESS + confidence) until you can write without guessing, or leave gaps explicit. |
@@ -54,21 +64,22 @@ When NOT to interrogate or challenge: unambiguous one-liners, mechanical ops, FD
54
64
 
55
65
  **Engagement path - zero ceremony.** Run `fde resume` (fallback: `node ~/.claude/fdeops/fde.js resume`). The **workspace registry** (written once by `fde resume --init <name>`) is the normal path; resolution order is env var override → registry → pointer file → workspace-name match (read-only) → `./.fde`. Writes require a bind (or `FDEOPS_ENGAGEMENT`), not folder name alone. It prints a **bounded** view of `context.md` - the curated head (state, next action) plus the most recent activity, with the older session log collapsed (use `fde resume --full` when you genuinely need the whole history). If it reports NO ENGAGEMENT: confirm the client name in conversation (one question), then run `fde resume --init <name>` yourself - the one setup step; the FDE never runs setup commands. Never install fdeops on infrastructure the FDE does not control.
56
66
 
57
- **The `fde` CLI does the deterministic work - use it instead of improvising shell:**
67
+ **You run the `fde` CLI for deterministic work - never improvise shell, never hand the command to the FDE:**
58
68
 
59
- | Mechanics | Command |
60
- |-----------|---------|
61
- | Load/create engagement memory | `fde resume` (bounded) / `fde resume --full` / `fde resume --init <name>` |
62
- | Day-1 repo recon (facts + ASK ON DAY 1) | `fde scan` - then YOU interpret against the brief |
63
- | Structured memory appends | `fde log decision\|risk\|delivery\|contact "<text>"` - on `contact`, add `--signal green\|amber\|red` to write a `[signal:…]` token |
64
- | Meeting notes memory | `fde debrief <file>` (or stdin) - `decision:`/`risk:`/`delivery:`/`contact:` prefixed lines route to their `.fde` file with dates; the rest lands as a dated block in `context.md` |
65
- | "What did we agree?" with dates | `fde receipts <term>` |
66
- | Portfolio across customers | `fde status` - trust from the latest dated `[signal:…]` token (stale after 21 days; keyword heuristic only as fallback); verify before acting |
67
- | Visual portfolio (one local page) | `fde dashboard` - renders `.fde/` `fieldbook.html`, deterministic, 0 tokens |
69
+ | When the FDE says (approx.) | You run |
70
+ |-----------------------------|---------|
71
+ | (session entry / where are we) | `fde resume` or use injected TRIAGE; `fde resume --init <name>` only if unbound |
72
+ | Day-1 look at the repo | `fde scan` - then you interpret against the brief |
73
+ | "Debrief these notes" / pastes meeting notes | Prefer `fde debrief --smart <notes>` show propose → on confirm `fde debrief --apply`. Fallback: structure `decision:`/`risk:`/`delivery:`/`contact:` lines yourself, show FDE, then `fde debrief` |
74
+ | "Prep me for the meeting with …" / walk-in brief | `fde prep "<short label>"` - present the brief in plain language; do not invent facts missing from `.fde/` |
75
+ | "When did we agree…?" / scope dispute | `fde receipts <term>` - answer with dates; no hit = gap, not proof |
76
+ | "Draft the sponsor update" / how are we doing | `fde status` then follow `references/status.md` for the narrative |
77
+ | "Log that they went quiet" / trust signal | `fde log contact "…" --signal amber\|green\|red` (after FDE confirms the read) |
78
+ | Want the HTML fieldbook | `fde dashboard` |
68
79
 
69
- **The debrief verb.** When the FDE shares meeting notes or a transcript, or says "debrief": structure the notes into lines prefixed `decision:` / `risk:` / `delivery:` / `contact:` (append `[signal:green|amber|red]` to a `contact:` line when the notes carry trust information), leave everything else unprefixed, **show the structured version to the FDE for confirmation**, then pipe it to `fde debrief`. Routing and dating are deterministic and cost zero tokens - your judgment is the structuring. Signal-reading guidance: `references/debrief.md`.
80
+ **The debrief verb.** Highest-frequency loop. When the FDE shares notes or says "debrief": **you** run the smart path (write notes to a temp file if needed). Show the proposed routing in plain language. Only `--apply` (or pipe prefixed lines) after they confirm. Never ask them to run the CLI. Detail: `references/debrief.md`.
70
81
 
71
- CLI missing → use the manual fallback commands inside each reference.
82
+ CLI missing → use the manual fallbacks inside each reference (still you write files; still never ask the FDE to run setup).
72
83
 
73
84
  **Token model - where the cost goes.** Deterministic work is the CLI's job and costs **zero model tokens**: memory writes, recon, receipts, status, dashboard, and the bounded `fde resume`. Session-start hooks inject **TRIAGE + bounded `context.md` + a one-line pointer** - never this full skill body (that loads only when `@fde` triggers). Spend tokens only on judgment - reading the situation, routing, running the phase method, writing the artifact. Three rules keep a full day of FDE work cheap: load the router first and pull **one** reference only when you route to it; never dump a whole `.fde/` file into context - read the bounded resume, or `fde receipts <term>` for a targeted slice; don't re-read files you already have. The expensive model should fire for real decisions, not for plumbing the CLI already does.
74
85
 
@@ -241,6 +252,7 @@ Running the engagement and ending it well.
241
252
  | Weekly update due, "need to send the sponsor something" | status | `references/status.md` |
242
253
  | Demo coming up, show-and-tell, exec walkthrough | demo-prep | `references/demo-prep.md` |
243
254
  | Just out of a meeting, raw notes, "they said…", "debrief" | debrief | the debrief verb (above) + `references/debrief.md` |
255
+ | Prep me for a meeting / walk-in brief / "what should I know before I talk to…" | - | run `fde prep "<label>"`, present in plain language |
244
256
  | Sponsor's boss needs a summary, board update, justify continued investment | exec-narrative | `references/exec-narrative.md` |
245
257
  | Status across all my customers | dashboard | `references/dashboard.md` |
246
258
  | Juggling 2+ customers, losing track, context-switching | multi-customer-ops | `references/multi-customer-ops.md` |
@@ -1,36 +1,51 @@
1
1
  # debrief - capture the meeting before it evaporates
2
2
 
3
- **Enter when:** the FDE just left a meeting/call and dumps raw notes, a transcript, or "they said…". The highest-frequency moment in FDE life (3–5×/day). Capture within the hour - memory decays fast.
3
+ **Enter when:** the FDE just left a meeting/call and dumps raw notes, a transcript, or "they said…". Highest-frequency moment in FDE life. Capture within the hour.
4
4
 
5
- **Read first:** `context.md`, `stakeholders.md` (to update signals against what's known).
5
+ **Read first:** `context.md`, `stakeholders.md` (signals against what's known).
6
+
7
+ **Who runs the CLI:** you (the agent). Never tell the FDE to type `fde debrief …`.
6
8
 
7
9
  ## Method (you do this work)
8
10
 
9
- Take the raw dump exactly as given. Extract into five buckets - **only what was actually said**; rule 4 applies with full force here (no invented names, no embellished quotes):
11
+ ### Preferred path - smart debrief (messy notes)
12
+
13
+ 1. Save the FDE's notes to a temp `.md` file in the workspace (or pipe stdin).
14
+ 2. Run `fde debrief --smart <notes.md>` (or `npx fdeops debrief --smart …`).
15
+ 3. Show the **proposed** routing in plain language (what would become decisions, risks, contacts, etc.).
16
+ 4. On FDE confirm → run `fde debrief --apply`.
17
+ 5. On reject → stop; ask what to change; do not apply.
18
+
19
+ No invented names or quotes. If the propose looks wrong, fix with judgment then re-propose or use the fallback path.
20
+
21
+ ### Fallback - you structure, then route
22
+
23
+ If `--smart` is unavailable or the notes are already cleanly prefixed:
10
24
 
11
- 1. **Decisions** - what was agreed, by whom, in their words where possible.
12
- 2. **Action items** - owner + due date. Unowned actions get `owner: unknown - ask`.
13
- 3. **Stakeholder signals** - tone shifts, hesitations, who went quiet, who pushed back, verbatim quotes that matter. Map each to green/amber/red movement *with the evidence*.
14
- 4. **Risks** - new ones surfaced, old ones confirmed or retired.
15
- 5. **Open questions** - what the FDE must chase before the next session.
25
+ 1. Extract into buckets - **only what was actually said**:
26
+ - **Decisions** - agreed, by whom, in their words where possible
27
+ - **Action items** - owner + due; unowned `owner: unknown - ask`
28
+ - **Stakeholder signals** - tone shifts with evidence green/amber/red
29
+ - **Risks** - new / confirmed / retired
30
+ - **Open questions** - what to chase next
31
+ 2. Format lines as `decision:` / `risk:` / `delivery:` / `contact:` (contacts may end with `[signal:green|amber|red]`).
32
+ 3. Show that structured version to the FDE for confirmation.
33
+ 4. Pipe to `fde debrief` (or write a file and run it).
16
34
 
17
- Ambiguity in the dump ask **one** clarifying question, then write. Never stall the capture on completeness.
35
+ One clarifying question max if the dump is ambiguous - then write. Never stall capture on completeness.
18
36
 
19
- ## Artifact (this IS the memory - write immediately)
37
+ ## Artifact
20
38
 
21
- - Decisions `decisions.md` via `fde log decision "<text> (meeting: <who>, <date>)"` or direct append, dated and attributed.
22
- - Signal changes → `stakeholders.md`: update the signal column with evidence + date. A quote moves a signal; a vibe does not.
23
- - Risks → `risks.md`, dated.
24
- - Action items + open questions → `context.md` under "Next actions".
25
- - Sacred/sensitive things mentioned (data, systems, politics) → `trust-profile.md` if new.
39
+ - Smart apply / debrief CLI writes the dated routes into the right `.fde/` files.
40
+ - If you must write directly: decisions → `decisions.md`; signals `stakeholders.md` Signal history; risks `risks.md`; next actions `context.md`. Prefer the CLI.
26
41
 
27
42
  ## Checkpoint
28
43
 
29
- Read back the 23 most consequential captures in one breath - "Logged: descope agreed with Dana; ops lead went amber (quote attached); CISO ticket now blocking" - so the FDE can correct on the spot. Then stop. No summary theatre.
44
+ Read back the 2-3 most consequential captures in one breath - so the FDE can correct on the spot. Then stop. No summary theatre.
30
45
 
31
46
  ## Principles
32
47
 
33
- - Capture within the hour or lose the nuance forever.
34
- - The verbatim quote outranks the paraphrase. The hesitation outranks the quote.
35
- - Signals move on evidence, never on inference.
36
- - A meeting that produced no decisions and no actions - say so; that's a finding about the meeting.
48
+ - Capture within the hour or lose the nuance.
49
+ - Verbatim quote outranks paraphrase; hesitation outranks quote.
50
+ - Signals move on evidence, never on vibe alone.
51
+ - A meeting with no decisions and no actions - say so; that is a finding.