fdeops 3.7.4 → 3.7.7

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 CHANGED
@@ -17,7 +17,7 @@ The **second brain for Forward Deployed Engineers** - engineers embedded at a cl
17
17
  written as a side effect of the work
18
18
  ```
19
19
 
20
- Describe your situation - `@fde` routes to the phase, runs the method, and every artifact lands in that shared memory. Nothing to maintain by hand.
20
+ Describe your situation - `@fde` routes to the right method and writes the matching `.fde/` artifact. Phase methods (land → close) live in the skill; the CLI owns scan, memory, and receipts. You still confirm judgment — the fieldbook does not maintain itself without you.
21
21
 
22
22
  ---
23
23
 
@@ -86,7 +86,7 @@ Not ready to install? `npx fdeops scan` runs on any repo you can read - day-1 re
86
86
 
87
87
  ## The week
88
88
 
89
- This is the actual habit, not the 35 skills:
89
+ This is the actual habit — the high-frequency loop, not the full skill matrix:
90
90
 
91
91
  - **Monday morning** - open your agent, context loads, you're not re-explaining anything
92
92
  - **After a meeting** - `fde debrief` turns raw notes into dated decisions, risks, and signals
@@ -148,15 +148,17 @@ fde debrief notes.md # route meeting notes into memory (also reads
148
148
  fde log decision "descope agreed with Kowalczyk"
149
149
  fde log contact "Denise gone quiet" --signal amber
150
150
  fde receipts <term> # dated search; no hit = a gap in the record, not proof of absence
151
- fde status # portfolio triage across all clients (red > amber > green)
152
- fde dashboard # render every engagement into one offline HTML fieldbook
151
+ fde status # current engagement triage (add --all for every client)
152
+ fde dashboard # current engagement fieldbook (add --all for every client)
153
153
  ```
154
154
 
155
+ Optional: `export FDEOPS_ENGAGEMENTS_ROOT=~/path/to/engagements` to isolate init/status/dashboard from the default `~/fde-engagements`.
156
+
155
157
  The latest dated `[signal:...]` token per stakeholder drives the trust column in `status` and `dashboard`; signals older than 21 days show as stale.
156
158
 
157
159
  <p align="center"><img src="media/terminal-demo.svg" alt="fde CLI - status, scan, dashboard" width="720"/></p>
158
160
 
159
- `fde dashboard (FieldBook)` renders every engagement into one offline HTML fieldbook - engagements sorted by trust, next action and open risks per client, one glance to know where to start:
161
+ `fde dashboard` (FieldBook) renders the **current** engagement by default. Pass `--all` for every client sorted by trust:
160
162
 
161
163
  <p align="center"><img width="1336" height="624" alt="Screenshot 2026-07-08 at 12 45 07" src="https://github.com/user-attachments/assets/5683614c-7730-4a3a-860d-185053a377eb" /></p>
162
164
 
package/bin/check.js CHANGED
@@ -32,6 +32,7 @@ const requiredTemplates = [
32
32
  'decisions.md',
33
33
  'risks.md',
34
34
  'delivery.md',
35
+ 'assumptions.md',
35
36
  ]
36
37
 
37
38
  for (const f of requiredTemplates) {
@@ -180,7 +181,7 @@ else ok('docs/schema.md')
180
181
  if (!fs.existsSync(path.join(root, 'SECURITY.md'))) fail('SECURITY.md missing')
181
182
  else ok('SECURITY.md')
182
183
 
183
- const exampleFiles = ['reality.md', 'decisions.md', 'delivery.md', 'stakeholders.md']
184
+ const exampleFiles = ['reality.md', 'decisions.md', 'delivery.md', 'stakeholders.md', 'assumptions.md']
184
185
  for (const f of exampleFiles) {
185
186
  const p = path.join(root, 'examples', 'garvey-payments', '.fde', f)
186
187
  if (!fs.existsSync(p)) fail(`examples/garvey-payments/.fde/${f} missing`)
@@ -220,12 +221,15 @@ for (const h of ['session-start', 'session-stop', 'pre-compact']) {
220
221
  }
221
222
  ok('hook exec bits')
222
223
 
223
- // registry-aware hooks: `fde resume --init` binds via ~/fde-engagements/.registry,
224
- // so every hook must consult it or registry-bound users get zero auto-capture
224
+ // registry-aware hooks: `fde resume --init` binds via <root>/.registry, so every
225
+ // hook must consult it or registry-bound users get zero auto-capture. The root
226
+ // is FDEOPS_ENGAGEMENTS_ROOT with a ~/fde-engagements default - hooks must use
227
+ // the env-aware root (CLI/hook parity), not a hardcoded home path.
225
228
  for (const h of ['session-start', 'session-stop', 'pre-compact']) {
226
229
  const body = read('hooks/' + h)
227
- if (!body.includes('fde-engagements/.registry')) fail(`hooks/${h} must consult the workspace registry`)
230
+ if (!/\.registry/.test(body)) fail(`hooks/${h} must consult the workspace registry`)
228
231
  if (!body.includes('registry_engagement_dir')) fail(`hooks/${h} missing registry_engagement_dir`)
232
+ if (!body.includes('FDEOPS_ENGAGEMENTS_ROOT')) fail(`hooks/${h} must honor FDEOPS_ENGAGEMENTS_ROOT (CLI/hook root parity)`)
229
233
  }
230
234
  ok('hooks registry-aware')
231
235
 
package/bin/fde.js CHANGED
@@ -16,8 +16,8 @@
16
16
  * fde debrief [file] meeting notes → structured memory (stdin if no file)
17
17
  * fde receipts <term> "what did we agree?" - search memory with dates
18
18
  * fde capture session-end snapshot → context.md (hooks use this)
19
- * fde status portfolio across ~/fde-engagements (red/amber/green)
20
- * fde dashboard render every engagement into one local fieldbook.html
19
+ * fde status [--all] current engagement (default) or full portfolio (--all)
20
+ * fde dashboard [--all] current engagement fieldbook (default) or all (--all)
21
21
  */
22
22
  const fs = require('fs')
23
23
  const path = require('path')
@@ -25,8 +25,12 @@ const os = require('os')
25
25
  const { execSync, execFileSync } = require('child_process')
26
26
 
27
27
  const HOME = os.homedir()
28
- const ENGAGEMENTS_ROOT = path.join(HOME, 'fde-engagements')
28
+ // FDEOPS_ENGAGEMENTS_ROOT isolates init/status/dashboard (and the registry) for
29
+ // dogfood/simulations. Default remains ~/fde-engagements.
30
+ const ENGAGEMENTS_ROOT = ((process.env.FDEOPS_ENGAGEMENTS_ROOT || '').trim().replace(/^~/, HOME))
31
+ || path.join(HOME, 'fde-engagements')
29
32
  const REGISTRY = path.join(ENGAGEMENTS_ROOT, '.registry')
33
+ const DEBRIEF_MAX_BYTES = 256 * 1024
30
34
  const CODE_EXT = ['.js', '.ts', '.tsx', '.jsx', '.py', '.java', '.go', '.rb', '.cs', '.php']
31
35
  const CONF_EXT = CODE_EXT.concat(['.env', '.yaml', '.yml', '.json'])
32
36
  // one routing table for structured appends - cmdLog and cmdDebrief share it
@@ -584,7 +588,12 @@ function resumeView(md) {
584
588
  // matches the bash hook's `wc -l` and the two bounded views stay byte-aligned.
585
589
  if (lines.length > 0 && lines[lines.length - 1] === '') lines.pop()
586
590
  if (lines.length <= 160) return md
587
- let headEnd = lines.findIndex(l => l.includes('fdeops auto-capture'))
591
+ // Anchor on the "## Session end" heading, NOT the "<!-- fdeops auto-capture -->"
592
+ // comment: this text is read via readClean (stripPrivate strips HTML comments),
593
+ // so the comment is gone by the time we get here. The heading is written on the
594
+ // very next line by cmdCapture and the session-stop hook and survives redaction.
595
+ // The bash bounded_context() anchors on the same heading - keep them identical.
596
+ let headEnd = lines.findIndex(l => /^##\s+Session end\b/.test(l.trim()))
588
597
  if (headEnd === -1) headEnd = 120
589
598
  headEnd = Math.min(headEnd, 120)
590
599
  const tailStart = Math.max(headEnd, lines.length - 40)
@@ -634,10 +643,26 @@ function cmdDebrief(args) {
634
643
  if (!eng) { console.error('no engagement - run: fde resume --init <name>'); process.exit(2) }
635
644
  let input = ''
636
645
  if (args[0]) {
637
- try { input = fs.readFileSync(args[0].replace(/^~/, HOME), 'utf8') }
638
- catch (_) { console.error(`cannot read ${args[0]}`); process.exit(1) }
646
+ const notesPath = args[0].replace(/^~/, HOME)
647
+ let st
648
+ try { st = fs.statSync(notesPath) } catch (_) { console.error(`cannot read ${args[0]}`); process.exit(1) }
649
+ if (st.size > DEBRIEF_MAX_BYTES) {
650
+ console.error(`debrief refused: ${args[0]} is ${st.size} bytes (max ${DEBRIEF_MAX_BYTES}). Split the notes or paste the relevant section.`)
651
+ process.exit(1)
652
+ }
653
+ let buf
654
+ try { buf = fs.readFileSync(notesPath) } catch (_) { console.error(`cannot read ${args[0]}`); process.exit(1) }
655
+ if (buf.includes(0)) {
656
+ console.error(`debrief refused: ${args[0]} looks binary (null bytes). Paste text notes only.`)
657
+ process.exit(1)
658
+ }
659
+ input = buf.toString('utf8')
639
660
  } else {
640
661
  try { input = fs.readFileSync(0, 'utf8') } catch (_) {} // stdin until EOF
662
+ if (Buffer.byteLength(input, 'utf8') > DEBRIEF_MAX_BYTES) {
663
+ console.error(`debrief refused: stdin is over ${DEBRIEF_MAX_BYTES} bytes. Split the notes.`)
664
+ process.exit(1)
665
+ }
641
666
  }
642
667
  const d = new Date()
643
668
  const date = d.toISOString().slice(0, 10)
@@ -681,7 +706,7 @@ function cmdReceipts(args) {
681
706
  // same list let an FDE cite a sales promise as a receipt - so they get a
682
707
  // separate, clearly-labelled section that is never mistaken for the record.
683
708
  const AGREEMENTS = ['decisions.md', 'delivery.md', 'success.md', 'risks.md', 'stakeholders.md']
684
- const CLAIMS = ['brief.md', 'reality.md', 'context.md']
709
+ const CLAIMS = ['brief.md', 'assumptions.md', 'reality.md', 'context.md']
685
710
  const collect = files => {
686
711
  const hits = []
687
712
  for (const f of files) {
@@ -735,26 +760,42 @@ function cmdCapture() {
735
760
  try { fs.appendFileSync(path.join(eng, 'context.md'), block) } catch (_) {}
736
761
  }
737
762
 
738
- function cmdStatus() {
763
+ function engagementSlugFromPath(eng) {
764
+ return path.basename(path.dirname(eng))
765
+ }
766
+
767
+ function cmdStatus(args) {
768
+ const all = args.includes('--all')
739
769
  if (!fs.existsSync(ENGAGEMENTS_ROOT)) { console.log('no engagements yet - fde resume --init <name>'); return }
740
770
  const rows = []
741
- for (const d of fs.readdirSync(ENGAGEMENTS_ROOT)) {
742
- if (d.startsWith('.')) continue
743
- const eng = path.join(ENGAGEMENTS_ROOT, d, '.fde')
744
- if (!fs.existsSync(eng)) continue
771
+ if (all) {
772
+ for (const d of fs.readdirSync(ENGAGEMENTS_ROOT)) {
773
+ if (d.startsWith('.')) continue
774
+ const eng = path.join(ENGAGEMENTS_ROOT, d, '.fde')
775
+ if (!fs.existsSync(eng)) continue
776
+ const s = computeSignals(eng)
777
+ rows.push({ name: d, phase: s.phase, trust: s.trust, signalAge: s.signalAge, stale: s.stale, updated: s.updated, topRisk: s.topRisk.slice(0, 60) })
778
+ }
779
+ } else {
780
+ const eng = resolveEngagement()
781
+ if (!eng) {
782
+ console.error('no engagement bound to this workspace.\nrun: fde resume --init <name> or fde status --all')
783
+ process.exit(2)
784
+ }
745
785
  const s = computeSignals(eng)
746
- rows.push({ name: d, phase: s.phase, trust: s.trust, signalAge: s.signalAge, stale: s.stale, updated: s.updated, topRisk: s.topRisk.slice(0, 60) })
786
+ rows.push({ name: engagementSlugFromPath(eng), phase: s.phase, trust: s.trust, signalAge: s.signalAge, stale: s.stale, updated: s.updated, topRisk: s.topRisk.slice(0, 60) })
747
787
  }
748
788
  if (!rows.length) { console.log('no engagements yet'); return }
749
789
  const order = { RED: 0, amber: 1, green: 2 }
750
790
  rows.sort((a, b) => order[a.trust] - order[b.trust])
751
- console.log('FDE PORTFOLIO - trust-first triage (heuristic: red > amber > green)\n')
791
+ console.log((all ? 'FDE PORTFOLIO' : 'FDE STATUS') + ' - trust-first triage (heuristic: red > amber > green)\n')
752
792
  for (const r of rows) {
753
793
  // "amber?" = structured signal went stale (>21d) - reconfirm before trusting it
754
794
  const label = r.trust + (r.stale ? '?' : '')
755
795
  const sig = r.signalAge != null ? `signal ${r.signalAge}d old${r.stale ? ' (STALE - reconfirm)' : ''} ` : ''
756
796
  console.log(` [${label.padEnd(6)}] ${r.name.padEnd(24)} phase:${r.phase.padEnd(10)} updated:${r.updated.padEnd(8)} ${sig}${r.topRisk}`)
757
797
  }
798
+ if (!all) console.log('\n(current engagement only - pass --all for the full portfolio)')
758
799
  console.log('\ntrust: latest [signal:x] token in stakeholders.md wins (fde log contact --signal, fde debrief); keyword heuristic only when none exists - verify before acting.')
759
800
  }
760
801
 
@@ -839,8 +880,12 @@ function hasRealContent(md) {
839
880
  return txt.length > 0
840
881
  }
841
882
 
842
- function gatherEngagements() {
883
+ function gatherEngagements(opts = {}) {
843
884
  const list = []
885
+ if (opts.only) {
886
+ list.push({ name: engagementSlugFromPath(opts.only), dir: opts.only, signals: computeSignals(opts.only) })
887
+ return list
888
+ }
844
889
  if (!fs.existsSync(ENGAGEMENTS_ROOT)) return list
845
890
  for (const d of fs.readdirSync(ENGAGEMENTS_ROOT).sort()) {
846
891
  if (d.startsWith('.')) continue
@@ -1364,11 +1409,22 @@ function paletteItemsHtml(ordered) {
1364
1409
  }
1365
1410
 
1366
1411
  function cmdDashboard(args) {
1412
+ const all = args.includes('--all')
1367
1413
  const outIdx = args.indexOf('--out')
1368
1414
  const outPath = outIdx !== -1 && args[outIdx + 1]
1369
1415
  ? path.resolve(args[outIdx + 1].replace(/^~/, HOME))
1370
- : path.join(ENGAGEMENTS_ROOT, 'fieldbook.html')
1371
- const engagements = gatherEngagements()
1416
+ : path.join(ENGAGEMENTS_ROOT, all ? 'fieldbook.html' : 'fieldbook-current.html')
1417
+ let engagements
1418
+ if (all) {
1419
+ engagements = gatherEngagements()
1420
+ } else {
1421
+ const eng = resolveEngagement()
1422
+ if (!eng) {
1423
+ console.error('no engagement bound to this workspace.\nrun: fde resume --init <name> or fde dashboard --all')
1424
+ process.exit(2)
1425
+ }
1426
+ engagements = gatherEngagements({ only: eng })
1427
+ }
1372
1428
  const counts = { green: 0, amber: 0, RED: 0 }
1373
1429
  engagements.forEach(e => { counts[e.signals.trust]++ })
1374
1430
  const today = formatToday(new Date())
@@ -1506,7 +1562,7 @@ switch (cmd) {
1506
1562
  case 'debrief': cmdDebrief(args); break
1507
1563
  case 'receipts': cmdReceipts(args); break
1508
1564
  case 'capture': cmdCapture(); break
1509
- case 'status': cmdStatus(); break
1565
+ case 'status': cmdStatus(args); break
1510
1566
  case 'dashboard': cmdDashboard(args); break
1511
1567
  default:
1512
1568
  console.log(`fde - deterministic core of fdeops
@@ -1519,6 +1575,7 @@ switch (cmd) {
1519
1575
  fde debrief [file] meeting notes → memory: decision:/risk:/delivery:/contact: lines route, rest → context.md (stdin if no file; --dry-run previews)
1520
1576
  fde receipts <term> "what did we agree?" with dates
1521
1577
  fde capture session-end memory snapshot (hooks use this)
1522
- fde status portfolio across all engagements
1523
- fde dashboard render every engagement into one local fieldbook.html`)
1578
+ fde status [--all] current engagement status (pass --all for full portfolio)
1579
+ fde dashboard [--all] current engagement fieldbook (pass --all for every client)
1580
+ env FDEOPS_ENGAGEMENTS_ROOT override ~/fde-engagements (init/status/dashboard/registry)`)
1524
1581
  }
package/hooks/pre-compact CHANGED
@@ -16,7 +16,11 @@ resolve_engagement_dir() {
16
16
  # Lines are "<workspace-path> <slug>": the path may contain spaces, the slug
17
17
  # never does, so split on the LAST space (same as the JS lastIndexOf parse).
18
18
  registry_engagement_dir() {
19
- local reg="$HOME/fde-engagements/.registry" slug
19
+ # FDEOPS_ENGAGEMENTS_ROOT mirrors ENGAGEMENTS_ROOT in bin/fde.js - the CLI and
20
+ # hooks MUST agree on the root or an isolated setup gets split memory.
21
+ local root="${FDEOPS_ENGAGEMENTS_ROOT:-$HOME/fde-engagements}"
22
+ root="${root/#\~/$HOME}"
23
+ local reg="$root/.registry" slug
20
24
  [ -f "$reg" ] || return 1
21
25
  slug=$(awk 'BEGIN{ws=ENVIRON["PWD"]; best=-1}
22
26
  { i = match($0, / [^ ]*$/)
@@ -28,7 +32,7 @@ registry_engagement_dir() {
28
32
  } }
29
33
  END { if (best >= 0) print slug }' "$reg" 2>/dev/null)
30
34
  [ -z "$slug" ] && return 1
31
- [ -d "$HOME/fde-engagements/$slug/.fde" ] && printf '%s\n' "$HOME/fde-engagements/$slug/.fde" && return 0
35
+ [ -d "$root/$slug/.fde" ] && printf '%s\n' "$root/$slug/.fde" && return 0
32
36
  return 1
33
37
  }
34
38
 
@@ -28,7 +28,11 @@ resolve_engagement_dir() {
28
28
  # Lines are "<workspace-path> <slug>": the path may contain spaces, the slug
29
29
  # never does, so split on the LAST space (same as the JS lastIndexOf parse).
30
30
  registry_engagement_dir() {
31
- local reg="$HOME/fde-engagements/.registry" slug
31
+ # FDEOPS_ENGAGEMENTS_ROOT mirrors ENGAGEMENTS_ROOT in bin/fde.js - the CLI and
32
+ # hooks MUST agree on the root or an isolated setup gets split memory.
33
+ local root="${FDEOPS_ENGAGEMENTS_ROOT:-$HOME/fde-engagements}"
34
+ root="${root/#\~/$HOME}"
35
+ local reg="$root/.registry" slug
32
36
  [ -f "$reg" ] || return 1
33
37
  slug=$(awk 'BEGIN{ws=ENVIRON["PWD"]; best=-1}
34
38
  { i = match($0, / [^ ]*$/)
@@ -40,7 +44,7 @@ registry_engagement_dir() {
40
44
  } }
41
45
  END { if (best >= 0) print slug }' "$reg" 2>/dev/null)
42
46
  [ -z "$slug" ] && return 1
43
- [ -d "$HOME/fde-engagements/$slug/.fde" ] && printf '%s\n' "$HOME/fde-engagements/$slug/.fde" && return 0
47
+ [ -d "$root/$slug/.fde" ] && printf '%s\n' "$root/$slug/.fde" && return 0
44
48
  return 1
45
49
  }
46
50
 
@@ -122,7 +126,10 @@ bounded_context() {
122
126
  total=$(wc -l < "$f" 2>/dev/null | tr -d ' ')
123
127
  [ -z "$total" ] && { cat "$f"; return; }
124
128
  if [ "$total" -le 160 ]; then cat "$f"; return; fi
125
- head_end=$(grep -n -m1 'fdeops auto-capture' "$f" 2>/dev/null | cut -d: -f1)
129
+ # Anchor on the "## Session end" heading, matching resumeView() in bin/fde.js.
130
+ # The JS path reads via readClean (strips HTML comments), so both sides must
131
+ # anchor on a marker that survives redaction - the heading, not the comment.
132
+ head_end=$(grep -n -m1 '^## Session end' "$f" 2>/dev/null | cut -d: -f1)
126
133
  if [ -n "$head_end" ]; then head_end=$((head_end - 1)); else head_end=120; fi
127
134
  [ "$head_end" -gt 120 ] && head_end=120
128
135
  [ "$head_end" -lt 0 ] && head_end=0
@@ -24,7 +24,11 @@ resolve_engagement_dir() {
24
24
  # Lines are "<workspace-path> <slug>": the path may contain spaces, the slug
25
25
  # never does, so split on the LAST space (same as the JS lastIndexOf parse).
26
26
  registry_engagement_dir() {
27
- local reg="$HOME/fde-engagements/.registry" slug
27
+ # FDEOPS_ENGAGEMENTS_ROOT mirrors ENGAGEMENTS_ROOT in bin/fde.js - the CLI and
28
+ # hooks MUST agree on the root or an isolated setup gets split memory.
29
+ local root="${FDEOPS_ENGAGEMENTS_ROOT:-$HOME/fde-engagements}"
30
+ root="${root/#\~/$HOME}"
31
+ local reg="$root/.registry" slug
28
32
  [ -f "$reg" ] || return 1
29
33
  slug=$(awk 'BEGIN{ws=ENVIRON["PWD"]; best=-1}
30
34
  { i = match($0, / [^ ]*$/)
@@ -36,7 +40,7 @@ registry_engagement_dir() {
36
40
  } }
37
41
  END { if (best >= 0) print slug }' "$reg" 2>/dev/null)
38
42
  [ -z "$slug" ] && return 1
39
- [ -d "$HOME/fde-engagements/$slug/.fde" ] && printf '%s\n' "$HOME/fde-engagements/$slug/.fde" && return 0
43
+ [ -d "$root/$slug/.fde" ] && printf '%s\n' "$root/$slug/.fde" && return 0
40
44
  return 1
41
45
  }
42
46
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fdeops",
3
- "version": "3.7.4",
3
+ "version": "3.7.7",
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,6 +1,6 @@
1
1
  ---
2
2
  name: fde
3
- description: The second brain for Forward Deployed Engineers. 35 skills across 6 domains - from first meeting to final handoff. Tell it your situation, it routes to the right skill, does the work, and the engagement memory writes itself.
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.
4
4
  ---
5
5
 
6
6
  # @fde
@@ -14,7 +14,7 @@ When this skill says "ask the FDE," it means the human. When it says "write to `
14
14
 
15
15
  ## Purpose
16
16
 
17
- The single entry point for an entire client engagement - 35 skills across 6 domains covering the full FDE lifecycle. 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 skill, **do the work**, and leave the memory updated so the next session starts where this one ended.
17
+ 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.
18
18
 
19
19
  You are not an advisor reading tips aloud. Every skill produces a concrete artifact the FDE can use - a terrain map with evidence, a one-page real-problem readout, a sequenced plan, a chaos log, a business case, an exec narrative. The artifact is the deliverable AND the memory.
20
20
 
@@ -153,7 +153,7 @@ After updating `.fde/` artifacts, suggest the ONE next move that accelerates the
153
153
 
154
154
  > "Brief written. You don't have repo access yet - want me to draft the request or are you handling that?"
155
155
 
156
- ## Routing - 6 domains, 35 skills
156
+ ## Routing - 6 domains
157
157
 
158
158
  Route on what you hear, then **read the skill reference from this skill's `references/` directory and follow its method**. Do not improvise from memory - the method is the product.
159
159
 
@@ -52,21 +52,25 @@ Evidence first, then the question. Let them reach the conclusion.
52
52
 
53
53
  ## Artifact
54
54
 
55
- **`reality.md`** - append an assumptions section:
55
+ **`assumptions.md`** - this IS the register (create if land did not). Keep one live table; do not only bury results in `reality.md`:
56
+
56
57
  ```markdown
57
- ## Assumptions audited - <date>
58
- | # | Assumption | Classification | Validation | Result | Impact |
59
- |---|-----------|---------------|------------|--------|--------|
60
- | 1 | API is the bottleneck | CRITICAL | p95 instrumentation | DISPROVED - 80% DB | Approach changes from API rewrite to query optimisation |
61
- | 2 | Team will adopt new tool | LOAD-BEARING | 3 individual interviews | CONFIRMED - 2/3 enthusiastic | Proceed with adoption plan |
62
- | 3 | Data clean enough for ML | CRITICAL | 200-record sample | PARTIAL - 12% null rate on key field | Data cleaning task added to plan |
58
+ | # | Assumption | Blast radius | How we test | Status | Evidence |
59
+ |---|------------|--------------|-------------|--------|----------|
60
+ | 1 | API is the bottleneck | CRITICAL | p95 instrumentation 24h | DISPROVED | 80% wait in DB layer (Day N) |
61
+ | 2 | Team will adopt new tool | LOAD-BEARING | 3 individual interviews | CONFIRMED | 2/3 describe a use case unprompted |
62
+ | 3 | Data clean enough for ML | CRITICAL | 200-record sample | PARTIAL OPEN follow-up | 12% nulls on key field; cleaning task added |
63
63
  ```
64
64
 
65
- **`decisions.md`** - if an assumption was disproved and the approach changed: what shifted, why, the evidence.
65
+ Status values: `OPEN` · `TESTING` · `CONFIRMED` · `DISPROVED` · `PARKED`. A CRITICAL row still `OPEN` blocks plan.
66
+
67
+ **`reality.md`** - short pointer only: which assumptions changed the approach and the implication for build.
68
+
69
+ **`decisions.md`** - if an assumption was disproved and the approach changed: what shifted, why, the evidence, same day.
66
70
 
67
71
  ## Checkpoint
68
72
 
69
- Tell the FDE: how many assumptions extracted, how many critical, which ones were tested, which changed the direction. If a critical assumption is disproved: recommend the next move (rescope, pivot, or the conversation with the sponsor) before the FDE asks.
73
+ Tell the FDE: how many assumptions extracted, how many critical, which ones were tested, which changed the direction. If a critical assumption is disproved: recommend the next move (rescope, pivot, or the conversation with the sponsor) before the FDE asks. If any CRITICAL remains OPEN: do not route to plan.
70
74
 
71
75
  ## Principles
72
76
 
@@ -127,7 +127,7 @@ The FDE's job is to make themselves replaceable. Not at handoff - every day. A c
127
127
 
128
128
  - **`decisions.md`** - each significant choice: what, alternatives considered, why this one. For non-trivial architecture decisions, present three options to the FDE (safe / pragmatic / aggressive) with costs and a recommendation - three options is a real decision; one option is a request for trust. Integration contracts go here too.
129
129
  - **`risks.md`** - new risks discovered while building.
130
- - **`delivery.md`** - what shipped, in business terms (time saved, failures prevented), and how to roll back. This is the value log the dashboard and close read.
130
+ - **`delivery.md`** - append a **value ledger** row for every ship: Date | Slice | Promised | Measured | Evidence | Rollback. "Measured" may be `pending` until the pulse exists - never skip the promised column. Narrative under Shipped is optional color; the ledger is the record status and close read.
131
131
 
132
132
  ## Checkpoint
133
133
 
@@ -154,6 +154,8 @@ Score every candidate use case before anything gets prototyped:
154
154
 
155
155
  Every line carries its evidence. `(churn: 47/90d)` `(ops lead, Day 5)` `(stated, unverified)`.
156
156
 
157
+ **`assumptions.md`** - update statuses from what discovery proved or disproved. Seed any new OPEN assumptions the brief never named. CRITICAL + OPEN must be named in the checkpoint.
158
+
157
159
  ## Checkpoint (before any build)
158
160
 
159
161
  Present to the FDE, four things, one paragraph each - no padding:
@@ -61,7 +61,24 @@ Notice: every stakeholder's initiative is P0 or P1. That's the problem this skil
61
61
 
62
62
  ## Artifact
63
63
 
64
- **`decisions.md`** - the triage table with scores, lanes, and the commitment statement. Dated. Referenced by plan and status.
64
+ **`decisions.md`** - the triage table with scores, lanes, **and an explicit Kill / Later commitment**. Dated. Referenced by plan and status.
65
+
66
+ Required closing block (plan will not treat triage as done without it):
67
+
68
+ ```markdown
69
+ ## Triage - <date>
70
+ ### Now (max 3)
71
+ | # | Initiative | Score | Why now |
72
+ ...
73
+ ### Next
74
+ ...
75
+ ### Kill / defer (not this phase)
76
+ | Initiative | Why not now | Who accepted |
77
+ |------------|-------------|--------------|
78
+ | ... | ... | <name, date> |
79
+
80
+ Commitment: we ship only Now. Additions require a removal.
81
+ ```
65
82
 
66
83
  **`reality.md`** - if triage revealed that the engagement scope is larger than the timeline supports, update the assessment.
67
84
 
@@ -72,11 +72,19 @@ Before the end of day 1, ship one visible thing: a small bug fix, a cleanup the
72
72
 
73
73
  **`trust-profile.md`** - sacred data (`<private>` tagged), fears heard, AI policy, approval chain. Sensitive: never loaded for status reads, never into subagent prompts.
74
74
 
75
- One falsifiable hypothesis about the real problem goes at the bottom of `brief.md` - discover will test it.
75
+ **`assumptions.md`** - seed every unverified claim from the brief (and the day-1 hypothesis) as rows with blast radius CRITICAL / LOAD-BEARING / CONVENIENCE and status `OPEN`. Do not wait for assumption-audit - land makes the register exist. Example:
76
+
77
+ ```markdown
78
+ | # | Assumption | Blast radius | How we test | Status | Evidence |
79
+ |---|------------|--------------|-------------|--------|----------|
80
+ | 1 | <claim from brief> | CRITICAL | <cheapest falsifying test> | OPEN | (stated, unverified) |
81
+ ```
82
+
83
+ One falsifiable hypothesis about the real problem also goes at the bottom of `brief.md` - discover / assumption-audit will test it.
76
84
 
77
85
  ## Checkpoint
78
86
 
79
- One page back to the FDE: success + sign-off owner, out-of-scope boundary, sacred data, stakeholder map with veto power, AI posture, the hypothesis. If it doesn't fit one page, the engagement isn't understood yet.
87
+ One page back to the FDE: success + sign-off owner, out-of-scope boundary, sacred data, stakeholder map with veto power, AI posture, the hypothesis, and the top CRITICAL assumptions still OPEN. If it doesn't fit one page, the engagement isn't understood yet.
80
88
 
81
89
  If remote: trust-building takes ~40% longer - push for a short video call before anything asynchronous.
82
90
 
@@ -84,6 +92,6 @@ If remote: trust-building takes ~40% longer - push for a short video call before
84
92
 
85
93
  - Never start technical work before `success.md` exists.
86
94
  - Sacred data never enters AI context. Ever.
87
- - The brief is a hypothesis; discover confirms it.
95
+ - The brief is a hypothesis; discover confirms it. Seed `assumptions.md` on day one.
88
96
  - The passed-over internal team is the best source of truth, not an obstacle.
89
97
  - If the customer cannot define success, that is the first problem to solve.
@@ -22,7 +22,7 @@ An FDE plan is not a sprint backlog. The technical sequence is the easy part. Th
22
22
 
23
23
  ## Method (you do this work)
24
24
 
25
- **0. Lock scope first.** Read `success.md`. If out-of-scope is undefined, define it now with the FDE - a plan on undefined scope accumulates silent commitments.
25
+ **0. Lock scope first.** Read `success.md` and `assumptions.md`. If out-of-scope is undefined, define it now with the FDE - a plan on undefined scope accumulates silent commitments. If any CRITICAL assumption is still `OPEN`, stop and run assumption-audit / discover before sequencing work.
26
26
 
27
27
  **1. Work backwards from success.** What's the last thing that must be true before done? And before that? That's the dependency chain - not a wish list.
28
28
 
@@ -36,24 +36,43 @@ An FDE plan is not a sprint backlog. The technical sequence is the easy part. Th
36
36
 
37
37
  **6. Stakeholder touchpoints every 2–3 tasks.** "Show progress to <name from stakeholders.md>." Not ceremony: a customer who sees small wins stays bought in; silence gets filled with doubt.
38
38
 
39
+ **7. End with a kill list.** Every plan names what you will **not** do this phase. If everything is "later," you have no plan - you have a wish list. Cap **Now** at 3 slices (same discipline as initiative-triage).
40
+
39
41
  **Acceptance criteria gate:** no task moves to build without written happy-path AND unhappy-path criteria. Can't write them = the task isn't understood; the open question goes to the customer **before** the task starts. Vague criteria surface later as scope creep and rework.
40
42
 
41
43
  ## Artifact
42
44
 
43
45
  The plan goes to **`decisions.md`** - always. Build reads the plan from `decisions.md`; anywhere else and the build starts blind.
44
46
 
47
+ A plan is **not done** until all four blocks exist:
48
+
45
49
  ```markdown
50
+ ## Plan - <date>
51
+ ### Now (max 3)
46
52
  Task N: <outcome, not activity>
47
53
  Delivers: <what someone can see/test>
48
54
  Accepts: <happy path> / <unhappy path>
49
55
  Touches: <files/systems - blast radius declared upfront>
50
56
  Risk: <what could go wrong + fallback>
51
57
  Verify: <specific check>
58
+ Value promised: <business unit change this slice claims>
59
+
60
+ ### Next
61
+ - ...
62
+
63
+ ### Later
64
+ - ...
65
+
66
+ ### Kill list (explicitly not this phase)
67
+ | Item | Why killed / deferred | Who accepted |
68
+ |------|----------------------|--------------|
69
+ | <rewrite / nice-to-have / political ask> | <evidence> | <name, date> |
52
70
  ```
53
71
 
72
+ No kill list → not a finished plan. Reopen with the FDE until the deferrals are written.
54
73
  ## Checkpoint
55
74
 
56
- Walk the FDE through: sequence + why this order, where the fragile work sits, where the touchpoints land, the acceptance gate on task 1. One question: "Which stakeholder sees the first visible slice, and when?"
75
+ Walk the FDE through: sequence + why this order, where the fragile work sits, where the touchpoints land, the acceptance gate on task 1, and the kill list. One question: "Which stakeholder sees the first visible slice, and when?" Second: "Who accepted what we are not doing?"
57
76
 
58
77
  ## Method - estimation (when the sponsor asks "how long, how much?")
59
78
 
@@ -118,5 +137,6 @@ Never quietly update tasks. Name the reset: update `reality.md` and `success.md`
118
137
  - Fragile zones early. Fail fast.
119
138
  - Every 2–3 tasks, a stakeholder touchpoint. Trust decays without visibility.
120
139
  - No written acceptance criteria, no build.
140
+ - No kill list, no finished plan.
121
141
  - Estimates are ranges, not promises. Name the assumptions.
122
142
  - Migrations: leaf nodes first, core last. Rollback before cutover.
@@ -2,29 +2,51 @@
2
2
 
3
3
  **Enter when:** the weekly update is due, an exec asks "where are we," or the FDE says "I need to send Dana something." This artifact decides renewals; engineers underinvest in it.
4
4
 
5
- **Read first:** `success.md` (the yardstick), `delivery.md`, `decisions.md`, `risks.md`, `context.md`. Gather the week's facts: `fde receipts` for agreements, `git log --since='7 days ago' --oneline` for shipped work.
5
+ **Read first:** `success.md` (the yardstick), `delivery.md` (value ledger), `decisions.md` (plan + kill list), `assumptions.md` (OPEN criticals), `risks.md`, `context.md`. Gather the week's facts: `fde receipts` for agreements, `git log --since='7 days ago' --oneline` for shipped work.
6
6
 
7
7
  ## Method (you do this work)
8
8
 
9
- 1. **Lead with value in their units** - time saved, errors prevented, revenue protected, risk retired. Never "completed the API endpoint"; always what the endpoint *does for the business*.
10
- 2. **Progress against `success.md`** - the agreed definition of done, not a task list. On / ahead / behind, with the why in one line.
11
- 3. **Bad news first, never buried.** A risk the sponsor learns from your update is managed; a risk they learn from their staff is a trust fire. Each risk: one line + what you're doing about it + what you need from them.
12
- 4. **The ask, explicit.** Access, a decision, an introduction, a sign-off. Updates without asks train sponsors to skim.
13
- 5. **Next week in three bullets.** What they'll see, when, and the next touchpoint.
9
+ **Always draft in SCQA.** One page maximum. No other shape.
14
10
 
15
- One page maximum. Exec voice: no jargon, no hedging, every claim traceable to the memory (`(shipped Tue, delivery.md)`). Draft in the **FDE's voice, for the FDE to send** - never send anything yourself.
11
+ | Block | What to write | Source |
12
+ |-------|---------------|--------|
13
+ | **S — Situation** | Where we are against `success.md`, in their words | success.md, delivery value ledger |
14
+ | **C — Complication** | What changed, what is at risk, or what we learned (bad news first) | risks.md, assumptions DISPROVED/OPEN, stakeholders signal |
15
+ | **Q — Question / Ask** | The one decision or help you need from them | decisions.md, access/sign-off needs |
16
+ | **A — Answer** | What you recommend / what happens next week (≤3 bullets) | plan Now lane, delivery promised→measured |
17
+
18
+ Then add, still on the same page:
19
+ 1. **Value this week** - from the value ledger: promised → measured (or "pending") with evidence citation.
20
+ 2. **Kill / defer reminder** - one line from the plan kill list so scope fights stay visible.
21
+ 3. **Hostile Q prep** - three questions a skeptical sponsor will ask, with one-line answers from memory.
22
+
23
+ Exec voice: no jargon, no hedging, every claim traceable (`(shipped Tue, delivery.md)`). Draft in the **FDE's voice, for the FDE to send** - never send anything yourself.
24
+
25
+ For board / renewal / sponsor's boss (longer pyramid): use `exec-narrative.md`. Do not invent a second weekly format.
16
26
 
17
27
  ## Artifact
18
28
 
19
- Append the draft to `delivery.md` under `## Status - <date>` (the running record the close phase and dashboard read). Note in `context.md`: status drafted, awaiting FDE review/send.
29
+ Append the draft to `delivery.md` under `## Status - <date>` using the SCQA headings. Note in `context.md`: status drafted, awaiting FDE review/send.
30
+
31
+ ```markdown
32
+ ## Status - YYYY-MM-DD
33
+ **S:** ...
34
+ **C:** ...
35
+ **Q:** ...
36
+ **A:** ...
37
+ **Value ledger:** promised … / measured … (evidence)
38
+ **Kill list reminder:** …
39
+ **Hostile Qs:** 1) … 2) … 3) …
40
+ ```
20
41
 
21
42
  ## Checkpoint
22
43
 
23
- Walk the FDE through the two highest-stakes lines - the worst risk and the biggest ask - and confirm the framing matches what the sponsor can hear right now (check `stakeholders.md` signal first: a red-signal sponsor gets a different opening than a green one).
44
+ Walk the FDE through the Complication and the Ask - confirm the framing matches what the sponsor can hear right now (check `stakeholders.md` signal first: a red-signal sponsor gets a different opening than a green one).
24
45
 
25
46
  ## Principles
26
47
 
27
- - No surprises: anything the sponsor would be angry to learn later goes in this update.
28
- - Value in their units, progress against the agreed yardstick, one page.
48
+ - SCQA every time. Situation Complication Ask Answer.
49
+ - No surprises: anything the sponsor would be angry to learn later goes in Complication.
50
+ - Value from the ledger, not from ticket theater.
29
51
  - An update without an ask is a missed move.
30
52
  - You draft; the FDE sends. Their voice, their relationship.
@@ -0,0 +1,11 @@
1
+ # Assumptions
2
+
3
+ <!-- Brief claims that are not yet evidence. Land seeds; assumption-audit / discover update. -->
4
+
5
+ | # | Assumption | Blast radius | How we test | Status | Evidence |
6
+ |---|------------|--------------|-------------|--------|----------|
7
+ | 1 | *(seed from brief - stated, unverified)* | CRITICAL / LOAD-BEARING / CONVENIENCE | | OPEN | |
8
+
9
+ **Status values:** `OPEN` · `TESTING` · `CONFIRMED` · `DISPROVED` · `PARKED`
10
+
11
+ **Rule:** a CRITICAL assumption still OPEN blocks plan. DISPROVED → update `reality.md` / `success.md` and log the reset in `decisions.md` the same day.
@@ -1,7 +1,17 @@
1
1
  # Delivery log
2
2
 
3
- <!-- Business-visible value, not ticket theater. -->
3
+ <!-- Business-visible value, not ticket theater. Every ship gets a value ledger row. -->
4
+
5
+ ## Value ledger
6
+
7
+ | Date | Slice | Promised | Measured | Evidence | Rollback |
8
+ |------|-------|----------|----------|----------|----------|
9
+ | | | *(what we said it would change)* | *(what actually changed, or pending)* | *(who/when/metric)* | |
4
10
 
5
11
  ## Shipped
6
12
 
13
+ ## Status updates
14
+
15
+ <!-- Append ## Status - YYYY-MM-DD drafts from @fde status (SCQA). -->
16
+
7
17
  ## Running value