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.
- package/README.md +25 -62
- package/adapters/cursor.fde.mdc +3 -2
- package/bin/check.js +30 -7
- package/bin/fde.js +180 -900
- package/bin/install.js +6 -1
- package/bin/lib/memory.js +164 -0
- package/bin/lib/render.js +666 -0
- package/bin/lib/trust.js +188 -0
- package/hooks/session-start +1 -1
- package/package.json +1 -1
- package/skills/fde/SKILL.md +26 -14
- package/skills/fde/references/debrief.md +35 -20
package/bin/lib/trust.js
ADDED
|
@@ -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 }
|
package/hooks/session-start
CHANGED
|
@@ -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.
|
|
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.
|
|
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",
|
package/skills/fde/SKILL.md
CHANGED
|
@@ -1,17 +1,26 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: fde
|
|
3
|
-
description: Second brain for Forward Deployed Engineers.
|
|
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
|
-
**
|
|
67
|
+
**You run the `fde` CLI for deterministic work - never improvise shell, never hand the command to the FDE:**
|
|
58
68
|
|
|
59
|
-
|
|
|
60
|
-
|
|
61
|
-
|
|
|
62
|
-
| Day-1
|
|
63
|
-
|
|
|
64
|
-
|
|
|
65
|
-
| "
|
|
66
|
-
|
|
|
67
|
-
|
|
|
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
|
|
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
|
|
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…".
|
|
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` (
|
|
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
|
-
|
|
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.
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
|
|
35
|
+
One clarifying question max if the dump is ambiguous - then write. Never stall capture on completeness.
|
|
18
36
|
|
|
19
|
-
## Artifact
|
|
37
|
+
## Artifact
|
|
20
38
|
|
|
21
|
-
-
|
|
22
|
-
-
|
|
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 2
|
|
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
|
|
34
|
-
-
|
|
35
|
-
- Signals move on evidence, never on
|
|
36
|
-
- A 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.
|