fdeops 3.7.3 → 3.7.6
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 +7 -5
- package/bin/check.js +8 -4
- package/bin/fde.js +115 -31
- package/hooks/pre-compact +14 -4
- package/hooks/session-start +14 -4
- package/hooks/session-stop +14 -4
- package/package.json +1 -1
- package/skills/fde/SKILL.md +3 -3
- package/skills/fde/references/assumption-audit.md +13 -9
- package/skills/fde/references/build.md +1 -1
- package/skills/fde/references/discover.md +2 -0
- package/skills/fde/references/initiative-triage.md +18 -1
- package/skills/fde/references/land.md +11 -3
- package/skills/fde/references/plan.md +22 -2
- package/skills/fde/references/status.md +33 -11
- package/templates/.fde/assumptions.md +11 -0
- package/templates/.fde/delivery.md +11 -1
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
|
|
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
|
|
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 #
|
|
152
|
-
fde dashboard #
|
|
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)
|
|
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
|
|
224
|
-
//
|
|
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 (
|
|
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
|
|
20
|
-
* fde dashboard
|
|
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
|
-
|
|
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
|
|
@@ -93,9 +97,17 @@ function resolveEngagement() {
|
|
|
93
97
|
// 1) explicit env (back-compat: accept old FDEOS_ENGAGEMENT too)
|
|
94
98
|
const env = (process.env.FDEOPS_ENGAGEMENT || process.env.FDEOS_ENGAGEMENT || '').replace(/^~/, HOME).trim()
|
|
95
99
|
if (env && fs.existsSync(env)) return env
|
|
96
|
-
// 2) workspace registry binding (written by resume --init)
|
|
100
|
+
// 2) workspace registry binding (written by resume --init). Match the cwd OR
|
|
101
|
+
// any ancestor of it - FDEs run commands from src/, packages/api/, etc., not
|
|
102
|
+
// just the repo root where they bound. Nearest (deepest) registered ancestor
|
|
103
|
+
// wins, exactly like git searching upward for .git. `startsWith(workspace +
|
|
104
|
+
// sep)` requires a true path-boundary ancestor, so /work/repo-2 never matches
|
|
105
|
+
// a binding on /work/repo. Kept in lockstep with registry_engagement_dir in
|
|
106
|
+
// hooks/session-start, session-stop, pre-compact.
|
|
97
107
|
const cwd = process.cwd()
|
|
98
|
-
const reg = readRegistry()
|
|
108
|
+
const reg = readRegistry()
|
|
109
|
+
.filter(r => cwd === r.workspace || cwd.startsWith(r.workspace + path.sep))
|
|
110
|
+
.sort((a, b) => b.workspace.length - a.workspace.length)[0]
|
|
99
111
|
if (reg) {
|
|
100
112
|
const p = path.join(ENGAGEMENTS_ROOT, reg.slug, '.fde')
|
|
101
113
|
if (fs.existsSync(p)) return p
|
|
@@ -626,10 +638,26 @@ function cmdDebrief(args) {
|
|
|
626
638
|
if (!eng) { console.error('no engagement - run: fde resume --init <name>'); process.exit(2) }
|
|
627
639
|
let input = ''
|
|
628
640
|
if (args[0]) {
|
|
629
|
-
|
|
630
|
-
|
|
641
|
+
const notesPath = args[0].replace(/^~/, HOME)
|
|
642
|
+
let st
|
|
643
|
+
try { st = fs.statSync(notesPath) } catch (_) { console.error(`cannot read ${args[0]}`); process.exit(1) }
|
|
644
|
+
if (st.size > DEBRIEF_MAX_BYTES) {
|
|
645
|
+
console.error(`debrief refused: ${args[0]} is ${st.size} bytes (max ${DEBRIEF_MAX_BYTES}). Split the notes or paste the relevant section.`)
|
|
646
|
+
process.exit(1)
|
|
647
|
+
}
|
|
648
|
+
let buf
|
|
649
|
+
try { buf = fs.readFileSync(notesPath) } catch (_) { console.error(`cannot read ${args[0]}`); process.exit(1) }
|
|
650
|
+
if (buf.includes(0)) {
|
|
651
|
+
console.error(`debrief refused: ${args[0]} looks binary (null bytes). Paste text notes only.`)
|
|
652
|
+
process.exit(1)
|
|
653
|
+
}
|
|
654
|
+
input = buf.toString('utf8')
|
|
631
655
|
} else {
|
|
632
656
|
try { input = fs.readFileSync(0, 'utf8') } catch (_) {} // stdin until EOF
|
|
657
|
+
if (Buffer.byteLength(input, 'utf8') > DEBRIEF_MAX_BYTES) {
|
|
658
|
+
console.error(`debrief refused: stdin is over ${DEBRIEF_MAX_BYTES} bytes. Split the notes.`)
|
|
659
|
+
process.exit(1)
|
|
660
|
+
}
|
|
633
661
|
}
|
|
634
662
|
const d = new Date()
|
|
635
663
|
const date = d.toISOString().slice(0, 10)
|
|
@@ -667,18 +695,42 @@ function cmdReceipts(args) {
|
|
|
667
695
|
const eng = resolveEngagement()
|
|
668
696
|
if (!eng) { console.error('no engagement - run: fde resume --init <name>'); process.exit(2) }
|
|
669
697
|
const rx = new RegExp(term.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'i')
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
698
|
+
// "receipts" answers "what did we AGREE?" - so dated, agreed records are the
|
|
699
|
+
// receipt. brief.md is the client's hypothesis and reality.md/context.md are
|
|
700
|
+
// working notes; a hit there is a CLAIM, not an agreement. Keeping them in the
|
|
701
|
+
// same list let an FDE cite a sales promise as a receipt - so they get a
|
|
702
|
+
// separate, clearly-labelled section that is never mistaken for the record.
|
|
703
|
+
const AGREEMENTS = ['decisions.md', 'delivery.md', 'success.md', 'risks.md', 'stakeholders.md']
|
|
704
|
+
const CLAIMS = ['brief.md', 'assumptions.md', 'reality.md', 'context.md']
|
|
705
|
+
const collect = files => {
|
|
706
|
+
const hits = []
|
|
707
|
+
for (const f of files) {
|
|
708
|
+
if (!fs.existsSync(path.join(eng, f))) continue
|
|
709
|
+
// readClean, not raw read: receipts must not grep sealed <private> notes
|
|
710
|
+
// back out. Redaction can shift line numbers past a multi-line block; the
|
|
711
|
+
// file:line is advisory - not leaking a sealed secret is worth that.
|
|
712
|
+
readClean(eng, f).split('\n').forEach((l, i) => {
|
|
713
|
+
if (rx.test(l)) hits.push(` ${f}:${i + 1} ${l.trim().slice(0, 160)}`)
|
|
714
|
+
})
|
|
715
|
+
}
|
|
716
|
+
return hits
|
|
717
|
+
}
|
|
718
|
+
const agreed = collect(AGREEMENTS)
|
|
719
|
+
const claimed = collect(CLAIMS)
|
|
720
|
+
if (agreed.length) {
|
|
721
|
+
console.log('AGREED (dated record - defensible):')
|
|
722
|
+
agreed.forEach(h => console.log(h))
|
|
723
|
+
}
|
|
724
|
+
if (claimed.length) {
|
|
725
|
+
if (agreed.length) console.log('')
|
|
726
|
+
console.log('CLAIMS & working notes (stated, NOT an agreement - verify before citing):')
|
|
727
|
+
claimed.forEach(h => console.log(h))
|
|
728
|
+
}
|
|
729
|
+
if (!agreed.length && !claimed.length) {
|
|
730
|
+
console.log(`no record of "${term}" - nothing was ever logged about it. A gap in the record, not proof of absence: if it WAS agreed, log it now, dated today.`)
|
|
731
|
+
} else if (!agreed.length) {
|
|
732
|
+
console.log('\n(no dated agreement matched - only unverified claims above. If this was agreed, log it: fde log decision "...")')
|
|
680
733
|
}
|
|
681
|
-
if (!found) console.log(`no record of "${term}" - nothing was ever logged about it. A gap in the record, not proof of absence: if it WAS agreed, log it now, dated today.`)
|
|
682
734
|
}
|
|
683
735
|
|
|
684
736
|
function cmdCapture() {
|
|
@@ -703,26 +755,42 @@ function cmdCapture() {
|
|
|
703
755
|
try { fs.appendFileSync(path.join(eng, 'context.md'), block) } catch (_) {}
|
|
704
756
|
}
|
|
705
757
|
|
|
706
|
-
function
|
|
758
|
+
function engagementSlugFromPath(eng) {
|
|
759
|
+
return path.basename(path.dirname(eng))
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
function cmdStatus(args) {
|
|
763
|
+
const all = args.includes('--all')
|
|
707
764
|
if (!fs.existsSync(ENGAGEMENTS_ROOT)) { console.log('no engagements yet - fde resume --init <name>'); return }
|
|
708
765
|
const rows = []
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
766
|
+
if (all) {
|
|
767
|
+
for (const d of fs.readdirSync(ENGAGEMENTS_ROOT)) {
|
|
768
|
+
if (d.startsWith('.')) continue
|
|
769
|
+
const eng = path.join(ENGAGEMENTS_ROOT, d, '.fde')
|
|
770
|
+
if (!fs.existsSync(eng)) continue
|
|
771
|
+
const s = computeSignals(eng)
|
|
772
|
+
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) })
|
|
773
|
+
}
|
|
774
|
+
} else {
|
|
775
|
+
const eng = resolveEngagement()
|
|
776
|
+
if (!eng) {
|
|
777
|
+
console.error('no engagement bound to this workspace.\nrun: fde resume --init <name> or fde status --all')
|
|
778
|
+
process.exit(2)
|
|
779
|
+
}
|
|
713
780
|
const s = computeSignals(eng)
|
|
714
|
-
rows.push({ name:
|
|
781
|
+
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) })
|
|
715
782
|
}
|
|
716
783
|
if (!rows.length) { console.log('no engagements yet'); return }
|
|
717
784
|
const order = { RED: 0, amber: 1, green: 2 }
|
|
718
785
|
rows.sort((a, b) => order[a.trust] - order[b.trust])
|
|
719
|
-
console.log('FDE PORTFOLIO - trust-first triage (heuristic: red > amber > green)\n')
|
|
786
|
+
console.log((all ? 'FDE PORTFOLIO' : 'FDE STATUS') + ' - trust-first triage (heuristic: red > amber > green)\n')
|
|
720
787
|
for (const r of rows) {
|
|
721
788
|
// "amber?" = structured signal went stale (>21d) - reconfirm before trusting it
|
|
722
789
|
const label = r.trust + (r.stale ? '?' : '')
|
|
723
790
|
const sig = r.signalAge != null ? `signal ${r.signalAge}d old${r.stale ? ' (STALE - reconfirm)' : ''} ` : ''
|
|
724
791
|
console.log(` [${label.padEnd(6)}] ${r.name.padEnd(24)} phase:${r.phase.padEnd(10)} updated:${r.updated.padEnd(8)} ${sig}${r.topRisk}`)
|
|
725
792
|
}
|
|
793
|
+
if (!all) console.log('\n(current engagement only - pass --all for the full portfolio)')
|
|
726
794
|
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.')
|
|
727
795
|
}
|
|
728
796
|
|
|
@@ -807,8 +875,12 @@ function hasRealContent(md) {
|
|
|
807
875
|
return txt.length > 0
|
|
808
876
|
}
|
|
809
877
|
|
|
810
|
-
function gatherEngagements() {
|
|
878
|
+
function gatherEngagements(opts = {}) {
|
|
811
879
|
const list = []
|
|
880
|
+
if (opts.only) {
|
|
881
|
+
list.push({ name: engagementSlugFromPath(opts.only), dir: opts.only, signals: computeSignals(opts.only) })
|
|
882
|
+
return list
|
|
883
|
+
}
|
|
812
884
|
if (!fs.existsSync(ENGAGEMENTS_ROOT)) return list
|
|
813
885
|
for (const d of fs.readdirSync(ENGAGEMENTS_ROOT).sort()) {
|
|
814
886
|
if (d.startsWith('.')) continue
|
|
@@ -1332,11 +1404,22 @@ function paletteItemsHtml(ordered) {
|
|
|
1332
1404
|
}
|
|
1333
1405
|
|
|
1334
1406
|
function cmdDashboard(args) {
|
|
1407
|
+
const all = args.includes('--all')
|
|
1335
1408
|
const outIdx = args.indexOf('--out')
|
|
1336
1409
|
const outPath = outIdx !== -1 && args[outIdx + 1]
|
|
1337
1410
|
? path.resolve(args[outIdx + 1].replace(/^~/, HOME))
|
|
1338
|
-
: path.join(ENGAGEMENTS_ROOT, 'fieldbook.html')
|
|
1339
|
-
|
|
1411
|
+
: path.join(ENGAGEMENTS_ROOT, all ? 'fieldbook.html' : 'fieldbook-current.html')
|
|
1412
|
+
let engagements
|
|
1413
|
+
if (all) {
|
|
1414
|
+
engagements = gatherEngagements()
|
|
1415
|
+
} else {
|
|
1416
|
+
const eng = resolveEngagement()
|
|
1417
|
+
if (!eng) {
|
|
1418
|
+
console.error('no engagement bound to this workspace.\nrun: fde resume --init <name> or fde dashboard --all')
|
|
1419
|
+
process.exit(2)
|
|
1420
|
+
}
|
|
1421
|
+
engagements = gatherEngagements({ only: eng })
|
|
1422
|
+
}
|
|
1340
1423
|
const counts = { green: 0, amber: 0, RED: 0 }
|
|
1341
1424
|
engagements.forEach(e => { counts[e.signals.trust]++ })
|
|
1342
1425
|
const today = formatToday(new Date())
|
|
@@ -1474,7 +1557,7 @@ switch (cmd) {
|
|
|
1474
1557
|
case 'debrief': cmdDebrief(args); break
|
|
1475
1558
|
case 'receipts': cmdReceipts(args); break
|
|
1476
1559
|
case 'capture': cmdCapture(); break
|
|
1477
|
-
case 'status': cmdStatus(); break
|
|
1560
|
+
case 'status': cmdStatus(args); break
|
|
1478
1561
|
case 'dashboard': cmdDashboard(args); break
|
|
1479
1562
|
default:
|
|
1480
1563
|
console.log(`fde - deterministic core of fdeops
|
|
@@ -1487,6 +1570,7 @@ switch (cmd) {
|
|
|
1487
1570
|
fde debrief [file] meeting notes → memory: decision:/risk:/delivery:/contact: lines route, rest → context.md (stdin if no file; --dry-run previews)
|
|
1488
1571
|
fde receipts <term> "what did we agree?" with dates
|
|
1489
1572
|
fde capture session-end memory snapshot (hooks use this)
|
|
1490
|
-
fde status
|
|
1491
|
-
fde dashboard
|
|
1573
|
+
fde status [--all] current engagement status (pass --all for full portfolio)
|
|
1574
|
+
fde dashboard [--all] current engagement fieldbook (pass --all for every client)
|
|
1575
|
+
env FDEOPS_ENGAGEMENTS_ROOT override ~/fde-engagements (init/status/dashboard/registry)`)
|
|
1492
1576
|
}
|
package/hooks/pre-compact
CHANGED
|
@@ -16,13 +16,23 @@ 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
|
-
|
|
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
|
-
slug=$(awk 'BEGIN{ws=ENVIRON["PWD"]}
|
|
25
|
+
slug=$(awk 'BEGIN{ws=ENVIRON["PWD"]; best=-1}
|
|
22
26
|
{ i = match($0, / [^ ]*$/)
|
|
23
|
-
if (i > 0
|
|
27
|
+
if (i > 0) {
|
|
28
|
+
wpath = substr($0, 1, i - 1)
|
|
29
|
+
if (ws == wpath || index(ws, wpath "/") == 1) {
|
|
30
|
+
if (length(wpath) > best) { best = length(wpath); slug = substr($0, i + 1) }
|
|
31
|
+
}
|
|
32
|
+
} }
|
|
33
|
+
END { if (best >= 0) print slug }' "$reg" 2>/dev/null)
|
|
24
34
|
[ -z "$slug" ] && return 1
|
|
25
|
-
[ -d "$
|
|
35
|
+
[ -d "$root/$slug/.fde" ] && printf '%s\n' "$root/$slug/.fde" && return 0
|
|
26
36
|
return 1
|
|
27
37
|
}
|
|
28
38
|
|
package/hooks/session-start
CHANGED
|
@@ -28,13 +28,23 @@ 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
|
-
|
|
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
|
-
slug=$(awk 'BEGIN{ws=ENVIRON["PWD"]}
|
|
37
|
+
slug=$(awk 'BEGIN{ws=ENVIRON["PWD"]; best=-1}
|
|
34
38
|
{ i = match($0, / [^ ]*$/)
|
|
35
|
-
if (i > 0
|
|
39
|
+
if (i > 0) {
|
|
40
|
+
wpath = substr($0, 1, i - 1)
|
|
41
|
+
if (ws == wpath || index(ws, wpath "/") == 1) {
|
|
42
|
+
if (length(wpath) > best) { best = length(wpath); slug = substr($0, i + 1) }
|
|
43
|
+
}
|
|
44
|
+
} }
|
|
45
|
+
END { if (best >= 0) print slug }' "$reg" 2>/dev/null)
|
|
36
46
|
[ -z "$slug" ] && return 1
|
|
37
|
-
[ -d "$
|
|
47
|
+
[ -d "$root/$slug/.fde" ] && printf '%s\n' "$root/$slug/.fde" && return 0
|
|
38
48
|
return 1
|
|
39
49
|
}
|
|
40
50
|
|
package/hooks/session-stop
CHANGED
|
@@ -24,13 +24,23 @@ 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
|
-
|
|
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
|
-
slug=$(awk 'BEGIN{ws=ENVIRON["PWD"]}
|
|
33
|
+
slug=$(awk 'BEGIN{ws=ENVIRON["PWD"]; best=-1}
|
|
30
34
|
{ i = match($0, / [^ ]*$/)
|
|
31
|
-
if (i > 0
|
|
35
|
+
if (i > 0) {
|
|
36
|
+
wpath = substr($0, 1, i - 1)
|
|
37
|
+
if (ws == wpath || index(ws, wpath "/") == 1) {
|
|
38
|
+
if (length(wpath) > best) { best = length(wpath); slug = substr($0, i + 1) }
|
|
39
|
+
}
|
|
40
|
+
} }
|
|
41
|
+
END { if (best >= 0) print slug }' "$reg" 2>/dev/null)
|
|
32
42
|
[ -z "$slug" ] && return 1
|
|
33
|
-
[ -d "$
|
|
43
|
+
[ -d "$root/$slug/.fde" ] && printf '%s\n' "$root/$slug/.fde" && return 0
|
|
34
44
|
return 1
|
|
35
45
|
}
|
|
36
46
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fdeops",
|
|
3
|
-
"version": "3.7.
|
|
3
|
+
"version": "3.7.6",
|
|
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,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: fde
|
|
3
|
-
description:
|
|
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
|
|
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
|
|
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
|
-
**`
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
|
61
|
-
|
|
|
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
|
-
|
|
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`** -
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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>`
|
|
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
|
|
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
|
-
-
|
|
28
|
-
-
|
|
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
|