fdeops 3.9.12 → 3.9.14

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/bin/check.js CHANGED
@@ -63,6 +63,7 @@ const requiredReferences = [
63
63
  'debug.md', 'rescue.md', 'ship.md', 'sketch.md', 'close.md', 'dashboard.md',
64
64
  'debrief.md', 'status.md', 'demo-prep.md',
65
65
  'healthcare.md', 'fintech.md', 'gov.md',
66
+ 'ai.md', 'eval-pack.md',
66
67
  ]
67
68
  for (const f of requiredReferences) {
68
69
  const p = path.join(root, 'skills', 'fde', 'references', f)
package/bin/fde.js CHANGED
@@ -401,6 +401,7 @@ const {
401
401
  memoryDirtyManual,
402
402
  commitMemory,
403
403
  memoryHead,
404
+ memoryGitHealthy,
404
405
  } = createMemoryApi({ fs, path, gitBinOk, writeOwnerIfMissing, atomicWriteFile })
405
406
 
406
407
  // Pull the body under a "## Heading" up to the next "##" (or EOF).
@@ -843,8 +844,11 @@ function cmdResume(args) {
843
844
  const fdeDir = path.join(engRoot, '.fde')
844
845
  const existed = fs.existsSync(fdeDir)
845
846
 
847
+ // Optional stubs (AI eval pack, …) stay in templates/ for copy-on-use — not day-1 scaffold.
848
+ const SKIP_INIT_TEMPLATES = new Set(['evals.md'])
846
849
  const fillTemplates = (destFde) => {
847
850
  for (const f of fs.readdirSync(tpl)) {
851
+ if (SKIP_INIT_TEMPLATES.has(f)) continue
848
852
  const src = path.join(tpl, f); const dst = path.join(destFde, f)
849
853
  if (fs.statSync(src).isDirectory()) fs.mkdirSync(dst, { recursive: true })
850
854
  else if (!fs.existsSync(dst)) fs.copyFileSync(src, dst)
@@ -1092,6 +1096,10 @@ function smartProposeText(input) {
1092
1096
  let bare = line
1093
1097
  .replace(/^[-*+]\s+/, '')
1094
1098
  .replace(/^\*\*(decision|risk|delivery|contact|next):?\*\*:?\s*/i, '$1: ')
1099
+ if (/^decided:\s+/i.test(bare)) {
1100
+ out.push(`decision: ${bare.replace(/^decided:\s+/i, '')}`)
1101
+ continue
1102
+ }
1095
1103
  if (/^(decision|risk|delivery|contact|next):\s*/i.test(bare)) {
1096
1104
  let routed = bare.replace(/^(decision|risk|delivery|contact|next):\s*/i, (m, t) => `${t.toLowerCase()}: `)
1097
1105
  if (/^contact:/i.test(routed) && !/\[signal:(red|amber|green)\]/i.test(routed)) {
@@ -1107,7 +1115,7 @@ function smartProposeText(input) {
1107
1115
  out.push(`next: ${next}`)
1108
1116
  continue
1109
1117
  }
1110
- if (/\b(we (decided|agreed)|decision:|descope|agreed to|agreement was|freeze scope)\b/i.test(bare)) {
1118
+ if (/\b(we (decided|agreed)|decided:|decision:|descope|agreed to|agreement was|freeze scope|freeze prompts)\b/i.test(bare)) {
1111
1119
  out.push(`decision: ${bare}`)
1112
1120
  } else if (/\b(open question|who signs|unclear who|unresolved)\b/i.test(bare)) {
1113
1121
  out.push(`risk: ${bare}`)
@@ -1167,6 +1175,12 @@ function readDebriefInput(args) {
1167
1175
  return input
1168
1176
  }
1169
1177
 
1178
+ function previewLine(text, max = 240) {
1179
+ const t = String(text || '').replace(/\s+/g, ' ').trim()
1180
+ if (t.length <= max) return t
1181
+ return `${t.slice(0, max)}… (${t.length} chars)`
1182
+ }
1183
+
1170
1184
  function routeDebriefInput(eng, input, { dry, force }) {
1171
1185
  const d = new Date()
1172
1186
  const date = d.toISOString().slice(0, 10)
@@ -1188,7 +1202,7 @@ function routeDebriefInput(eng, input, { dry, force }) {
1188
1202
  continue
1189
1203
  }
1190
1204
  if (type === 'next') {
1191
- if (dry) console.log(`→ context.md ## Next action - ${body}`)
1205
+ if (dry) console.log(`→ context.md ## Next action - ${previewLine(body)}`)
1192
1206
  else nextAction = body
1193
1207
  counts.next++
1194
1208
  continue
@@ -1196,7 +1210,7 @@ function routeDebriefInput(eng, input, { dry, force }) {
1196
1210
  const sigInline = (body.match(/\[signal:(red|amber|green)\]/i) || [])[1]
1197
1211
  if (sigInline) body = body.replace(/\[signal:(red|amber|green)\]/i, '').trim()
1198
1212
  const entry = datedEntry(eng, date, body, type === 'contact' && sigInline ? sigInline.toLowerCase() : '')
1199
- if (dry) console.log(`→ ${LOG_FILES[type]} ${entry}`)
1213
+ if (dry) console.log(`→ ${LOG_FILES[type]} ${previewLine(entry)}`)
1200
1214
  else appendLogEntry(eng, type, entry, { skipCommit: true })
1201
1215
  counts[type]++
1202
1216
  } else {
@@ -1210,7 +1224,7 @@ function routeDebriefInput(eng, input, { dry, force }) {
1210
1224
  if (nextAction && !dry) setNextAction(eng, nextAction)
1211
1225
  if (ctxLines.length) {
1212
1226
  const stamp = `${date} ${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`
1213
- if (dry) ctxLines.forEach(l => console.log(`→ context.md - ${l}`))
1227
+ if (dry) ctxLines.forEach(l => console.log(`→ context.md - ${previewLine(l)}`))
1214
1228
  else lockedAppendFile(path.join(eng, 'context.md'), `\n## Debrief - ${stamp}\n${ctxLines.map(l => `- ${l}`).join('\n')}\n`)
1215
1229
  }
1216
1230
  return { counts, ctxLines, date, nextAction }
@@ -1305,9 +1319,22 @@ function cmdReceipts(args) {
1305
1319
  }
1306
1320
  const agreed = collect(AGREEMENTS)
1307
1321
  const claimed = collect(CLAIMS)
1322
+ const dirty = memoryDirtyManual(eng)
1323
+ const dirtySet = new Set(dirty)
1324
+ const dirtyAgreedHits = [...new Set(
1325
+ agreed.map(h => (h.match(/^\s*([^:]+):/) || [])[1]).filter(f => f && dirtySet.has(f))
1326
+ )]
1308
1327
  if (agreed.length) {
1309
1328
  console.log('ON RECORD (dated - defensible):')
1310
- agreed.forEach(h => console.log(h))
1329
+ agreed.forEach(h => {
1330
+ const file = (h.match(/^\s*([^:]+):/) || [])[1]
1331
+ console.log(h + (file && dirtySet.has(file) ? ' ⚠ dirty file' : ''))
1332
+ })
1333
+ if (dirtyAgreedHits.length) {
1334
+ console.log(
1335
+ `⚠ memory dirty (uncommitted manual edits: ${dirtyAgreedHits.join(', ')}) - dated lines above may not match the tamper-evident ledger until reviewed`
1336
+ )
1337
+ }
1311
1338
  }
1312
1339
  if (claimed.length) {
1313
1340
  if (agreed.length) console.log('')
@@ -1490,7 +1517,18 @@ function collectDoctorIssues(eng) {
1490
1517
  }
1491
1518
  if (s.stale) issues.push(`trust signal is STALE (${s.signalAge}d) - reconfirm with fde log contact ... --signal`)
1492
1519
  if (!readOwner(eng)) issues.push('no .owner - run any write or: fde owner set you@firm.com')
1493
- if (!fs.existsSync(path.join(eng, '.git'))) issues.push('memory not git-versioned - next write will init, or re-run resume --init')
1520
+ const gitHealth = memoryGitHealthy(eng)
1521
+ if (!gitHealth.ok) {
1522
+ if (gitHealth.reason === 'broken') {
1523
+ issues.push(
1524
+ 'memory git is BROKEN (UNVERSIONED) - receipts are not tamper-evident; repair: mv .fde/.git .fde/.git.broken && re-run any fde write (or resume --init) to re-init the ledger'
1525
+ )
1526
+ } else if (gitHealth.reason === 'no-git-bin') {
1527
+ issues.push('git binary missing - engagement memory cannot be versioned (receipts stay dated, not tamper-evident)')
1528
+ } else {
1529
+ issues.push('memory not git-versioned - next write will init, or re-run resume --init')
1530
+ }
1531
+ }
1494
1532
  const success = readClean(eng, 'success.md')
1495
1533
  if (!firstLine(success, 80)) issues.push('success.md has no stated done-definition - fill before plan/build')
1496
1534
  if (!sectionBody(readClean(eng, 'context.md'), 'Next action')) {
@@ -1501,6 +1539,18 @@ function collectDoctorIssues(eng) {
1501
1539
  `phase is ${s.phase} with ${s.openRisks} open risk(s) - retire, hand off, or move still-live ones before calling the embed done`
1502
1540
  )
1503
1541
  }
1542
+ if (s.phase === 'close' || s.phase === 'ship') {
1543
+ if (!hasValueBucket(eng)) {
1544
+ issues.push(
1545
+ `phase is ${s.phase} with no value bucket (cost-save | risk-mitigation | revenue-uplift) in success.md or delivery value ledger`
1546
+ )
1547
+ }
1548
+ if (engagementTouchesAI(eng) && !hasEvalReceipt(eng)) {
1549
+ issues.push(
1550
+ `phase is ${s.phase} with AI in scope but no eval receipt (evals.md Verdict or delivery Eval / Ship receipts) — required before green ship/close`
1551
+ )
1552
+ }
1553
+ }
1504
1554
  const dupes = findDuplicateOpenRisks(eng)
1505
1555
  if (dupes.length) {
1506
1556
  const sample = (dupes[0][0] || '').replace(/\s+/g, ' ').trim().slice(0, 60)
@@ -1511,6 +1561,71 @@ function collectDoctorIssues(eng) {
1511
1561
  return issues
1512
1562
  }
1513
1563
 
1564
+ // Strip template comments / italic *(hints)* so doctor does not treat stubs as filled.
1565
+ function stripTemplateNoise(md) {
1566
+ return String(md || '')
1567
+ .replace(/<!--[\s\S]*?-->/g, '')
1568
+ .replace(/\*\([^)]*\)\*/g, '')
1569
+ }
1570
+
1571
+ const VALUE_BUCKET_RE = /(cost[- ]?save|risk[- ]?mitigat|revenue[- ]?uplift)/i
1572
+
1573
+ function hasValueBucket(eng) {
1574
+ const success = stripTemplateNoise(readClean(eng, 'success.md'))
1575
+ const bucketLine = success.match(/\*\*Primary value bucket:\*\*\s*(.+)/i)
1576
+ if (bucketLine && VALUE_BUCKET_RE.test(bucketLine[1].trim())) return true
1577
+ if (!/\*\*Primary value bucket:\*\*/i.test(success) && VALUE_BUCKET_RE.test(success)) return true
1578
+
1579
+ const ledger = stripTemplateNoise(sectionBody(readClean(eng, 'delivery.md'), 'Value ledger') || '')
1580
+ const table = parseMdTable(ledger)
1581
+ if (table) {
1582
+ const bIdx = colIndex(table.headers, /bucket/i)
1583
+ if (bIdx !== -1) {
1584
+ for (const row of table.rows) {
1585
+ const cell = String(row[bIdx] || '').trim()
1586
+ if (cell && VALUE_BUCKET_RE.test(cell)) return true
1587
+ }
1588
+ } else if (VALUE_BUCKET_RE.test(ledger)) {
1589
+ return true
1590
+ }
1591
+ } else if (VALUE_BUCKET_RE.test(ledger)) {
1592
+ return true
1593
+ }
1594
+ return false
1595
+ }
1596
+
1597
+ // AI in scope for ship/close hygiene — delivery/decisions/trust evidence only.
1598
+ // Do not scan terrain.md: its template headers mention LLM and would false-positive every ship.
1599
+ function engagementTouchesAI(eng) {
1600
+ const trust = readClean(eng, 'trust-profile.md')
1601
+ const aiSec = stripTemplateNoise(sectionBody(trust, 'AI policy') || '')
1602
+ if (aiSec.trim().length > 20) return true
1603
+ const blob = stripTemplateNoise([
1604
+ readClean(eng, 'delivery.md'),
1605
+ readClean(eng, 'decisions.md'),
1606
+ ].join('\n'))
1607
+ return /\b(llm|rag|embedding|inference|model card|agentic|openai|anthropic|vector database|vector db)\b/i.test(blob)
1608
+ }
1609
+
1610
+ function hasEvalReceipt(eng) {
1611
+ const evalsPath = path.join(eng, 'evals.md')
1612
+ if (fs.existsSync(evalsPath)) {
1613
+ const e = stripTemplateNoise(readClean(eng, 'evals.md'))
1614
+ // Empty G1 stub + "Pass / fail" heading is not a receipt — need a real verdict/run/result.
1615
+ if (/\*\*Verdict:\*\*\s*SHIP\b/i.test(e) || /(?:^|\n)\s*-\s*\*\*Verdict:\*\*\s*SHIP\b/i.test(e)) return true
1616
+ if (/\bLast run:\s*\d{4}-\d{2}-\d{2}/i.test(e)) return true
1617
+ if (/\|\s*G\d+\s*\|[^|\n]+\|[^|\n]+\|[^|\n]+\|[^|\n]+\|\s*pass\s*\|/i.test(e)) return true
1618
+ }
1619
+ const del = stripTemplateNoise(readClean(eng, 'delivery.md'))
1620
+ if (/#{1,6}\s+Eval\b/i.test(del) && /\b(pass|SHIP|\d+\/\d+)\b/i.test(sectionBody(del, 'Eval') || del)) return true
1621
+ if (/\beval (pack|receipt)[:\s].*\b(pass|SHIP)\b/i.test(del)) return true
1622
+ const receipts = sectionBody(del, 'Ship receipts') || ''
1623
+ if (/\bevals\.md\b/i.test(receipts) && /\b(pass|SHIP)\b/i.test(receipts) && !/\*\([^)]*evals\.md[^)]*\)\*/i.test(receipts)) {
1624
+ return true
1625
+ }
1626
+ return false
1627
+ }
1628
+
1514
1629
  // Lean line for session-start TRIAGE - count + top issue + NL cue. Omitted when clean.
1515
1630
  function hygieneTriageLines(eng) {
1516
1631
  const issues = collectDoctorIssues(eng)
@@ -1651,10 +1766,24 @@ function cmdGarden(args) {
1651
1766
  const apply = args.includes('--apply')
1652
1767
  const eng = resolveEngagement({ forWrite: apply })
1653
1768
  if (!eng) { console.error('no engagement - run: fde resume --init <name>'); process.exit(2) }
1769
+ const gitHealth = memoryGitHealthy(eng)
1654
1770
  // Gardener contract (from Rowboat note_curation): no new facts, no deleted substance,
1655
- // reversible via git, confirm before apply. Mechanical only - no LLM rewrite.
1656
- console.log('GARDEN (contract: no new facts · no deleted substance · reversible via memory git)')
1771
+ // reversible via git when healthy, confirm before apply. Mechanical only - no LLM rewrite.
1772
+ if (gitHealth.ok) {
1773
+ console.log('GARDEN (contract: no new facts · no deleted substance · reversible via memory git)')
1774
+ } else if (gitHealth.reason === 'broken') {
1775
+ console.log('GARDEN (contract: no new facts · no deleted substance · ⚠ memory git BROKEN — NOT reversible until ledger is repaired)')
1776
+ } else {
1777
+ console.log('GARDEN (contract: no new facts · no deleted substance · ⚠ memory not git-versioned — NOT reversible)')
1778
+ }
1657
1779
  console.log(resumeTriage(eng))
1780
+ if (!gitHealth.ok) {
1781
+ console.log(
1782
+ gitHealth.reason === 'broken'
1783
+ ? '\n⚠ ledger is UNVERSIONED (corrupt .git). Repair before trusting garden apply: mv .fde/.git .fde/.git.broken && run any fde write to re-init.'
1784
+ : '\n⚠ no memory git — garden apply cannot create a reversible commit until the ledger exists.'
1785
+ )
1786
+ }
1658
1787
  const proposals = []
1659
1788
  const s = computeSignals(eng)
1660
1789
  if (s.stale) {
@@ -1664,6 +1793,16 @@ function cmdGarden(args) {
1664
1793
  text: `Reconfirm stale ${s.trust} signal (${s.signalAge}d): fde log contact "…" --signal`,
1665
1794
  })
1666
1795
  }
1796
+ const dupes = findDuplicateOpenRisks(eng)
1797
+ if (dupes.length) {
1798
+ const sample = (dupes[0][0] || '').replace(/\s+/g, ' ').trim().slice(0, 50)
1799
+ proposals.push({
1800
+ id: 'dedupe-risks',
1801
+ kind: 'apply',
1802
+ text: `Consolidate ${dupes.length} duplicate open-risk cluster(s) (e.g. "${sample}${sample.length >= 50 ? '…' : ''}") — keep first, retire echoes`,
1803
+ clusters: dupes,
1804
+ })
1805
+ }
1667
1806
  const ctx = readEng(eng, 'context.md')
1668
1807
  const sessionBlocks = []
1669
1808
  const lines = ctx.split('\n')
@@ -1689,12 +1828,26 @@ function cmdGarden(args) {
1689
1828
  proposals.forEach((p, i) => console.log(` ${i + 1}. [${p.kind}] ${p.text}`))
1690
1829
  if (!apply) {
1691
1830
  console.log('\nApply mechanical items only: fde garden --apply')
1692
- console.log('Manual items stay yours. Every apply commits to memory git.')
1831
+ console.log('Manual items stay yours. Every apply commits to memory git when the ledger is healthy.')
1693
1832
  return
1694
1833
  }
1834
+ if (!gitHealth.ok && gitHealth.reason === 'broken') {
1835
+ console.error('refusing garden --apply while memory git is broken - repair the ledger first')
1836
+ process.exit(1)
1837
+ }
1695
1838
  ensureMemoryGit(eng)
1696
1839
  let applied = 0
1840
+ const touched = new Set()
1697
1841
  for (const p of proposals) {
1842
+ if (p.id === 'dedupe-risks') {
1843
+ const n = applyRiskDedupe(eng, p.clusters)
1844
+ if (n > 0) {
1845
+ applied++
1846
+ touched.add('risks.md')
1847
+ console.log(`applied: retired ${n} duplicate open-risk echo(s) → ## Retired`)
1848
+ }
1849
+ continue
1850
+ }
1698
1851
  if (p.id !== 'archive-sessions') continue
1699
1852
  const cutDates = new Set(p.sessionBlocks.map(b => b.date))
1700
1853
  const keep = []
@@ -1728,13 +1881,61 @@ function cmdGarden(args) {
1728
1881
  atomicWriteFile(path.join(eng, 'context.md'), keep.join('\n').replace(/\n*$/, '\n'))
1729
1882
  })
1730
1883
  applied++
1884
+ touched.add('context.md')
1885
+ touched.add('context-archive.md')
1731
1886
  console.log(`applied: archived ${p.sessionBlocks.length} old session-end blocks → context-archive.md`)
1732
1887
  }
1733
- const hash = commitMemory(eng, 'garden', { files: ['context.md', 'context-archive.md'] })
1888
+ const hash = commitMemory(eng, 'garden', { files: [...touched] })
1734
1889
  if (!applied) console.log('no mechanical proposals applied (manual items remain)')
1735
1890
  else console.log(`garden done${hash ? ` @${hash}` : ''}`)
1736
1891
  }
1737
1892
 
1893
+ // Keep the first open-risk bullet per fingerprint; move later echoes under ## Retired.
1894
+ function applyRiskDedupe(eng, clusters) {
1895
+ const p = path.join(eng, 'risks.md')
1896
+ let md = readEng(eng, 'risks.md')
1897
+ if (!md) return 0
1898
+ const echoTexts = new Set()
1899
+ for (const group of clusters) {
1900
+ for (let i = 1; i < group.length; i++) echoTexts.add(group[i])
1901
+ }
1902
+ if (!echoTexts.size) return 0
1903
+ const retiredLines = []
1904
+ const kept = []
1905
+ let inRetired = false
1906
+ let moved = 0
1907
+ for (const raw of md.split('\n')) {
1908
+ const t = raw.trim()
1909
+ if (/^#{1,6}\s+Retired\b/i.test(t)) {
1910
+ inRetired = true
1911
+ kept.push(raw)
1912
+ continue
1913
+ }
1914
+ if (!inRetired) {
1915
+ const m = t.match(/^-\s*\[\d{4}-\d{2}-\d{2}\]\s*(?:\[@[^\]]+\]\s*)?(.*)$/)
1916
+ if (m && echoTexts.has(m[1].trim())) {
1917
+ retiredLines.push(raw)
1918
+ moved++
1919
+ continue
1920
+ }
1921
+ }
1922
+ kept.push(raw)
1923
+ }
1924
+ if (!moved) return 0
1925
+ let out = kept.join('\n')
1926
+ if (!/^#{1,6}\s+Retired\b/im.test(out)) {
1927
+ out = out.replace(/\n*$/, '\n\n## Retired\n')
1928
+ }
1929
+ const stamp = new Date().toISOString().slice(0, 10)
1930
+ const block = retiredLines.map(l => {
1931
+ const body = l.trim().replace(/^-\s*/, '')
1932
+ return `- [${stamp}] (garden dedupe) ${body}`
1933
+ }).join('\n')
1934
+ out = appendUnderSection(out, 'Retired', block)
1935
+ withFileLock(p, () => { atomicWriteFile(p, out.endsWith('\n') ? out : out + '\n') })
1936
+ return moved
1937
+ }
1938
+
1738
1939
  function engagementSlugFromPath(eng) {
1739
1940
  return path.basename(path.dirname(eng))
1740
1941
  }
package/bin/lib/memory.js CHANGED
@@ -151,12 +151,32 @@ function createMemoryApi(deps) {
151
151
  } catch (_) { return '' }
152
152
  }
153
153
 
154
+ // True only when .git exists AND can resolve HEAD. A corrupt ledger (broken
155
+ // HEAD / missing objects) still has a .git directory — existsSync alone lied.
156
+ function memoryGitHealthy(eng) {
157
+ if (!eng) return { ok: false, reason: 'missing' }
158
+ if (!fs.existsSync(path.join(eng, '.git'))) return { ok: false, reason: 'missing' }
159
+ if (!gitBinOk()) return { ok: false, reason: 'no-git-bin' }
160
+ try {
161
+ execFileSync('git', ['rev-parse', '--verify', 'HEAD'], {
162
+ cwd: eng, encoding: 'utf8', timeout: 5000, stdio: ['ignore', 'pipe', 'ignore'],
163
+ })
164
+ execFileSync('git', ['status', '--porcelain'], {
165
+ cwd: eng, encoding: 'utf8', timeout: 10000, stdio: ['ignore', 'pipe', 'ignore'],
166
+ })
167
+ return { ok: true, reason: '' }
168
+ } catch (_) {
169
+ return { ok: false, reason: 'broken' }
170
+ }
171
+ }
172
+
154
173
  return {
155
174
  MEMORY_EPHEMERAL,
156
175
  memoryPorcelainPaths,
157
176
  memoryDirtyManual,
158
177
  commitMemory,
159
178
  memoryHead,
179
+ memoryGitHealthy,
160
180
  ensureMemoryGit,
161
181
  }
162
182
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fdeops",
3
- "version": "3.9.12",
3
+ "version": "3.9.14",
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",
@@ -270,6 +270,7 @@ Running the engagement and ending it well.
270
270
  | Signal | Overlay |
271
271
  |--------|---------|
272
272
  | AI, ML, LLM, model, embeddings, RAG, agents, fine-tuning, inference, drift | `references/ai.md` |
273
+ | Golden set, eval suite, eval pack, pass/fail before AI ship, HITL gate for model | `references/eval-pack.md` (+ `ai.md`) |
273
274
  | Deck, slides, report, governance framework, compliance pack, ADR, PDF | `references/artifacts.md` |
274
275
  | Patient data, PHI, HIPAA, EHR, clinical | `references/healthcare.md` |
275
276
  | Payments, cardholder data, PCI-DSS, anything that moves money | `references/fintech.md` |
@@ -36,6 +36,19 @@ Never start with the most powerful model. Start with the cheapest that meets the
36
36
 
37
37
  Write model selection rationale to `decisions.md`. Include: models tested, test set size, scores, cost comparison.
38
38
 
39
+ ## Engagement eval pack (before AI ships)
40
+
41
+ When any slice touches a model, embeddings, RAG, or an agent: create or update `.fde/evals.md` **before** ship. Full method: `references/eval-pack.md`. This is the engagement-local test set — not unit tests.
42
+
43
+ **Minimum pack (do not grow until the minimum exists):**
44
+ 1. **Component + quality bar** — one sentence each; kill switch / fallback named.
45
+ 2. **Golden cases** — 5–20 representative inputs with expected outputs and a pass rule. Prefer real production-shaped data (sanitized).
46
+ 3. **Failure modes** — at least the silent ones: hallucination/ungrounded, retrieval miss (if RAG), drift, cost runaway.
47
+ 4. **Pass/fail** — dated run; Verdict **SHIP** or **NO-SHIP**; critical fails must be 0.
48
+ 5. **HITL gate** — which decisions need human review before action (align with `trust-profile.md`). Empty when policy requires review → NO-SHIP.
49
+
50
+ **When to write:** plan seeds the pack; sketch/build grows goldens; ship requires Verdict SHIP and a receipt in `delivery.md` → `## Ship receipts`. Non-AI work skips this file entirely.
51
+
39
52
  ## RAG architecture (retrieval-augmented generation)
40
53
 
41
54
  When the AI needs to answer questions about the client's data:
@@ -78,12 +91,13 @@ When the AI takes actions (not just generates text):
78
91
 
79
92
  ## Writes
80
93
 
81
- `trust-profile.md` - AI policy, data classification, model hosting, human-in-the-loop requirements. `decisions.md` - model selection rationale, architecture choices. `risks.md` - bias findings, drift observations, cost projections. `delivery.md` - AI component inventory with kill switches documented.
94
+ `trust-profile.md` - AI policy, data classification, model hosting, human-in-the-loop requirements. `evals.md` - golden cases, failure modes, SHIP/NO-SHIP, HITL. `decisions.md` - model selection rationale, architecture choices. `risks.md` - bias findings, drift observations, cost projections. `delivery.md` - AI component inventory with kill switches + eval receipt on ship.
82
95
 
83
96
  ## Principles
84
97
 
85
98
  - AI degrades silently. Monitor outputs, not just uptime.
86
99
  - Start with the cheapest model that meets the quality bar.
100
+ - No golden set, no AI ship (`evals.md` Verdict SHIP).
87
101
  - Every AI component needs a kill switch and a fallback path.
88
102
  - Log reasoning, not just results. Debug AI from its decisions.
89
103
  - Drift is inevitable. Define the detection method before shipping.
@@ -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`** - 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.
130
+ - **`delivery.md`** - append a **value ledger** row for every ship: Date | Slice | Bucket | Promised | Measured | Evidence | Rollback. Bucket is `cost-save` / `risk-mitigation` / `revenue-uplift`. "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
 
@@ -17,6 +17,12 @@ The engagement doesn't end at ship. It ends when the customer can maintain what
17
17
  - Which risk almost became real?
18
18
  - AI components: did they behave in production? What failure modes did the prototype hide? Is the team equipped to maintain them?
19
19
 
20
+ **1b. Value + receipts close gate (refuse green close if any fail):**
21
+ - Primary value bucket in `success.md` matches what the sponsor funded; at least one ledger row has **Measured** (not forever-`pending`) with evidence for that bucket — or the retrospective explicitly records “not measured; sponsor accepted pending.”
22
+ - Audit receipt exists for the final shipped path (exceptions/operating map walked; cite file).
23
+ - Eval receipt: **n/a if no AI**, else final golden/eval result + HITL owner recorded; kill switch / fallback named in `handoff.md`.
24
+ - One line in the retrospective: which bucket moved, by how much, vs baseline.
25
+
20
26
  **2. The pattern.** Anything that happened here and will happen again - a compliance approach, a migration pattern, a stakeholder dynamic - gets encoded for reuse. **If you do it twice, encode it.**
21
27
 
22
28
  **3. The handoff.** Operational knowledge for the person woken at 2am, not technical documentation: the 3 things that will break and the fix for each · who holds the tribal knowledge · what each alert means · deploy and rollback in plain language. AI components additionally: model version, what normal output looks like (so drift is recognisable), fallback behaviour, who owns retraining, **how to disable the AI path without taking down the feature** - without this the team turns it off at the first misbehaviour and it stays off.
@@ -33,11 +39,12 @@ The engagement doesn't end at ship. It ends when the customer can maintain what
33
39
 
34
40
  ## Checkpoint
35
41
 
36
- Direct assessment to the FDE: did the engagement achieve `success.md` · 2–3 lessons that matter · is the pattern worth encoding · is the handoff complete or where are the gaps. Honest - a gap named now is cheaper than a callback in six weeks.
42
+ Direct assessment to the FDE: did the engagement achieve `success.md` · 2–3 lessons that matter · is the pattern worth encoding · is the handoff complete or where are the gaps. Also: value bucket + audit receipt green; eval **n/a or green**. Pending Measured without sponsor acceptance = gap, not green close. Honest - a gap named now is cheaper than a callback in six weeks.
37
43
 
38
44
  ## Principles
39
45
 
40
46
  - Done = the customer operates without you.
47
+ - No named value bucket moved (or sponsor-accepted pending) = not a green close.
41
48
  - The retrospective is an investment in the next engagement, not a post-mortem.
42
49
  - Encode what repeated. The same lesson learned twice is a process failure.
43
50
  - Write the handoff for 2am.
@@ -92,6 +92,7 @@ The real spec is what people **do** when the system fails - not what the slide d
92
92
  - **The hesitation.** When someone says "well, there's also this other thing we do…" - stop them, ask them to finish. The main story is what they're comfortable explaining; the hesitation is the real problem.
93
93
  - **"Which part of the codebase do you least want to touch?"** The answer is unanimous and it's the load-bearing wall. Check it against your churn scan - when the human answer and the churn data agree, that's your first map landmark.
94
94
  - **Shadow AI.** Someone pasting data into ChatGPT to cope = a real unmet need + an uncontrolled data risk. Note both.
95
+ - **Exception-led operating map.** For each real break (not the slide-deck process): what fails, who notices first, what they do today, and which artifact is trusted in that moment. Prefer exceptions over happy-path swimlanes — the workaround is the operating system. Write rows under `terrain.md` → `## Operating map (exception-led)`. If the section is missing on an older engagement, add it; never regenerate the rest of terrain. When AI is in play, also fill `## Intelligence placement` (deterministic vs LLM judgement vs human approve).
95
96
 
96
97
  ## Method - part 3: workshop facilitation
97
98
 
@@ -165,6 +166,11 @@ Score every candidate use case before anything gets prototyped:
165
166
  **Data flow:** <entry → transform → store → exit>
166
167
  **Test landscape:** <covered / gaps / lies>
167
168
  **Unknowns:** <named explicitly - an honest gap beats a confident guess>
169
+
170
+ ## Operating map (exception-led)
171
+ | Exception / break | Who notices first | What they do today | System of record then | Blast | Evidence |
172
+ |-------------------|-------------------|--------------------|----------------------|-------|----------|
173
+ | <break> | <role> | <workaround> | <sheet/DB/person> | CRITICAL / LOAD-BEARING / CONVENIENCE | <who/day> |
168
174
  ```
169
175
 
170
176
  Every line carries its evidence. `(churn: 47/90d)` `(ops lead, Day 5)` `(stated, unverified)`.
@@ -173,11 +179,12 @@ Every line carries its evidence. `(churn: 47/90d)` `(ops lead, Day 5)` `(stated,
173
179
 
174
180
  ## Checkpoint (before any build)
175
181
 
176
- Present to the FDE, four things, one paragraph each - no padding:
182
+ Present to the FDE, five things, one paragraph each - no padding:
177
183
  1. The real problem, with the two strongest pieces of evidence.
178
184
  2. The top 3 risk areas of the codebase, one line of why each.
179
185
  3. What must not be touched without characterisation tests.
180
- 4. The recommendation: confirm brief / descope / rescope - and the decision it puts in front of the sponsor.
186
+ 4. The exception-led operating map: the two breaks that matter most, who owns the workaround, and where shadow systems live.
187
+ 5. The recommendation: confirm brief / descope / rescope - and the decision it puts in front of the sponsor.
181
188
 
182
189
  If discovery revealed the problem is 3× the brief: the FDE tells the customer **before** telling themselves it's manageable. Lead with evidence, offer three paths (descope / rescope / pause-and-plan), confirm any reset in writing - update `success.md` and `brief.md` before continuing.
183
190
 
@@ -0,0 +1,43 @@
1
+ # eval-pack - prove the system before it acts
2
+
3
+ **Enter when:** the work touches AI/LLM/agents/RAG, or ship/close is blocked because there is no evidence the non-deterministic path is safe. Activate alongside `ai.md`, `sketch`, `build`, or `ship` — not instead of them.
4
+
5
+ **Read first:** `trust-profile.md` (AI policy + HITL), `terrain.md` (operating map), `delivery.md`. Create or extend `evals.md`.
6
+
7
+ Non-AI engagements skip this pack entirely.
8
+
9
+ ## Why this exists
10
+
11
+ Intelligence without evidence is token-maxing with a nicer name. An FDE earns trust by showing: golden cases, failure modes, and a human gate before action.
12
+
13
+ ## Method (you do this work)
14
+
15
+ **1. Scope the judgement surface.** One sentence: which step uses model judgement, and what must never be autonomous.
16
+
17
+ **2. Build a golden set (minimum 5–20 for a slice; prefer 50–100 before broad scale).** For each case:
18
+ - input (sanitized — no `<private>` raw values)
19
+ - expected outcome or expert-approved acceptance note
20
+ - pass rule (exact / contains / short rubric)
21
+ - source: real historical example / expert label / staged fixture
22
+
23
+ **3. Score pass/fail, not vibes.** Run the suite. Record count pass / fail. Failures get a failure-mode tag (missing data, wrong record, format drift, hallucination, retrieval miss, unsafe action, other).
24
+
25
+ **4. Human-in-the-loop gate.** Name which outcomes require human approve before side effects. If none, write why that is allowed under `trust-profile.md` AI policy — do not invent permission.
26
+
27
+ **5. Ship rule.** Until `evals.md` shows Verdict **SHIP** with a dated run (critical fails = 0) and HITL filled when policy requires it, AI-touching ship stays **fix-first**. Log a one-line eval receipt in `delivery.md` → `## Ship receipts`.
28
+
29
+ ## Artifact — `evals.md`
30
+
31
+ Create on first AI-touching slice (not at `resume --init`). Use the stub in `templates/.fde/evals.md`. Every claim needs a source. Missing evidence → leave the cell `unknown - ask:`, never invent scores.
32
+
33
+ ## Checkpoint
34
+
35
+ Present to the FDE: suite size, pass rate, top failure mode, HITL gate, Verdict SHIP/NO-SHIP. If they want to ship without a run: say no, and offer the smallest suite that would unblock.
36
+
37
+ ## Principles
38
+
39
+ - No golden set, no AI ship.
40
+ - Pass/fail beats “looks good.”
41
+ - Failure modes are the product — the happy path is table stakes.
42
+ - HITL is a gate, not a slide.
43
+ - Non-AI work does not need this file.
@@ -65,6 +65,7 @@ Let silence sit. If their fear doesn't match the written brief, the brief is wro
65
65
  - **The previous attempt** - "we tried something similar last year" is the most important sentence in the first meeting. Who was involved? Still there and protective, or gone because of it?
66
66
  - **The passed-over internal team** - they know exactly what's wrong, and they resent the FDE's presence. Find them before the first standup, ask what they tried, use their language in every meeting. Make them look right and they protect you; ignore them and they wait for the mistake.
67
67
  - **The sacred thing** - "Is there anything in this environment I should treat as untouchable?" The hesitation before the answer is the answer.
68
+ - **Exception path (operating map seed)** - "When the happy path breaks this week, what do people actually do — who do they call, what spreadsheet opens, what do they skip?" Capture the break → workaround → who owns it. Do not build a full map on day 1; seed rows later in `terrain.md` → `## Operating map (exception-led)` during discover. Unknowns stay `unknown - ask:`.
68
69
  - **AI posture and policy** - tools already in use (sanctioned or shadow), and: "Does your organisation have a policy on AI-generated code? Are there decisions where you would not be comfortable with AI involvement?"
69
70
  - **Boundaries in multi-vendor rooms** - who owns what surface, who signs off before a change crosses it.
70
71
 
@@ -76,7 +77,7 @@ Before the end of day 1, ship one visible thing: a small bug fix, a cleanup the
76
77
 
77
78
  **`brief.md`** - what they said, who sent the FDE, the timeline, **and the gap list**.
78
79
 
79
- **`success.md`** - what done looks like, how it's measured, who actually signs off, what is explicitly out of scope. Agreed with the customer, not assumed.
80
+ **`success.md`** - what done looks like, **primary value bucket** (`cost-save` | `risk-mitigation` | `revenue-uplift`), baseline → target, who actually signs off, what is explicitly out of scope. Agreed with the customer, not assumed.
80
81
 
81
82
  **`stakeholders.md`**:
82
83
  ```markdown
@@ -100,7 +101,7 @@ One falsifiable hypothesis about the real problem also goes at the bottom of `br
100
101
 
101
102
  ## Checkpoint
102
103
 
103
- 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.
104
+ One page back to the FDE: success + value bucket + sign-off owner, out-of-scope boundary, sacred data, stakeholder map with veto power, AI posture, the hypothesis, the top CRITICAL assumptions still OPEN, and any exception-path seeds heard (break → workaround → owner) for discover to map into `terrain.md`. If it doesn't fit one page, the engagement isn't understood yet.
104
105
 
105
106
  If remote: trust-building takes ~40% longer - push for a short video call before anything asynchronous.
106
107
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  **Enter when:** a slice is built, reviewed, and ready to deploy.
4
4
 
5
- **Read first:** `context.md`, `delivery.md`. Load `trust-profile.md` if the deploy touches regulated data or needs an approval chain.
5
+ **Read first:** `context.md`, `delivery.md`, `success.md`. Load `trust-profile.md` if the deploy touches regulated data or needs an approval chain. Load `evals.md` when the deploy touches AI/ML/LLM/RAG/agents.
6
6
 
7
7
  Opening question, calm tech lead voice: **has anyone actually *run* the rollback, or is it still a slide?** If only planned, that's today's work - say so plainly.
8
8
 
@@ -42,10 +42,26 @@ Score each dimension green/amber/red. This is the gate, not a suggestion:
42
42
  | Runbook | Exists and someone other than you has read it | Exists but unreviewed | Missing |
43
43
  | Monitoring | Alerts configured, owner named, dashboard live | Alerts configured, no named owner | No monitoring |
44
44
 
45
+ ### Value + receipts gate (score with the table above)
46
+
47
+ | Dimension | Green | Amber | Red |
48
+ |-----------|-------|-------|-----|
49
+ | **Value bucket** | `success.md` names primary bucket (`cost-save` \| `risk-mitigation` \| `revenue-uplift`) and a baseline→target metric; this slice’s value-ledger row has **Bucket** + **Promised** | Bucket named; **Measured** still `pending` with a pulse date | No bucket, or Promised empty / ticket-theater only |
50
+ | **Audit receipt** | Dated line in `delivery.md` (`## Ship receipts` or ledger Evidence) proving exceptions/operating path were walked — cite `terrain.md` / `reality.md` / `audit.md` | Path described, not verified this ship | No audit receipt for this slice |
51
+ | **Eval receipt** | **n/a** (no AI on this slice) **or** `evals.md` Verdict SHIP with dated golden run + HITL gate named | Eval pack exists; known fails open with owner + date | AI in scope and no eval receipt |
52
+ | **AI eval pack** | `.fde/evals.md` Verdict SHIP; goldens run this change; critical fails 0; HITL filled if policy requires | Pack exists; run stale vs change log | AI-touching deploy and pack missing / NO-SHIP / HITL required but empty |
53
+
45
54
  **Any RED = stop. Do not deploy. Fix the red dimension first.**
46
55
  **2+ AMBER = sponsor conversation before deploying.** Present the ambers and get explicit "proceed" or "fix first."
47
56
 
48
- Write the readiness score to `delivery.md` before deploying. The score is the evidence if anything goes wrong.
57
+ **AI-touching deploys (model, embeddings, RAG, agent, or inference path):**
58
+ 1. Read `.fde/evals.md`. If missing → **RED. Do not deploy.** Create the pack (`eval-pack` / `ai` overlay) and re-score.
59
+ 2. If Verdict is not **SHIP**, or Last run is older than the latest change-log row → **RED.**
60
+ 3. If `trust-profile.md` requires human-in-the-loop and the HITL gate has no reviewer → **RED.**
61
+ 4. Log in `delivery.md` → `## Ship receipts` before deploy: audit cite + eval receipt.
62
+ 5. Non-AI deploys: Eval = **n/a** — do not invent an empty pack.
63
+
64
+ Write the readiness score (including value + receipts) to `delivery.md` before deploying. The score is the evidence if anything goes wrong.
49
65
 
50
66
  ## Pre-blast challenge (before the deploy button)
51
67
 
@@ -146,13 +162,14 @@ Adoption isn't a handoff-stage problem - it starts during build. Software that l
146
162
 
147
163
  ## Checkpoint
148
164
 
149
- Before 100%: canary clean, business metric verified, pulse written into `delivery.md`. Any item unconfirmedthe deploy waits. For enterprise-scale: scale-readiness gate passed before broad rollout.
165
+ Before 100%: canary clean, business metric verified, pulse written into `delivery.md`. Also green: value bucket named, audit receipt dated, eval receipt **n/a or pass**. Missing any of those not green. For enterprise-scale: scale-readiness gate passed before broad rollout.
150
166
 
151
167
  ## Principles
152
168
 
153
169
  - A deployment without a tested rollback is reckless.
154
170
  - Roll back on any canary anomaly; investigate safely.
155
171
  - Verify the business metric, not just the technical one.
156
- - No pulse, no done.
172
+ - No value bucket, no green ship. No pulse, no done.
173
+ - AI path without eval receipt = fix-first; non-AI ships leave eval as n/a.
157
174
  - Scale readiness is organizational, not just technical. Check all 8 dimensions.
158
175
  - Adoption is measured from day one, not hoped for at launch.
@@ -4,9 +4,17 @@
4
4
 
5
5
  ## Value ledger
6
6
 
7
- | Date | Slice | Promised | Measured | Evidence | Rollback |
8
- |------|-------|----------|----------|----------|----------|
9
- | | | *(what we said it would change)* | *(what actually changed, or pending)* | *(who/when/metric)* | |
7
+ | Date | Slice | Bucket | Promised | Measured | Evidence | Rollback |
8
+ |------|-------|--------|----------|----------|----------|----------|
9
+ | | | *(cost-save / risk-mitigation / revenue-uplift)* | *(what we said it would change)* | *(what actually changed, or pending)* | *(who/when/metric)* | |
10
+
11
+ ## Ship receipts
12
+
13
+ <!-- Fill before green ship. Eval = n/a unless AI touches the slice. -->
14
+
15
+ | Date | Slice | Audit receipt | Eval receipt |
16
+ |------|-------|---------------|--------------|
17
+ | | | *(dated cite: exceptions/operating path verified — terrain/reality/audit)* | *(n/a \| evals.md pass + HITL owner)* |
10
18
 
11
19
  ## Shipped
12
20
 
@@ -0,0 +1,42 @@
1
+ # Engagement eval pack
2
+
3
+ <!-- AI-touching work only. Fill before ship. Empty pack = do not deploy AI path. Non-AI engagements: leave unused or delete. -->
4
+
5
+ ## Component
6
+ - **Name / slice:**
7
+ - **Model / stack:** <!-- rules | small model | frontier | RAG | agent -->
8
+ - **Quality bar:**
9
+ - **Kill switch / fallback:**
10
+ - **Owner:**
11
+
12
+ ## Golden cases
13
+
14
+ | ID | Input (sanitized) | Expected | Pass rule | Last run | Result |
15
+ |----|-------------------|----------|-----------|----------|--------|
16
+ | G1 | | | | | |
17
+
18
+ ## Failure modes
19
+
20
+ | Mode | How it shows up | Detection | Mitigation |
21
+ |------|-----------------|-----------|------------|
22
+ | | | | |
23
+
24
+ ## Pass / fail (this ship)
25
+ - **Golden:** _/_ pass (threshold: _)
26
+ - **Critical fails (must be 0):**
27
+ - **Verdict:** <!-- SHIP | NO-SHIP -->
28
+ - **Evidence:** <!-- who/when -->
29
+
30
+ ## Human-in-the-loop gate
31
+
32
+ | Decision / action | Autonomous OK? | Reviewer role | Escalation |
33
+ |-------------------|----------------|---------------|------------|
34
+ | | | | |
35
+
36
+ **HITL rule for this ship:**
37
+
38
+ ## Change log
39
+
40
+ | Date | What changed | Pack re-run? | Notes |
41
+ |------|--------------|--------------|-------|
42
+ | | | | |
@@ -3,5 +3,7 @@
3
3
  <!-- Agreed definition of done. Out-of-scope is as important as in-scope. -->
4
4
 
5
5
  **Done when:**
6
+ **Primary value bucket:** <!-- cost-save | risk-mitigation | revenue-uplift (pick one) -->
7
+ **Baseline → target:** <!-- metric, number, by when -->
6
8
  **Explicitly out of scope:**
7
9
  **Stakeholder who signs off:**
@@ -5,3 +5,21 @@
5
5
  **Stack:**
6
6
  **Hotspots (handle with care):**
7
7
  **Test gaps:**
8
+
9
+ ## Operating map (exception-led)
10
+
11
+ <!-- How work actually runs when the happy path fails. Fill in discover; leave blank until heard/seen. -->
12
+
13
+ | Exception / break | Who notices first | What they do today (workaround) | System of record then | Blast if wrong | Evidence |
14
+ |-------------------|-------------------|---------------------------------|-----------------------|----------------|----------|
15
+ | | | | | | |
16
+
17
+ **Shadow systems / silent workarounds:**
18
+ **Sacred / untouchable in ops:**
19
+ **Previous attempt residue:**
20
+
21
+ ## Intelligence placement (when AI is in play)
22
+
23
+ | Step | Deterministic | Model judgement | Human approve |
24
+ |------|---------------|-----------------|---------------|
25
+ | | | | |