fdeops 3.16.0 → 3.17.0

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
@@ -2,16 +2,9 @@
2
2
 
3
3
  **Forward deployed engineering skills for AI coding agents.**
4
4
 
5
- Skills encode the workflows, quality gates, and judgment Forward Deployed Engineers use on someone else's site. Packaged so an AI coding agent can run the embed end-to-end: discovery, POC, slice, on-site proof, eval, signed outcome. The workspace still compiles and commits. `@fde` does not leave.
5
+ Skills encode the workflows, quality gates, and judgment Forward Deployed Engineers use on someone else's site. Packaged so an AI coding agent can run the embed end-to-end: discovery, POC, their codebase (greenfield or brownfield), go-live, eval, signed outcome. The workspace still compiles and commits. `@fde` does not leave.
6
6
 
7
- ```text
8
- LAND DISCOVER PLAN SHIP PROVE CLOSE
9
- ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐
10
- │ Brief │ ───▶ │ Reality│ ───▶ │ Sequence│───▶ │ Live │ ───▶ │ Signed │ ───▶ │ They │
11
- │ Trust │ │ Terrain│ │ Align │ │ slice │ │ off │ │ run │
12
- └────────┘ └────────┘ └────────┘ └────────┘ └────────┘ └────────┘
13
- /brief /discover /plan /ship /outcome /close
14
- ```
7
+ <img width="1774" height="887" alt="FDEops-githubposter" src="https://github.com/user-attachments/assets/9eb842d2-a356-4995-9057-841f1b2d15d6" />
15
8
 
16
9
  ---
17
10
 
@@ -21,24 +14,24 @@ Each command loads the same `@fde` skill. You never pick from 31 names.
21
14
 
22
15
  | What you're doing | Command | Key principle |
23
16
  |-------------------|---------|-----------|
24
- | Land the embed | `/brief` | Who signs done |
25
- | Find the real problem | `/discover` | Brief is a hypothesis |
26
- | Plan the sequence | `/plan` | Backwards from done |
27
- | Ship a slice | `/ship` | Live with a rollback |
28
- | Prove the outcome | `/outcome` | Promised, measured, accepted |
29
- | Close the embed | `/close` | They run it without you |
17
+ | First week on site | `/brief` | Name who signs done |
18
+ | Find the real problem | `/discover` | Treat the brief as a hypothesis |
19
+ | Sequence the work | `/plan` | Work backwards from done |
20
+ | Ship to their production | `/ship` | One change they can see, then go live with a rollback you have run |
21
+ | Get the number accepted | `/outcome` | Promised, measured, accepted |
22
+ | Hand off so they run it | `/close` | They operate it without you |
30
23
 
31
24
  Also:
32
25
 
33
26
  | What you're doing | Command | Key principle |
34
27
  |-------------------|---------|-----------|
35
- | Sponsor went silent | `/trust` | Process or trust |
36
- | Scope dispute | `/receipts` | Dated, or a gap |
28
+ | Sponsor went quiet | `/trust` | Process gap, or they stopped trusting you |
29
+ | When did we agree? | `/receipts` | A dated line, or it did not happen |
37
30
  | After a meeting | `/debrief` | Notes into the record |
38
- | Walk-in | `/prep` | One page from the record |
39
- | Friday readout | `/readout` | Promised, measured, accepted |
31
+ | Prep before the meeting | `/prep` | One page from the record |
32
+ | Friday sponsor update | `/readout` | Promised, measured, accepted |
40
33
 
41
- Skills also activate on English: naming a client, running a POC, slicing a feature, asking what was agreed. A throwaway one-liner in an unbound repo can skip `@fde`. A client slice cannot.
34
+ Skills also activate on English: naming a client, running a POC, changing their checkout, going live, asking what was agreed. A throwaway one-liner in an unbound repo can skip `@fde`. Bound client work cannot.
42
35
 
43
36
  ---
44
37
 
@@ -100,9 +93,9 @@ Requires Node.js >= 18. Override: `FDEOPS_ENGAGEMENT`. See [docs/install.md](doc
100
93
 
101
94
  ---
102
95
 
103
- ## All 31 Skills
96
+ ## All 30 Skills
104
97
 
105
- The commands above are the entry points. Under the hood, `@fde` activates these 31 skills, each a structured workflow with steps, an artifact, and a checkpoint. You never pick one by name. Full detail: [docs/skills-reference.md](docs/skills-reference.md).
98
+ The commands above are the entry points. Under the hood, `@fde` activates these 30 skills, each a structured workflow with steps, an artifact, and a checkpoint. You never pick one by name. Full detail: [docs/skills-reference.md](docs/skills-reference.md).
106
99
 
107
100
  ### Land - Brief and trust
108
101
 
@@ -118,7 +111,7 @@ The commands above are the entry points. Under the hood, `@fde` activates these
118
111
 
119
112
  | Skill | What It Does | Use When |
120
113
  |--------|--------------|----------|
121
- | [discover](skills/fde/references/discover.md) | Repo + workaround + the real problem | Brief feels wrong, shadow processes |
114
+ | [discover](skills/fde/references/discover.md) | Question first, then repo + workaround | Brief feels wrong, shadow processes |
122
115
  | [test-assumptions](skills/fde/references/test-assumptions.md) | Untested assumptions by blast radius | Brief feels too neat |
123
116
  | [score-use-cases](skills/fde/references/score-use-cases.md) | Value × urgency × alignment / complexity | Everything is P0 |
124
117
  | [poc](skills/fde/references/poc.md) | Kill the killer assumption in a day | POC, spike, need to de-risk |
@@ -127,23 +120,22 @@ The commands above are the entry points. Under the hood, `@fde` activates these
127
120
 
128
121
  | Skill | What It Does | Use When |
129
122
  |--------|--------------|----------|
130
- | [plan](skills/fde/references/plan.md) | Backwards from done, PR-sized | What order, what is done |
123
+ | [plan](skills/fde/references/plan.md) | Backwards from done, Kill if on each PR | What order, what is done |
131
124
  | [business-case](skills/fde/references/business-case.md) | Cost of nothing → investment → return | Defend budget or timeline |
132
125
  | [three-options](skills/fde/references/three-options.md) | Three genuine options | "What should we do?" |
133
126
  | [pick-three](skills/fde/references/pick-three.md) | Pick three from twenty urgents | Everything is urgent |
134
127
 
135
- ### Ship - Go live
128
+ ### Ship - On their repo, then live
136
129
 
137
130
  | Skill | What It Does | Use When |
138
131
  |--------|--------------|----------|
139
- | [thin-slices](skills/fde/references/thin-slices.md) | Vertical slices, on-site proof | Building on their codebase |
132
+ | [ship](skills/fde/references/ship.md) | One change they can see (greenfield or brownfield), proven on their staging, then go live | Building, updating, or going live |
140
133
  | [what-breaks](skills/fde/references/what-breaks.md) | Impact from contained → irreversible | Touching shared infrastructure |
141
134
  | [rescue](skills/fde/references/rescue.md) | Production fire or trust fire | Down, or they went quiet |
142
- | [ship](skills/fde/references/ship.md) | Intent vs diff, pre-flight, rollback | Going live |
143
135
  | [review](skills/fde/references/review.md) | Did we only build what we agreed | Before merge, scope creep |
144
136
  | [rollback](skills/fde/references/rollback.md) | Test the escape route before 2am | "We can always revert" |
145
137
 
146
- ### Prove - Outcome
138
+ ### Outcome - Get the number accepted
147
139
 
148
140
  | Skill | What It Does | Use When |
149
141
  |--------|--------------|----------|
@@ -192,7 +184,7 @@ One skill. One reference file per situation. One folder per client.
192
184
  ```
193
185
 
194
186
  - **Process, not prose.** Each reference is a workflow with an artifact and a checkpoint, not a tip sheet.
195
- - **Ground loop.** Name → characterise → prove where they live → log. The workspace compiles; `@fde` stays.
187
+ - **Ground loop.** Name → characterise → prove where they live → go live → log. The workspace compiles; `@fde` stays.
196
188
  - **You confirm.** Nothing is written until you say so.
197
189
  - **Progressive disclosure.** `SKILL.md` is the entry point. One `references/*.md` loads when routed.
198
190
  - **Local CLI.** Writes and status cost zero model tokens. The AI coding agent runs it.
@@ -207,7 +199,7 @@ Change hosts, install `@fde` on the new one, bind if needed, keep talking. The r
207
199
  fdeops/
208
200
  ├── skills/fde/ # the one skill hosts load
209
201
  │ ├── SKILL.md # router
210
- │ └── references/ # 31 skills + overlays (you never pick)
202
+ │ └── references/ # 30 skills + overlays (you never pick)
211
203
  │ ├── land.md # Land
212
204
  │ ├── audit.md
213
205
  │ ├── who-decides.md
@@ -221,13 +213,12 @@ fdeops/
221
213
  │ ├── business-case.md
222
214
  │ ├── three-options.md
223
215
  │ ├── pick-three.md
224
- │ ├── thin-slices.md # Ship
216
+ │ ├── ship.md # Ship
225
217
  │ ├── what-breaks.md
226
218
  │ ├── rescue.md
227
- │ ├── ship.md
228
219
  │ ├── review.md
229
220
  │ ├── rollback.md
230
- │ ├── readout.md # Prove
221
+ │ ├── readout.md # Outcome
231
222
  │ ├── demo-prep.md
232
223
  │ ├── debrief.md
233
224
  │ ├── board-memo.md
@@ -274,7 +265,7 @@ fdeops/
274
265
 
275
266
  AI coding agents are built for a repo, not for a client. They forget the sponsor, the promise, who can say yes, and whether anyone accepted the number. Monday morning they start from the ticket again.
276
267
 
277
- FDEOps is what you take on site. One `@fde` skill runs the embed from discovery to signed outcome: POC, slice, on-site proof, eval when a model judges, promised → measured → accepted. A local CLI dates every decision. `.fde/` is markdown on your laptop. You confirm; then it is on the record.
268
+ FDEOps is what you take on site. One `@fde` skill runs the embed from discovery to signed outcome: POC, their codebase, go-live, eval when a model judges, promised → measured → accepted. A local CLI dates every decision. `.fde/` is markdown on your laptop. You confirm; then it is on the record.
278
269
 
279
270
  ---
280
271
 
@@ -314,7 +305,7 @@ Local only - `git` + files, no network, no telemetry. Plain markdown. The model
314
305
  ## Principles
315
306
 
316
307
  - **The artifact is the memory** - producing the work and recording it are one action
317
- - **Ground loop** - name the slice, characterise their code, prove it where they live, log the outcome
308
+ - **Ground loop** - name the change, characterise their code, prove it on their staging, go live, log the outcome
318
309
  - **Skills, not autonomy** - the kit says what to check; judgment stays yours
319
310
  - **Brief is a hypothesis** - discover before building the wrong thing
320
311
  - **Evidence on every claim** - these files get defended in the room
@@ -6,7 +6,7 @@ You are the AI coding agent for a **Forward Deployed Engineer (FDE)** - the huma
6
6
 
7
7
  When the FDE types **`@fde`**, names a client, pastes meeting notes, asks what was agreed, or describes embed work (quiet sponsor, brief feels wrong, Friday update) - load the skill. If `fde resume` says NO ENGAGEMENT: ask the client name once, then **you** run `fde resume --init <slug>`. Never tell them to type it.
8
8
 
9
- Do **not** load `@fde` for a one-line typo in an unbound repo. On a bound client, stay on `@fde` for POC, slices, characterisation, on-site proof, eval, and go-live.
9
+ Do **not** load `@fde` for a one-line typo in an unbound repo. On a bound client, stay on `@fde` for POC, small PRs, characterisation, proof on their staging, eval, and go-live.
10
10
 
11
11
  - Skill (single source of truth): `~/.claude/skills/fde/SKILL.md`
12
12
  - **Never ask the FDE to pick a skill.** Read the situation, route silently, do the work.
@@ -6,7 +6,7 @@ Context for Gemini CLI when assisting a **Forward Deployed Engineer (FDE)** - th
6
6
 
7
7
  When the FDE types **`@fde`**, names a client, pastes meeting notes, asks what was agreed, or describes embed work (quiet sponsor, brief feels wrong, Friday update) - load the skill. If `fde resume` says NO ENGAGEMENT: ask the client name once, then **you** run `fde resume --init <slug>`. Never tell them to type it.
8
8
 
9
- Do **not** load `@fde` for a one-line typo in an unbound repo. On a bound client, stay on `@fde` for POC, slices, characterisation, on-site proof, eval, and go-live.
9
+ Do **not** load `@fde` for a one-line typo in an unbound repo. On a bound client, stay on `@fde` for POC, small PRs, characterisation, proof on their staging, eval, and go-live.
10
10
 
11
11
  - Skill (single source of truth): `~/.claude/skills/fde/SKILL.md`
12
12
  - **Never ask the FDE to pick a skill.** Read the situation, route silently, do the work.
@@ -62,7 +62,7 @@ The model reads SKILL.md, routes to the right skill, and produces artifacts in y
62
62
 
63
63
  ## Model size recommendations
64
64
 
65
- The methodology is detailed (31 skills, routing logic, evidence format, memory contract). Larger models handle it better:
65
+ The methodology is detailed (30 skills, routing logic, evidence format, memory contract). Larger models handle it better:
66
66
 
67
67
  | Model class | Experience |
68
68
  |-------------|-----------|
@@ -31,4 +31,4 @@ Defaults to the current directory if no path is given. Existing files are never
31
31
 
32
32
  ## The principle
33
33
 
34
- The adapter only tells the tool **where the brain is and how to behave**. All 31 skills, the overlays, and the memory contract live once in `skills/fde/SKILL.md`. Update the brain, every platform gets it. That's why fdeops feels native in whatever the FDE already uses, without five things to keep in sync.
34
+ The adapter only tells the tool **where the brain is and how to behave**. All 30 skills, the overlays, and the memory contract live once in `skills/fde/SKILL.md`. Update the brain, every platform gets it. That's why fdeops feels native in whatever the FDE already uses, without five things to keep in sync.
@@ -6,7 +6,7 @@ You are the AI coding agent for a **Forward Deployed Engineer (FDE)** - the huma
6
6
 
7
7
  When the FDE types **`@fde`**, names a client, pastes meeting notes, asks what was agreed, or describes embed work (quiet sponsor, brief feels wrong, Friday update) - load the skill. If `fde resume` says NO ENGAGEMENT: ask the client name once, then **you** run `fde resume --init <slug>`. Never tell them to type it.
8
8
 
9
- Do **not** load `@fde` for a one-line typo in an unbound repo. On a bound client, stay on `@fde` for POC, slices, characterisation, on-site proof, eval, and go-live.
9
+ Do **not** load `@fde` for a one-line typo in an unbound repo. On a bound client, stay on `@fde` for POC, small PRs, characterisation, proof on their staging, eval, and go-live.
10
10
 
11
11
  - Skill (single source of truth): `~/.claude/skills/fde/SKILL.md`
12
12
  - **Never ask the FDE to pick a skill.** Read the situation, route silently, do the work.
@@ -11,7 +11,7 @@ You are the AI coding agent for a **Forward Deployed Engineer (FDE)** - the huma
11
11
 
12
12
  When the FDE types **`@fde`**, names a client, pastes meeting notes, asks what was agreed, or describes embed work (quiet sponsor, brief feels wrong, Friday update) - load `@fde`. If `fde resume` says NO ENGAGEMENT: ask the client name once, then **you** run `fde resume --init <slug>`. Never tell them to type it.
13
13
 
14
- Do **not** load `@fde` for a one-line typo in an unbound repo. On a bound client, stay on `@fde` for POC, slices, characterisation, on-site proof, eval, and go-live.
14
+ Do **not** load `@fde` for a one-line typo in an unbound repo. On a bound client, stay on `@fde` for POC, small PRs, characterisation, proof on their staging, eval, and go-live.
15
15
 
16
16
  - Skill (single source of truth): `~/.claude/skills/fde/SKILL.md` (or the copy this install placed)
17
17
  - **Never ask the FDE to pick a skill.** Read the situation, route silently, do the work.
package/bin/check.js CHANGED
@@ -203,6 +203,14 @@ ok(`router dispatch (${mentioned.length} reference targets verified) + memory co
203
203
  const extra = fs.readdirSync(refDir).filter(f => f.endsWith('.md') && !mentioned.includes(f))
204
204
  if (extra.length) fail(`unrouted reference file(s) - dead method: ${extra.join(', ')}`)
205
205
  else ok('no unrouted reference files')
206
+
207
+ // The on-site change loop lives in ship.md. A sibling method is a split.
208
+ for (const dead of ['small-prs.md', 'thin-slices.md', 'implement.md']) {
209
+ if (fs.existsSync(path.join(refDir, dead))) {
210
+ fail(`${dead} must not exist - that craft lives in ship.md`)
211
+ }
212
+ }
213
+ ok('ship is one method (no implement / small-prs / thin-slices sibling)')
206
214
  }
207
215
 
208
216
  const install = read('bin/install.js')
@@ -221,8 +229,10 @@ if (read('package.json').includes('postinstall')) {
221
229
  }
222
230
 
223
231
  const readme = read('README.md')
224
- if (/session\.gif|demo\.gif|<img /i.test(readme)) {
225
- fail('README must not embed images - front door is text; the recording lives in docs/USAGE.md')
232
+ if (/session\.gif|demo\.gif/i.test(readme)) {
233
+ fail('README must not embed session.gif or demo.gif - the recording lives in docs/USAGE.md')
234
+ } else if (/<img /i.test(readme) && !/user-attachments\/assets/.test(readme)) {
235
+ fail('README <img> must be the GitHub poster (user-attachments), not a local gif')
226
236
  } else ok('README is text (no gif)')
227
237
 
228
238
  const usage = read('docs/USAGE.md')
@@ -268,9 +278,11 @@ for (const cmd of ['/brief', '/discover', '/plan', '/ship', '/outcome', '/close'
268
278
  if (/(^|[^\w/])\/got\b/.test(readme)) fail('README must use /outcome, not /got')
269
279
  ok('README slash commands documented')
270
280
 
271
- // Front-door map is the embed left-to-right (LANDCLOSE), not a pile of situations.
272
- if (!readme.slice(0, 4000).includes('LAND') || !readme.slice(0, 4000).includes('/discover')) {
273
- fail('README must include the LAND→CLOSE command-map diagram near the top')
281
+ // Front-door map is the embed left-to-right (LandClose). After the GitHub
282
+ // poster (#68) the table is the map: /brief /discover /plan /ship /outcome /close.
283
+ const front = readme.slice(0, 4000)
284
+ if (!['/brief', '/discover', '/plan', '/ship', '/outcome', '/close'].every(c => front.includes(c))) {
285
+ fail('README must include the Land→Close command map near the top')
274
286
  } else ok('README command-map diagram')
275
287
 
276
288
  if (readme.includes('your-client-repo')) {
package/bin/fde.js CHANGED
@@ -408,6 +408,8 @@ const SECRET_PATTERNS = [
408
408
  { name: 'Slack token', re: /\bxox[baprs]-[A-Za-z0-9-]{10,}\b/ },
409
409
  { name: 'PEM private key', re: /-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----/ },
410
410
  { name: 'Bearer token', re: /\bBearer\s+[A-Za-z0-9._\-]{20,}\b/ },
411
+ { name: 'database URL', re: /\b[a-z][a-z0-9+.-]*:\/\/[^/\s:]+:[^/\s@]+@/i },
412
+ { name: 'api key assignment', re: /\b(?:api[_-]?key|secret|password)\s*=\s*\S{8,}/i },
411
413
  ]
412
414
 
413
415
  function findSecretHit(text) {
@@ -769,6 +771,89 @@ function firstLine(md, maxLen) {
769
771
  return ''
770
772
  }
771
773
 
774
+ // reality.md is the one panel whose job is "not the brief". If the file does
775
+ // not carry Working theory / Evidence / Differs from brief, do not scrape a
776
+ // first line that might be the inherited brief and label it truth.
777
+ function parseReality(md, maxLen) {
778
+ const theory = (md.match(/\*\*Working theory:\*\*\s*(.*)/i) || [])[1]
779
+ const hasSchema = /\*\*(Working theory|Evidence|Differs from brief how):\*\*/i.test(md)
780
+ const theoryText = (theory || '').trim()
781
+ if (theoryText) {
782
+ const line = theoryText.length > maxLen ? theoryText.slice(0, maxLen - 1).trim() + '…' : theoryText
783
+ return { line, missing: '' }
784
+ }
785
+ if (hasSchema) return { line: '', missing: '' }
786
+ const prose = firstLine(md, maxLen)
787
+ if (prose) {
788
+ return {
789
+ line: '',
790
+ missing: 'UNREADABLE - reality.md does not match the schema (Working theory / Evidence / Differs from brief). Not showing the brief as truth.',
791
+ }
792
+ }
793
+ return { line: '', missing: '' }
794
+ }
795
+
796
+ function appendValueLedgerRow(eng, cells) {
797
+ ensureMemoryGit(eng)
798
+ const p = path.join(eng, 'delivery.md')
799
+ let md = readEng(eng, 'delivery.md')
800
+ if (!md) md = '# Delivery log\n\n## Value ledger\n\n'
801
+ const date = new Date().toISOString().slice(0, 10)
802
+ const cols = []
803
+ for (let i = 0; i < 7; i++) cols.push((cells[i] || '').replace(/\|/g, '\\|').trim() || ' ')
804
+ const row = `| ${date} | ${cols.join(' | ')} |`
805
+ const lines = md.split('\n')
806
+ let inLedger = false
807
+ let lastTableLine = -1
808
+ for (let i = 0; i < lines.length; i++) {
809
+ if (/^##\s+Value ledger\b/i.test(lines[i])) { inLedger = true; continue }
810
+ if (inLedger && /^##\s+/.test(lines[i])) break
811
+ if (inLedger && /^\|/.test(lines[i].trim())) lastTableLine = i
812
+ }
813
+ if (lastTableLine === -1) md = appendUnderSection(md, 'Value ledger', row)
814
+ else {
815
+ lines.splice(lastTableLine + 1, 0, row)
816
+ md = lines.join('\n')
817
+ }
818
+ withFileLock(p, () => { atomicWriteFile(p, md.endsWith('\n') ? md : md + '\n') })
819
+ recordLastWrite(eng, 'delivery.md', row)
820
+ commitMemory(eng, 'log delivery', { files: ['delivery.md'] })
821
+ }
822
+
823
+ function retireOpenRisks(eng, needle) {
824
+ const n = String(needle || '').toLowerCase()
825
+ if (!n) return 0
826
+ const p = path.join(eng, 'risks.md')
827
+ const md = readEng(eng, 'risks.md')
828
+ if (!md) return 0
829
+ const retired = []
830
+ const kept = []
831
+ let inRetired = false
832
+ for (const raw of md.split('\n')) {
833
+ const t = raw.trim()
834
+ if (/^#{1,6}\s+Retired\b/i.test(t)) { inRetired = true; kept.push(raw); continue }
835
+ if (!inRetired) {
836
+ const m = t.match(/^-\s*\[\d{4}-\d{2}-\d{2}\]\s*(?:\[@[^\]]+\]\s*)?(.*)$/)
837
+ if (m && m[1].toLowerCase().includes(n)) {
838
+ retired.push(raw)
839
+ continue
840
+ }
841
+ }
842
+ kept.push(raw)
843
+ }
844
+ if (!retired.length) return 0
845
+ let out = kept.join('\n')
846
+ if (!/^#{1,6}\s+Retired\b/im.test(out)) out = out.replace(/\n*$/, '\n\n## Retired\n')
847
+ const stamp = new Date().toISOString().slice(0, 10)
848
+ const block = retired.map(l => {
849
+ const body = l.trim().replace(/^-\s*/, '')
850
+ return `- [${stamp}] (retired) ${body}`
851
+ }).join('\n')
852
+ out = appendUnderSection(out, 'Retired', block)
853
+ withFileLock(p, () => { atomicWriteFile(p, out.endsWith('\n') ? out : out + '\n') })
854
+ return retired.length
855
+ }
856
+
772
857
  // Engagement age from the .fde/ directory's own birth time - hidden (not
773
858
  // fabricated as 0) on filesystems that do not report birthtime.
774
859
  function daysElapsed(eng) {
@@ -817,6 +902,7 @@ function parseMdTable(md) {
817
902
  function colIndex(headers, rx) { return headers.findIndex(h => rx.test(h)) }
818
903
 
819
904
  const {
905
+ personFromSignalText,
820
906
  signalSubjectKey,
821
907
  nextActionLine,
822
908
  computeSignals,
@@ -856,6 +942,8 @@ function parseSignalHistoryEntries(eng) {
856
942
  }
857
943
 
858
944
  function displayNameFromSignalText(text) {
945
+ const person = personFromSignalText(text)
946
+ if (person) return person
859
947
  const t = String(text).trim()
860
948
  const proper = t.match(/^([A-Z][a-z]+(?:\s+[A-Z][a-z]+)?)/)
861
949
  if (proper) return proper[1]
@@ -885,11 +973,11 @@ function extractStakeholders(eng) {
885
973
  const stance = stanceIdx !== -1 ? (cs[stanceIdx] || '').trim() : ''
886
974
  const note = notesIdx !== -1 ? (cs[notesIdx] || '').trim() : ''
887
975
  const words = name.replace(/\([^)]*\)/g, '').split(/\s+/).filter(w => w && !/^(dr|mr|mrs|ms)\.?$/i.test(w))
888
- const frag = (words[0] || '').replace(/[^a-z0-9]/gi, '')
976
+ const nameKey = signalSubjectKey(name)
889
977
  let signal = null, matchedDate = null
890
- if (frag.length >= 3) {
978
+ if (nameKey) {
891
979
  for (const h of history) {
892
- if (h.text.trim().toLowerCase().startsWith(frag.toLowerCase()) && (!matchedDate || h.date >= matchedDate)) {
980
+ if (signalSubjectKey(h.text) === nameKey && (!matchedDate || h.date >= matchedDate)) {
893
981
  signal = h.signal; matchedDate = h.date
894
982
  }
895
983
  }
@@ -1317,6 +1405,9 @@ function cmdLog(args) {
1317
1405
  if (!['red', 'amber', 'green'].includes(signal)) { console.error('usage: fde log contact <text> --signal red|amber|green'); process.exit(1) }
1318
1406
  args.splice(sigIdx, 2)
1319
1407
  }
1408
+ let retire = false
1409
+ const retireIdx = args.indexOf('--retire')
1410
+ if (retireIdx !== -1) { retire = true; args.splice(retireIdx, 1) }
1320
1411
  const type = args[0]; const text = args.slice(1).join(' ')
1321
1412
  const eng = resolveEngagement({ forWrite: true })
1322
1413
  if (!eng) { console.error('no engagement - run: fde resume --init <name>'); process.exit(2) }
@@ -1333,11 +1424,26 @@ function cmdLog(args) {
1333
1424
  return
1334
1425
  }
1335
1426
 
1336
- if (!LOG_FILES[type] || !text) { console.error(`usage: fde log <decision|risk|delivery|contact> <text> [--signal red|amber|green] [--force]\n fde log phase <${PHASES.join('|')}>\n fde log --undo`); process.exit(1) }
1427
+ if (!LOG_FILES[type] || !text) { console.error(`usage: fde log <decision|risk|delivery|contact> <text> [--signal red|amber|green] [--force]\n fde log risk --retire <text>\n fde log phase <${PHASES.join('|')}>\n fde log --undo`); process.exit(1) }
1337
1428
  if (signal && type !== 'contact') { console.error('--signal only applies to: fde log contact'); process.exit(1) }
1429
+ if (retire && type !== 'risk') { console.error('--retire only applies to: fde log risk'); process.exit(1) }
1338
1430
  const hit = findSecretHit(text)
1339
1431
  if (hit && !force) { refuseSecret('log text', hit); process.exit(1) }
1340
1432
  if (hit && force) console.error(`warning: logging possible ${hit} (--force)`)
1433
+ if (retire) {
1434
+ const n = retireOpenRisks(eng, text)
1435
+ if (!n) { console.error(`no open risk matched ${JSON.stringify(text)}`); process.exit(1) }
1436
+ const hash = commitMemory(eng, 'retire risk', { files: ['risks.md'] })
1437
+ console.log(`retired ${n} risk(s) → risks.md${hash ? ` @${hash}` : ''}`)
1438
+ return
1439
+ }
1440
+ if (type === 'delivery' && text.includes('|')) {
1441
+ const cells = text.split('|').map(s => s.trim())
1442
+ appendValueLedgerRow(eng, cells)
1443
+ const hash = memoryHead(eng)
1444
+ console.log(`logged → delivery.md (value ledger)${hash ? ` @${hash}` : ''}`)
1445
+ return
1446
+ }
1341
1447
  const date = new Date().toISOString().slice(0, 10)
1342
1448
  const entry = datedEntry(eng, date, text, signal || '')
1343
1449
  appendLogEntry(eng, type, entry)
@@ -1651,9 +1757,11 @@ function cmdDebrief(args) {
1651
1757
  input = readDebriefInput(args)
1652
1758
  }
1653
1759
 
1654
- if (smart) {
1760
+ if (smart) {
1655
1761
  const { proposePath, clean, blocks } = writeProposal(eng, smartProposeText(input))
1656
1762
  console.log('SMART PROPOSE (heuristic - review before apply; no new facts invented beyond line rewrites)\n')
1763
+ console.log('Prefix vocabulary (lines that route): decision: risk: delivery: contact: next:')
1764
+ console.log('Everything else → context.md. Keep the prefixes; the preview gate stays.\n')
1657
1765
  routeDebriefInput(eng, clean, { dry: true, force, sealed: blocks })
1658
1766
  if (!apply) {
1659
1767
  console.log(`\nproposal saved → ${proposePath}`)
@@ -2182,6 +2290,8 @@ function collectDoctorIssues(eng) {
2182
2290
  `${aliases.length} stakeholder identity cluster(s) (e.g. "${sample}") - same person under different names? consolidate in stakeholders.md`
2183
2291
  )
2184
2292
  }
2293
+ const reality = parseReality(readClean(eng, 'reality.md'), 220)
2294
+ if (reality.missing) issues.push(reality.missing)
2185
2295
  return issues
2186
2296
  }
2187
2297
 
@@ -2484,7 +2594,7 @@ function cmdRedact(args) {
2484
2594
  console.log('nothing changed')
2485
2595
  return
2486
2596
  }
2487
- const hash = commitMemory(eng, `redact ${term.slice(0, 40)}`, { files: touched })
2597
+ const hash = commitMemory(eng, `redact ${hits.length} line(s)`, { files: touched })
2488
2598
  console.log(`redacted ${hits.length} line(s) in ${touched.join(', ')}${hash ? ` @${hash}` : ''}`)
2489
2599
  console.log('rotate the real credential if this was a secret - history may still contain it')
2490
2600
  }
@@ -2585,6 +2695,15 @@ function cmdGarden(args) {
2585
2695
  sessionBlocks,
2586
2696
  })
2587
2697
  }
2698
+ const dirty = memoryDirtyManual(eng)
2699
+ if (dirty.length) {
2700
+ proposals.push({
2701
+ id: 'bless-manual',
2702
+ kind: 'apply',
2703
+ text: `Bless ${dirty.length} hand-written file(s) into the ledger: ${dirty.slice(0, 5).join(', ')}${dirty.length > 5 ? '…' : ''}`,
2704
+ files: dirty,
2705
+ })
2706
+ }
2588
2707
  if (!proposals.length) {
2589
2708
  console.log('\nNothing to tidy.')
2590
2709
  return
@@ -2613,6 +2732,12 @@ function cmdGarden(args) {
2613
2732
  }
2614
2733
  continue
2615
2734
  }
2735
+ if (p.id === 'bless-manual') {
2736
+ applied++
2737
+ for (const f of p.files) touched.add(f)
2738
+ console.log(`applied: bless ${p.files.join(', ')}`)
2739
+ continue
2740
+ }
2616
2741
  if (p.id !== 'archive-sessions') continue
2617
2742
  const cutDates = new Set(p.sessionBlocks.map(b => b.date))
2618
2743
  const keep = []
@@ -2744,6 +2869,10 @@ function cmdStatus(args) {
2744
2869
  }
2745
2870
  }
2746
2871
  if (!all) console.log('\n(current engagement only - pass --all for the full portfolio)')
2872
+ if (!all) {
2873
+ const current = resolveEngagement()
2874
+ if (current) for (const line of hygieneTriageLines(current)) console.log(line)
2875
+ }
2747
2876
  console.log('\ntrust: worst active [signal:x] across stakeholders (latest per person) - a green from B cannot clear an amber/red on A; keyword heuristic only when none exists.')
2748
2877
  }
2749
2878
 
@@ -2801,7 +2930,9 @@ function cmdDashboard(args) {
2801
2930
  // line instead of sharing one, a shorter cap just meant more sentences
2802
2931
  // cut off mid-thought for no reason.
2803
2932
  e.brief = firstLine(readClean(e.dir, 'brief.md'), 220)
2804
- e.reality = firstLine(readClean(e.dir, 'reality.md'), 220)
2933
+ const reality = parseReality(readClean(e.dir, 'reality.md'), 220)
2934
+ e.reality = reality.line
2935
+ e.realityMissing = reality.missing
2805
2936
  e.overlay = detectOverlay(e.dir)
2806
2937
  e.days = daysElapsed(e.dir)
2807
2938
  e.phaseLabel = phaseLabel(e.signals.phase)
@@ -2839,6 +2970,10 @@ function cmdDashboard(args) {
2839
2970
  }
2840
2971
  console.log(`fieldbook → ${outPath}`)
2841
2972
  console.log(`${engagements.length} engagement(s) rendered · ${counts.RED} red / ${counts.amber} amber / ${counts.green} green · 0 tokens (pure render)`)
2973
+ if (!all) {
2974
+ const current = resolveEngagement()
2975
+ if (current) for (const line of hygieneTriageLines(current)) console.log(line)
2976
+ }
2842
2977
  if (args.includes('--open')) {
2843
2978
  // arg-array form: the path is never interpolated into a shell string.
2844
2979
  const [bin, pre] = process.platform === 'darwin' ? ['open', []]
@@ -2985,7 +3120,8 @@ function cmdVault(args) {
2985
3120
  const ctx = readClean(e.dir, 'context.md')
2986
3121
  e.next = (sectionBody(ctx, 'Next action', { lastNonEmpty: true }).split('\n').find(l => l.trim()) || '').trim()
2987
3122
  e.brief = firstLine(readClean(e.dir, 'brief.md'), 400)
2988
- e.reality = firstLine(readClean(e.dir, 'reality.md'), 400)
3123
+ const reality = parseReality(readClean(e.dir, 'reality.md'), 400)
3124
+ e.reality = reality.line || reality.missing
2989
3125
  e.overlay = detectOverlay(e.dir)
2990
3126
  e.days = daysElapsed(e.dir)
2991
3127
  e.stakeholders = extractStakeholders(e.dir)
@@ -3186,19 +3322,20 @@ function printUsage() {
3186
3322
  fde resume --init <name> create + bind engagement for this workspace (rebind replaces)
3187
3323
  fde resume --bind show what this workspace is bound to, and what resolves
3188
3324
  fde triage TRIAGE block only (hooks / Cursor session entry)
3189
- fde log <type> <text> append decision|risk|delivery|contact (contact takes --signal red|amber|green; --force to allow secret-like text)
3325
+ fde log <type> <text> append decision|risk|delivery|contact (contact takes --signal red|amber|green; delivery "a|b|c" writes the value ledger; --force to allow secret-like text)
3326
+ fde log risk --retire move matching open-risk bullets to ## Retired
3190
3327
  fde log phase <phase> set engagement phase (land|discover|plan|ship|prove|close)
3191
3328
  fde log --undo remove the last CLI log/debrief entry from memory
3192
3329
  fde debrief [file] meeting notes → memory (prefixed lines; --dry-run; --force)
3193
- fde debrief --smart heuristic propose (prefix + light keywords); agent routes, CLI gates → --apply
3330
+ fde debrief --smart heuristic propose (prints decision:/risk:/delivery:/contact:/next:); --apply after confirm
3194
3331
  fde ingest stage … stage raw pull into <engagement>/.inbox/ (not .fde/)
3195
3332
  fde ingest list list staged inbox items
3196
3333
  fde ingest propose <id> smart-propose a staged item → .debrief-propose (confirm before apply)
3197
3334
  fde ingest apply same as: fde debrief --apply
3198
3335
  fde prep [label] grounded walk-in brief from existing .fde/ only
3199
- fde doctor lint engagement memory (stale signals, gaps)
3200
- fde redact <term> preview/remove lines containing a buried term (pass --apply to commit)
3201
- fde tidy [--apply] propose safe consolidations (contract: no new facts; git-reversible)
3336
+ fde doctor lint engagement memory (stale signals, gaps). status/dashboard/resume print the same issues
3337
+ fde redact <term> preview/remove lines containing a buried term (pass --apply to commit; subject never repeats the term)
3338
+ fde tidy [--apply] propose consolidations; blesses hand-written dirty files when you apply
3202
3339
  fde owner [set email] who keeps this engagement record
3203
3340
  fde receipts <term> "what did we agree?" with dates
3204
3341
  fde status [--all] value ledger, then trust (pass --all for full portfolio)
package/bin/lib/render.js CHANGED
@@ -467,10 +467,10 @@ ${e.lastSession ? `<div class="fb-now-session">${inlineMd(e.lastSession)}</div>`
467
467
  // needed vs what's actually true. Squeezed onto one truncated line, that
468
468
  // contrast disappears. Each gets its own line, its own room to finish a
469
469
  // thought, not a race to fit before an ellipsis.
470
- const whyBlock = (e.brief || e.reality) ? `<div class="fb-block">
470
+ const whyBlock = (e.brief || e.reality || e.realityMissing) ? `<div class="fb-block">
471
471
  <div class="fb-sec">Why</div>
472
472
  ${e.brief ? `<p class="fb-why"><span class="t-faint">What they asked for:</span> ${inlineMd(e.brief)}</p>` : ''}
473
- ${e.reality ? `<p class="fb-why fb-why-reality"><span class="fb-accent-label">What's actually true:</span> ${inlineMd(e.reality)}</p>` : ''}
473
+ ${e.realityMissing ? `<p class="fb-why fb-why-missing"><span class="fb-accent-label">What's actually true:</span> ${escapeHtml(e.realityMissing)}</p>` : e.reality ? `<p class="fb-why fb-why-reality"><span class="fb-accent-label">What's actually true:</span> ${inlineMd(e.reality)}</p>` : ''}
474
474
  </div>` : ''
475
475
 
476
476
  // Vitals: a fixed field-facing gut-check panel, not a Movement block that
package/bin/lib/trust.js CHANGED
@@ -48,12 +48,46 @@ function createTrustApi(deps) {
48
48
  return { ok: true, warn: '' }
49
49
  }
50
50
 
51
- // Subject key for a signal-history line - first real name word (same spirit as
52
- // extractStakeholders). A green about Randy must not clear an amber about Denise.
51
+ // Event labels are not people. "INCIDENT: Marcus escalated" must key on
52
+ // Marcus, or a recovered engagement stays RED in front of the sponsor.
53
+ const SIGNAL_EVENT_KEYS = new Set([
54
+ 'incident', 'recovery', 'alert', 'update', 'note', 'status', 'escalation',
55
+ 'blocker', 'outage', 'fire', 'issue', 'sev', 'sev1', 'sev2', 'p1', 'p2', 'p3',
56
+ 'resolved', 'risk', 'decision', 'delivery', 'contact',
57
+ ])
58
+
59
+ function personFromSignalText(text) {
60
+ let cleaned = String(text || '')
61
+ .replace(/\[@[^\]]+\]/g, '')
62
+ .replace(/\([^)]*\)/g, '')
63
+ .replace(/\[signal:[^\]]+\]/gi, '')
64
+ .replace(/\[\d{4}-\d{2}-\d{2}\]/g, '')
65
+ .replace(/^([A-Z]{2,}[A-Z0-9_-]*):?\s+/, '')
66
+ .trim()
67
+ const names = cleaned.match(/\b[A-Z][a-z]{1,20}(?:\s+[A-Z][a-z]{1,20})?\b/g) || []
68
+ for (const name of names) {
69
+ const first = name.split(/\s+/)[0].toLowerCase()
70
+ if (SIGNAL_EVENT_KEYS.has(first)) continue
71
+ return name
72
+ }
73
+ return ''
74
+ }
75
+
76
+ // Subject key for a signal-history line. Prefer a proper name in the bullet.
77
+ // A green about Randy must not clear an amber about Denise.
53
78
  // Strip author tags [@email-local] so attribution never becomes the subject key.
54
79
  function signalSubjectKey(text) {
80
+ const person = personFromSignalText(text)
81
+ if (person) {
82
+ const frag = person.split(/\s+/)[0].replace(/[^a-z0-9]/gi, '').toLowerCase()
83
+ if (frag.length >= 3) return frag
84
+ }
55
85
  const cleaned = String(text).replace(/\[@[^\]]+\]/g, '').replace(/\([^)]*\)/g, '')
56
- const words = cleaned.split(/\s+/).filter(w => w && !/^(dr|mr|mrs|ms)\.?$/i.test(w))
86
+ .replace(/^([A-Z]{2,}[A-Z0-9_-]*):?\s+/, '')
87
+ const words = cleaned.split(/\s+/).filter(w => {
88
+ const n = w.replace(/[^a-z0-9]/gi, '').toLowerCase()
89
+ return n.length >= 3 && !SIGNAL_EVENT_KEYS.has(n) && !/^(dr|mr|mrs|ms)$/i.test(w)
90
+ })
57
91
  const frag = (words[0] || '').replace(/[^a-z0-9]/gi, '').toLowerCase()
58
92
  return frag.length >= 3 ? frag : ('anon:' + cleaned.slice(0, 48).toLowerCase())
59
93
  }
@@ -196,6 +230,7 @@ function createTrustApi(deps) {
196
230
 
197
231
  return {
198
232
  stakeholdersMemoryHealth,
233
+ personFromSignalText,
199
234
  signalSubjectKey,
200
235
  parsePhase,
201
236
  countOpenRisks,
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fdeops-ingest-mcp",
3
- "version": "3.16.0",
3
+ "version": "3.17.0",
4
4
  "private": true,
5
5
  "description": "Thin stdio MCP sink for FDEOps ingest (stage → propose → apply). Zero runtime dependencies.",
6
6
  "bin": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fdeops",
3
- "version": "3.16.0",
3
+ "version": "3.17.0",
4
4
  "description": "Forward deployed engineering skills for AI coding agents. Your agent forgets the client every morning - the sponsor, the promise, who signed off. FDEOps keeps that as dated markdown on your laptop: one @fde skill, a deterministic local CLI, 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/plugin.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
3
3
  "name": "fdeops",
4
- "version": "3.16.0",
4
+ "version": "3.17.0",
5
5
  "description": "Forward deployed engineering skills for AI coding agents: per-client memory in local .fde/ files, one @fde skill. Local-only, no network.",
6
6
  "author": {
7
7
  "name": "Subash Natarajan",
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: fde
3
- description: Keeps the engagement record for client work. Use when they name a client or stakeholder. Use when they debrief a meeting or paste notes. Use when they ask what was agreed. Use when they run a POC, slice a feature on the client's codebase, prove it on their staging, or need evals before a model acts. Use when they prep a readout, when trust shifts, or they say @fde. Route and run the local fde CLI (or npx --yes fdeops). Never ask them to type commands. Not for ordinary code edits in an unbound repo.
3
+ description: Keeps the engagement record for client work. Use when they name a client or stakeholder. Use when they debrief a meeting or paste notes. Use when they ask what was agreed. Use when they run a POC, change the client's codebase, prove it on their staging, go live, or need evals before a model acts. Use when they prep a readout, when trust shifts, or they say @fde. Route and run the local fde CLI (or npx --yes fdeops). Never ask them to type commands. Not for ordinary code edits in an unbound repo.
4
4
  ---
5
5
 
6
6
  # @fde
@@ -17,7 +17,7 @@ The **engagement record** for one client, from first meeting to signed outcome.
17
17
 
18
18
  ## When NOT to use
19
19
 
20
- A one-line typo or compile error in a file that will not ship. On a bound client: stay here for POC, slice, characterisation, eval, go-live, rollback, and acceptance.
20
+ A one-line typo or compile error in a file that will not ship. On a bound client: stay here for POC, characterisation, the change on their repo, eval, go-live, rollback, and acceptance.
21
21
 
22
22
  ## Use these first
23
23
 
@@ -32,15 +32,15 @@ After a meeting: `fde debrief --smart` → confirm → `--apply`. Walk-in: `fde
32
32
 
33
33
  ## Ground loop
34
34
 
35
- On someone else's site the work is not "write code, remember later." Every slice stays on `@fde`:
35
+ On someone else's site the work is not "write code, remember later." Every change on a bound client stays on `@fde`:
36
36
 
37
37
  1. **Name it** in `decisions.md` (plan) or kill it in a day (poc).
38
- 2. **Characterise their code** before you change it (thin-slices). Their tests, their runner.
39
- 3. **Prove it where they live.** Staging they operate, a screen the signer in `success.md` can reject.
40
- 4. **If a model judges:** `evals.md` Verdict SHIP before the slice is done (eval-pack).
41
- 5. **Log delivery.** Outcome is promised → measured → accepted, not a green CI.
38
+ 2. **Characterise their code** before you change it. Brownfield: their tests, their runner. Greenfield: the empty tree, first path they can click.
39
+ 3. **Prove it on their staging.** Staging they operate, a screen the signer in `success.md` can reject.
40
+ 4. **If a model judges:** `evals.md` Verdict SHIP before that change is done (eval-pack).
41
+ 5. **Log delivery.** Outcome is promised → measured → accepted, not a green CI. Then go live with a rollback you have run (`ship`).
42
42
 
43
- A throwaway file can skip the loop. A client slice cannot.
43
+ A throwaway file can skip the loop. Bound client work cannot.
44
44
 
45
45
  ## Human surface vs agent plumbing
46
46
 
@@ -147,10 +147,9 @@ Read **one** reference and follow it. Do not improvise from memory.
147
147
 
148
148
  | You hear | Skill | Reference |
149
149
  |----------|-------|-----------|
150
- | Large feature, need visible progress every 2-3 days, slice it, characterise their tests, POC follow-through | thin-slices | `references/thin-slices.md` |
151
150
  | What could go wrong, touching shared infrastructure, need to assess impact | what-breaks | `references/what-breaks.md` |
152
151
  | Production down, urgent - OR stakeholder gone quiet, trust slipping | rescue | `references/rescue.md` |
153
- | Ready to deploy, going live, pre-flight check | ship | `references/ship.md` |
152
+ | Start building, update their checkout, first module, visible progress, their tests, POC follow-through, ready to deploy, going live, pre-flight | ship | `references/ship.md` |
154
153
  | Review this change, is it safe, does it match what we agreed | review | `references/review.md` |
155
154
  | Diff grew / scope creep in the PR / "did we only build what we said" / KEEP JUSTIFY SPLIT DROP | review (+ ship if going live) | `references/review.md` Stage 1 · `references/ship.md` Intent vs diff |
156
155
  | Wrap the session / share the thinking / catch teammates up / before I open the PR | (memory contract - session digest) | SKILL.md **On exit** - write TL;DR + decisions/why into `.fde/`; no transcript sync |
@@ -196,7 +195,7 @@ Ready to build with no `terrain.md` / plan: discover or plan first. Takeover wit
196
195
  ## Principles
197
196
 
198
197
  - Never ask the FDE to pick a phase. That's your job.
199
- - Ground loop on a bound client: name → characterise → prove where they live → log. Do not hand the slice to a generic coding pack.
198
+ - Ground loop on a bound client: name → characterise → prove on their staging → go live → log. Do not hand their repo to a generic coding pack.
200
199
  - Read `context.md` before speaking. One sharp question - never a barrage.
201
200
  - Never invent people, meetings, or numbers - `unknown - ask:` beats a polished lie.
202
201
  - Every phase ends with its artifact written. No artifact, no "done."
@@ -31,15 +31,28 @@ Q: <one question that changes where you dig>
31
31
  GUESS: <your answer, so they can correct it>
32
32
  ```
33
33
 
34
- Stop when you can write the decision sentence under **Frame the decision first**. If a name, quote, or metric is still missing, write `unknown - ask:` - never invent ops folklore to make the map look complete.
34
+ Stop when you can write the four lines under **Frame the decision first**. If a name, quote, or metric is still missing, write `unknown - ask:` - never invent ops folklore to make the map look complete.
35
35
 
36
36
  ## Frame the decision first
37
37
 
38
- Before any scanning, write one sentence at the top of your working notes:
38
+ Same SCQA spine as readout (`S → C → Q → A`), aimed at the floor, not a deck. Write it **before** any scan. Confirm with the FDE, then dig.
39
39
 
40
- > "What will the sponsor do differently because of this discovery?"
40
+ | Line | What it is | Fail if |
41
+ |------|------------|---------|
42
+ | **Situation** | What they already treat as true - the workaround, the sheet, the owner who left | It could be copied from the RFP |
43
+ | **Complication** | What broke, so they cannot stay here | No tension, or three problems joined by "and" |
44
+ | **Question** | One decision the named signer must make | It smuggles the solution ("how do we add alerting") |
45
+ | **Answer-space** | Shape of a satisfying answer: confirm brief / descope / rescope / pause | A novel, or "insights" |
41
46
 
42
- If you can't name the decision this informs (descope? rescope? pick use case A over B? touch module X first?), you're collecting trivia, not discovering. Every output of this phase is aimed at that decision.
47
+ Tests on **Question** - rewrite until all five hold:
48
+
49
+ 1. **Decision-shaped** - answering it changes what someone does.
50
+ 2. **Single** - one thing, not three.
51
+ 3. **Scoped** - who, where, by when.
52
+ 4. **Answerable** - evidence could settle it in this engagement.
53
+ 5. **Neutral** - does not assume the fix.
54
+
55
+ Cannot write the Question → keep interrogating. Do not `fde scan`. Every later output of this phase aims at that Question. Sub-questions go to the operating map or `assumptions.md`, not into the Question.
43
56
 
44
57
  ## Method - part 1: the codebase (you do this work)
45
58
 
@@ -147,13 +160,18 @@ Score every candidate use case before anything gets prototyped:
147
160
 
148
161
  ## Artifact (this IS the memory - write it as you work)
149
162
 
150
- **`reality.md`** - the readout the FDE takes into the sponsor meeting:
163
+ **`reality.md`** - the readout the FDE takes into the sponsor meeting. Keep the three schema lines the dashboard reads (`Working theory` / `Evidence` / `Differs from brief how`). Then the decision frame:
164
+
151
165
  ```markdown
152
166
  # Reality (actual problem)
153
- **Decision this informs:** <one line>
154
- **Confirmed:** <real problem> (evidence: <workaround/data/quote, source, day>)
155
- **Stated brief was wrong/right because:** <delta, with evidence>
156
- **Implication for build:** <thin-slice direction>
167
+ **Working theory:** <the real problem, one sentence>
168
+ **Evidence:** <workaround/data/quote, source, day>
169
+ **Differs from brief how:** <delta, with evidence>
170
+ **Situation:** <what the floor already treats as true>
171
+ **Complication:** <what forces a decision now>
172
+ **Question:** <one decision-shaped sentence>
173
+ **Answer-space:** confirm brief / descope / rescope / pause - and what a yes looks like
174
+ **Implication for build:** <first small PR>
157
175
  **Validated with:** <who, when>
158
176
  ```
159
177
 
@@ -180,11 +198,11 @@ Every line carries its evidence. `(churn: 47/90d)` `(ops lead, Day 5)` `(stated,
180
198
  ## Checkpoint (before any build)
181
199
 
182
200
  Present to the FDE, five things, one paragraph each - no padding:
183
- 1. The real problem, with the two strongest pieces of evidence.
201
+ 1. The Question, then the real problem, with the two strongest pieces of evidence.
184
202
  2. The top 3 risk areas of the codebase, one line of why each.
185
203
  3. What must not be touched without characterisation tests.
186
204
  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.
205
+ 5. The Answer-space: confirm brief / descope / rescope - and the decision it puts in front of the sponsor.
188
206
 
189
207
  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.
190
208
 
@@ -198,13 +216,14 @@ Acme's brief blamed missing monitoring. Discovery goes to the workaround first.
198
216
 
199
217
  `git log` shows the reconciliation module at 47 commits/90d with no tests, all from one author who left in February. Marco (ops lead) turns out to keep a spreadsheet: every morning he re-runs the job manually and eyeballs the totals - a habit nobody mentioned because to him it is just the job. That spreadsheet is the system of record when the job fails, which is the actual finding.
200
218
 
201
- `reality.md`: **Confirmed:** the job has no owner, and the manual re-run masks failures for a day (evidence: Marco's sheet, Day 5; two silent failures since March, finance escalation Mar 14). **Stated brief was wrong because:** alerting existed last year and was disabled - adding it again without an owner reproduces the same outcome. `terrain.md` gets the hotspot row and an operating-map row: `job fails silently → Marco notices next morning → re-runs by hand → spreadsheet is truth → LOAD-BEARING (Marco, Day 5)`.
219
+ `reality.md` keeps the schema, then the frame. **Working theory:** the job has no owner, and the manual re-run masks failures for a day. **Evidence:** Marco's sheet, Day 5; two silent failures since March, finance escalation Mar 14. **Differs from brief how:** alerting existed last year and was disabled - adding it again without an owner reproduces the same outcome. **Situation:** Marco re-runs the job every morning and the spreadsheet is truth when it fails. **Complication:** two silent failures since March already hit finance, and the author of the module left in February. **Question:** should Priya fund a named owner on the failure path, or fund alerting and accept the same miss in six months? **Answer-space:** fund ownership / fund alerting-as-theatre / pause until she names who acks. `terrain.md` gets the hotspot row and an operating-map row: `job fails silently → Marco notices next morning → re-runs by hand → spreadsheet is truth → LOAD-BEARING (Marco, Day 5)`.
202
220
 
203
- Checkpoint to the FDE names the sponsor decision this creates: fund ownership, or fund alerting and accept the same failure in six months.
221
+ Checkpoint to the FDE leads with that Question, not a tour of the repo.
204
222
 
205
223
  ## Principles
206
224
 
207
225
  - The brief is a hypothesis until evidence confirms it.
226
+ - No scan until the Question is one decision the signer must make.
208
227
  - The workaround is more honest than the requirements document.
209
228
  - Churn data + the human's "don't touch that" pointing at the same module = the map is true.
210
229
  - Never modify code before the terrain map exists.
@@ -1,6 +1,6 @@
1
1
  # eval-pack - prove the system before it acts
2
2
 
3
- **Enter when:** the work touches AI/LLM/agents/RAG, or they need to POC a model, or ship/close is blocked because there is no evidence the non-deterministic path is safe. Activate alongside `ai.md`, `poc`, `thin-slices`, or `ship` - not instead of them.
3
+ **Enter when:** the work touches AI/LLM/agents/RAG, or they need to POC a model, or ship/close is blocked because there is no evidence the non-deterministic path is safe. Activate alongside `ai.md`, `poc`, or `ship` - not instead of them.
4
4
 
5
5
  **Read first:** `trust-profile.md` (AI policy + HITL), `terrain.md` (operating map), `delivery.md`. Create or extend `evals.md`.
6
6
 
@@ -22,13 +22,13 @@ 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` 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 test-assumptions / discover before sequencing work.
25
+ **0. Lock scope first.** Read `success.md`, `assumptions.md`, and the **Question** on `reality.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 test-assumptions / discover before sequencing work. If `reality.md` has no Question, stop and finish discover - you are sequencing trivia.
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
 
29
29
  **2. Front-load the fragile.** Check `terrain.md` hotspots. Risky modules go early - fail fast, not in week three.
30
30
 
31
- **3. Slice vertically.** Each task delivers something visible and testable end to end ("user submits form, sees it saved"), never a horizontal layer ("build the database layer").
31
+ **3. One user action per change.** Each task delivers something visible and testable ("user submits form, sees it saved"), never a layer ("build the database layer"). See `ship`.
32
32
 
33
33
  **4. Size to 30-90 minutes, PR-sized.** Longer = two tasks. Each task implementable, testable, reviewable without a thousand-line diff.
34
34
 
@@ -36,7 +36,7 @@ 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 pick-three).
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 PRs (same discipline as pick-three).
40
40
 
41
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.
42
42
 
@@ -54,6 +54,7 @@ Delivers: <what someone can see/test>
54
54
  Accepts: <happy path> / <unhappy path>
55
55
  Touches: <files/systems - blast radius declared upfront>
56
56
  Risk: <what could go wrong + fallback>
57
+ Kill if: <the observation that voids this slice - copy from assumptions.md How we test, or the check that means stop>
57
58
  Verify: <specific check>
58
59
  Value promised: <business unit change this slice claims>
59
60
 
@@ -72,7 +73,7 @@ Value promised: <business unit change this slice claims>
72
73
  No kill list → not a finished plan. Reopen with the FDE until the deferrals are written.
73
74
  ## Checkpoint
74
75
 
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?"
76
+ Walk the FDE through: sequence + why this order, where the fragile work sits, where the touchpoints land, the acceptance gate and **Kill if** 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?" Third: "What observation stops task 1 this week?"
76
77
 
77
78
  ## Method - estimation (when the sponsor asks "how long, how much?")
78
79
 
@@ -96,6 +97,7 @@ Every FDE gets asked this in week one. The honest answer is a range, not a numbe
96
97
  - Add 30% buffer for integration work (it always takes longer).
97
98
  - Add 50% buffer for AI/ML work (eval cycles are unpredictable).
98
99
  - Name assumptions explicitly: "assumes API docs are accurate", "assumes staging environment exists."
100
+ - Each named assumption needs a **kill observation**: the result that voids the estimate. Copy it from `assumptions.md` → How we test. No kill observation = it is not an assumption, it is hope.
99
101
  - Revisit estimates every 2 weeks. An estimate that never updates is fiction.
100
102
 
101
103
  Write estimates to `decisions.md` under `## Sizing`. Include the assumptions - when they break, the estimate changes and the FDE has evidence for the conversation.
@@ -135,7 +137,7 @@ Never quietly update tasks. Name the reset: update `reality.md` and `success.md`
135
137
 
136
138
  Acme, after discover: the reconciliation job is unowned, Marco's spreadsheet is the real fallback.
137
139
 
138
- **Now** is three tasks, not eight. Task 1 is *failures reach a named human* - delivers a page to a rota, accepts "kill the job mid-run → the on-call is paged within 15 min", touches the job wrapper and the alert config, rollback is re-disable the route, verify by killing it in staging. Value promised: `risk-mitigation - a silent failure becomes a 15-minute one`.
140
+ **Now** is three tasks, not eight. Task 1 is *failures reach a named human* - delivers a page to a rota, accepts "kill the job mid-run → the on-call is paged within 15 min", touches the job wrapper and the alert config, rollback is re-disable the route, **Kill if:** a real failure page is acked by nobody on the rota (the *finance would act* assumption, DISPROVED if Marco is the only name that answers), verify by killing it in staging. Value promised: `risk-mitigation - a silent failure becomes a 15-minute one`.
139
141
 
140
142
  The kill list in `decisions.md` is where the plan earns its keep: the rewrite of the reconciliation service that Tom keeps proposing goes there - *deferred, the failure mode is ownership not architecture (Priya accepted, Jun 12)* - along with the finance dashboard finance asked for directly. Both stay visible so the same argument is not re-litigated in week 4 without a receipt.
141
143
 
@@ -148,5 +150,6 @@ First visible slice goes to Marco, not Priya: he is the one whose morning change
148
150
  - Every 2-3 tasks, a stakeholder touchpoint. Trust decays without visibility.
149
151
  - No written acceptance criteria, no build.
150
152
  - No kill list, no finished plan.
151
- - Estimates are ranges, not promises. Name the assumptions.
153
+ - No **Kill if** on a Now PR, that PR is hope.
154
+ - Estimates are ranges, not promises. Name the assumptions and the observation that voids them.
152
155
  - Migrations: leaf nodes first, core last. Rollback before cutover.
@@ -1,10 +1,102 @@
1
- # ship - production without surprises
1
+ # ship - on their site, then live
2
2
 
3
- **Enter when:** a slice is built, reviewed, and ready to deploy.
3
+ **Enter when:** you are writing or updating on their codebase, they need to see something real, or you are going live.
4
4
 
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.
5
+ **Read first:** `context.md`, `decisions.md`, `delivery.md`, `success.md`. Load `terrain.md` before you touch their code. Load `trust-profile.md` if the deploy touches regulated data or needs an approval chain. Load `evals.md` when the work touches AI/ML/LLM/RAG/agents.
6
6
 
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.
7
+ Do not ask them to pick a mode. Name where you are, then start at the matching section:
8
+
9
+ - Nothing on their staging yet → **one change they can see**
10
+ - On staging, the signer in `success.md` can reject it → **go-live**
11
+ - Prod is the question → **go-live**. Do not start a second change.
12
+
13
+ If going live, opening question: **has anyone actually *run* the rollback, or is it still a slide?** If only planned, that's today's work - say so plainly.
14
+
15
+ A same-day throwaway that kills an assumption is `poc`. This method is the real change on a repo they will own, then production.
16
+
17
+ ## Field (name it once, then the same loop)
18
+
19
+ | | Brownfield | Greenfield |
20
+ |--|------------|------------|
21
+ | What you touch | Code they already run | A new path or empty tree they will own |
22
+ | First move | Characterise their tests, their runner, the workaround in `terrain.md` | First path a user can click. Not the whole product. |
23
+ | Proof | Their staging, a screen they already use | Their staging, or the environment they will operate. Local demo is not delivery. |
24
+ | Undo | Revert this change on its own | Same. If you cannot undo it, the design is coupled. |
25
+
26
+ Skip POC only when the killer assumption already lives in the repo (typical brownfield). If the bet is unproven, `poc` first.
27
+
28
+ ## Method - one change they can see
29
+
30
+ One change = one thing a user can do, with a test, that you can revert on its own. Not "all the APIs, then all the UI." Not a 2,000-line dump. A PR is how this often lands. It is not the job. The job is the change they can see.
31
+
32
+ ```
33
+ BAD (layers):
34
+ 1: all database models
35
+ 2: all API endpoints
36
+ 3: all UI components
37
+ 4: wire everything together (and pray)
38
+
39
+ GOOD (one user action each):
40
+ 1: User can create a payment (schema + endpoint + minimal UI) - testable
41
+ 2: User can view payment status (query + endpoint + UI) - testable
42
+ 3: Payment retry on failure (logic + endpoint + UI feedback) - testable
43
+ 4: Admin can void a payment (auth + logic + UI) - testable
44
+ ```
45
+
46
+ Each change is independently revertible.
47
+
48
+ **Before you start this change:**
49
+
50
+ - [ ] It is in `decisions.md` with acceptance criteria (happy + unhappy path)
51
+ - [ ] Blast radius declared: which files, which systems, which users affected
52
+ - [ ] Rollback named: revert this change, or something more specific
53
+ - [ ] No dependency on an unmerged change (if dependent, state it and land in order)
54
+ - [ ] `Kill if` is written - the observation that stops this change
55
+
56
+ **The loop.** In this order:
57
+
58
+ ```
59
+ Read existing code in the area (search before creating)
60
+ → Characterise what is already there (their tests, their runner; greenfield: the empty tree)
61
+ → Implement the smallest path that works
62
+ → Prove it on their staging (below)
63
+ → Cleanup pass (dedupe, simplify - behaviour unchanged)
64
+ → Self-review against acceptance criteria
65
+ → Commit with a message the client's team can read
66
+ → Update decisions.md + delivery.md
67
+ ```
68
+
69
+ **Prove it on their staging.** A green check on your laptop is not delivery.
70
+
71
+ - Run **their** test command, typecheck, or smallest proving path. Write the command and the result in `delivery.md`.
72
+ - If the signer in `success.md` cannot reject this on a screen they already use, it is not proven.
73
+ - Staging they operate beats a local demo. If you have no staging: `unknown - ask:` who owns an environment, then stop pretending it shipped.
74
+ - Model in the path: `eval-pack` until `evals.md` says SHIP. Do not skip because "it looked right in chat."
75
+
76
+ The proof is whatever this client already believes, plus one new receipt they can replay.
77
+
78
+ **Size.** Each change targets:
79
+
80
+ | Metric | Target | Why |
81
+ |--------|--------|-----|
82
+ | Lines changed | 100-300 | Reviewable in one sitting |
83
+ | Time to implement | 30-90 minutes | Testable before context decays |
84
+ | Files touched | 1-5 | Blast radius stays containable |
85
+ | Tests added | ≥1 per new behaviour | Proves this change; guards against regression |
86
+
87
+ Larger than 300 lines → split first. "It's all connected" means the design needs work, not a bigger dump.
88
+
89
+ **Show it.** Every 2-3 changes, something the customer can see: an endpoint they can hit, a UI they can click, a metric that moved, a risk that was retired. Technical progress invisible to stakeholders is trust decay. `delivery.md` gets updated after every visible change.
90
+
91
+ **The scope trap.** Mid-change discoveries ("this module also needs updating," "I should refactor this while I'm here"):
92
+
93
+ - If it's in `decisions.md`: do it as a separate change.
94
+ - If it's NOT in `decisions.md`: log it as a scope receipt (see `hold-scope.md`), don't touch it.
95
+ - Ugly code outside this change stays ugly. That is discipline, not laziness.
96
+
97
+ After each change: tests pass (state the command and result), acceptance criteria met, blast radius as declared, `Kill if` still false. After every 2-3: what did they see, and what's their signal? Then, when the signer can reject it on their staging, go-live below.
98
+
99
+ ---
8
100
 
9
101
  ## Deployment readiness gate (confirm the target before building the runway)
10
102
 
@@ -24,7 +116,7 @@ Before scoring readiness, confirm WHERE this is going. State it in 2-3 lines - b
24
116
  | **Compliance** | Region constraints? Data residency? Encryption requirements? CAB/change window? | |
25
117
  | **Infra-as-code** | Terraform/Pulumi/CDK/manual? State file location? | |
26
118
 
27
- **If anything is blank:** ask now. Discovering deployment constraints AFTER build is where timelines slip. If the client hasn't defined these yet, that's a conversation before you write the runbook - not after.
119
+ **If anything is blank:** ask now. Discovering deployment constraints after the change is where timelines slip. If the client hasn't defined these yet, that's a conversation before you write the runbook - not after.
28
120
 
29
121
  Write confirmed deployment context to `delivery.md` under a `## Deployment target` section.
30
122
 
@@ -46,9 +138,9 @@ Score each dimension green/amber/red. This is the gate, not a suggestion:
46
138
 
47
139
  | Dimension | Green | Amber | Red |
48
140
  |-----------|-------|-------|-----|
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 |
141
+ | **Value bucket** | `success.md` names primary bucket (`cost-save` \| `risk-mitigation` \| `revenue-uplift`) and a baseline→target metric; this change's value-ledger row has **Bucket** + **Promised** | Bucket named; **Measured** still `pending` with a pulse date | No bucket, or Promised empty / ticket-theater only |
142
+ | **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 change |
143
+ | **Eval receipt** | **n/a** (no AI on this change) **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
144
  | **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
145
 
54
146
  **Any RED = stop. Do not deploy. Fix the red dimension first.**
@@ -65,7 +157,7 @@ Write the readiness score (including value + receipts) to `delivery.md` before d
65
157
 
66
158
  ## Intent vs diff (before pre-blast)
67
159
 
68
- Ship the change you intended - not the drift that snuck in. Run this on the deploy branch against the **one-line intent** from `decisions.md` / `success.md` (the slice you said you were building).
160
+ Ship the change you intended - not the drift that snuck in. Run this on the deploy branch against the **one-line intent** from `decisions.md` / `success.md` (the change you said you were building).
69
161
 
70
162
  ```bash
71
163
  git diff <base>...HEAD --stat
@@ -78,7 +170,7 @@ Score every touched path (or logical hunk):
78
170
  |---------------|---------|------|
79
171
  | | **KEEP** | Directly required for the stated intent |
80
172
  | | **JUSTIFY** | Adjacent but load-bearing - one sentence why it must ship *now*, or split |
81
- | | **SPLIT** | Real work, wrong PR - park in `decisions.md` kill/Next; do not deploy with this slice |
173
+ | | **SPLIT** | Real work, wrong change - park in `decisions.md` kill/Next; do not deploy with this one |
82
174
  | | **DROP** | Noise (format-only, drive-by rename, unrelated tidy) - revert before ship |
83
175
 
84
176
  **Any SPLIT or DROP still in the tree = fix-first.** JUSTIFY without a written sentence = treat as SPLIT. Log a one-line receipt in `delivery.md`: `intent vs diff: KEEP n · JUSTIFY n · SPLIT n · DROP n - <intent>`.
@@ -164,12 +256,12 @@ Never skip a step. The sponsor always wants to skip from pilot to standard - tha
164
256
 
165
257
  ## Method - progressive adoption (built it, now people need to use it)
166
258
 
167
- Adoption isn't a handoff-stage problem - it starts during build. Software that launches to silence is software that gets decommissioned.
259
+ Adoption isn't a handoff-stage problem - it starts while you are still writing the change. Software that launches to silence is software that gets decommissioned.
168
260
 
169
- **During build:**
261
+ **During the change:**
170
262
  - **Feature flags from day one.** Every new capability behind a flag. Ship to 5% of users first. Watch behavior before opening to 100%.
171
263
  - **Feedback loops built in.** A thumbs-up/down, a "was this helpful?", a usage counter. Instrument adoption, don't assume it.
172
- - **Resistance signals.** Watch for: workaround creation (they built a spreadsheet instead of using the tool), drop-off after day 3 (onboarding fails), vocal detractors (one influential skeptic can kill adoption). Address these DURING build, not after launch.
264
+ - **Resistance signals.** Watch for: workaround creation (they built a spreadsheet instead of using the tool), drop-off after day 3 (onboarding fails), vocal detractors (one influential skeptic can kill adoption). Address these before launch, not after.
173
265
 
174
266
  **At launch:**
175
267
  - **Champion network.** Identify 2-3 power users per team who adopt early. Support them intensely - they become your multiplier.
@@ -180,24 +272,35 @@ Adoption isn't a handoff-stage problem - it starts during build. Software that l
180
272
 
181
273
  ## Artifact
182
274
 
183
- **`delivery.md`** - deployment record: what shipped, when, what it delivers in business terms, rollback procedure, pulse definition, **scale-readiness assessment, and adoption metrics**. Written for whoever inherits the system.
275
+ **`decisions.md`** - each change: what was implemented, what was tested, what was deferred, `Kill if`.
276
+
277
+ **`delivery.md`** - each visible change in business language; then the deployment record: what shipped, when, rollback procedure, pulse definition, **scale-readiness assessment, and adoption metrics**. Written for whoever inherits the system.
184
278
 
185
279
  ## Checkpoint
186
280
 
187
- 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**, **intent vs diff clean** (no unresolved SPLIT/DROP). Missing any of those → not green. For enterprise-scale: scale-readiness gate passed before broad rollout.
281
+ After each change: tests pass, acceptance criteria met, blast radius as declared, `Kill if` still false, proven on staging they operate.
282
+
283
+ Before 100% live: canary clean, business metric verified, pulse written into `delivery.md`. Also green: value bucket named, audit receipt dated, eval receipt **n/a or pass**, **intent vs diff clean** (no unresolved SPLIT/DROP). Missing any of those → not green. For enterprise-scale: scale-readiness gate passed before broad rollout.
188
284
 
189
285
  ## Worked example
190
286
 
191
- Acme, shipping the failure-routing slice into a payments environment on a Thursday.
287
+ Acme, brownfield. Plan Now has three changes, not "the payments rewrite."
192
288
 
193
- Readiness scoring catches two things the diff does not. The audit receipt is missing: the operating map says Marco's manual re-run is the fallback, and nobody has checked whether the new page fires *before* his morning run or after - if after, the alert changes nothing. That gets walked and cited before deploy. Second, the intent-vs-diff read shows the PR also touches the settlement retry that was deferred in build; it comes out.
289
+ Change 1 is *user sees retry status on a failed payment* - schema + endpoint + the existing ops screen, 180 lines, their `pytest -k payments` green, revert is this change. `Kill if:` the signer cannot reject it on the screen they already use. Ugly retry-queue code two files over stays ugly. `decisions.md` logs the change; `delivery.md` says ops can see a retry without opening the spreadsheet. Marco sees it on staging they operate. That is the proof. Local green was not.
290
+
291
+ Then Thursday go-live of the failure-routing change. Readiness scoring catches two things the diff does not. The audit receipt is missing: the operating map says Marco's manual re-run is the fallback, and nobody has checked whether the new page fires *before* his morning run or after - if after, the alert changes nothing. That gets walked and cited before deploy. Second, the intent-vs-diff read shows the PR also touches the settlement retry that was deferred; it comes out.
194
292
 
195
293
  Pre-blast challenge: "what does this break if it fires at 3am and nobody acks?" Answer: nothing breaks, but the rota is not yet agreed - so the deploy waits on a name, not on code. That is a one-day slip that prevents a fake green.
196
294
 
197
295
  After deploy: `delivery.md` ship receipt with the audit cite, the kill test evidence, and the rollback line. Eval receipt: n/a, no AI in this path.
198
296
 
297
+ Greenfield is the same loop with an empty tree: first path a user can click, on an environment they will operate, then this go-live. Not the whole product in one dump.
298
+
199
299
  ## Principles
200
300
 
301
+ - One user action per change. Layers are untestable until assembled.
302
+ - On their staging, and you can undo it. Local green is not delivery.
303
+ - The ugly code outside this change stays ugly. That's discipline, not laziness.
201
304
  - A deployment without a tested rollback is reckless.
202
305
  - Roll back on any canary anomaly; investigate safely.
203
306
  - Verify the business metric, not just the technical one.
@@ -40,7 +40,7 @@ CONVENIENCE - if wrong, a task changes but the approach holds
40
40
  | "The team will adopt the new tool" | Ask three team members individually: "Show me how you'd use this" | 1h | 2 of 3 can describe a use case without prompting |
41
41
  | "The data is clean enough for ML" | Sample 200 records, count nulls/duplicates/format errors | 1h | <5% error rate on the fields the model needs |
42
42
 
43
- **4. Run the killer test first.** The assumption with the highest blast radius AND the cheapest validation gets tested immediately. This single principle saves more engagement time than any other: if the killer assumption is wrong, you've saved weeks; if it holds, you've bought confidence.
43
+ **4. Run the killer test first.** The assumption with the highest blast radius AND the cheapest validation gets tested immediately. This single principle saves more engagement time than any other: if the killer assumption is wrong, you've saved weeks; if it holds, you've bought confidence. Write the kill observation in `How we test` as the result that would **stop** the plan - plan copies that line onto each Now PR as `Kill if`.
44
44
 
45
45
  **5. Present findings as a fact base, not a challenge.**
46
46
 
@@ -8,4 +8,4 @@
8
8
  **Blast radius:** `CRITICAL` · `LOAD-BEARING` · `CONVENIENCE`
9
9
  **Status:** `OPEN` · `TESTING` · `CONFIRMED` · `DISPROVED` · `PARKED`
10
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.
11
+ **Rule:** a CRITICAL assumption still OPEN blocks plan. `How we test` is the kill observation (the result that stops the work) - plan copies it as `Kill if`. DISPROVED → update `reality.md` / `success.md` and log the reset in `decisions.md` the same day.
@@ -1,7 +1,12 @@
1
1
  # Reality (actual problem)
2
2
 
3
- <!-- Hypothesis until the discover phase confirms with evidence. -->
3
+ <!-- Hypothesis until the discover phase confirms with evidence. Dashboard reads Working theory / Evidence / Differs from brief how. -->
4
4
 
5
5
  **Working theory:**
6
6
  **Evidence:**
7
7
  **Differs from brief how:**
8
+
9
+ **Situation:**
10
+ **Complication:**
11
+ **Question:**
12
+ **Answer-space:**
@@ -1,100 +0,0 @@
1
- # thin-slices - thin slices on someone else's codebase
2
-
3
- **Enter when:** the build task is larger than a single PR, multiple files or systems are involved, or the FDE needs to show visible progress to a stakeholder every 2-3 days.
4
-
5
- **Read first:** `decisions.md` (the plan), `terrain.md` (the danger zones), `context.md`. This skill works *inside* the build phase - it's the execution discipline that makes large features safe on codebases you don't own.
6
-
7
- The FDE who builds a 2,000-line feature in one PR gets it rejected. The FDE who ships five 200-line PRs - each reviewable, testable, and revertible - earns the team's trust. On someone else's codebase, small and visible beats clever and large.
8
-
9
- ## Method (you do this work)
10
-
11
- **1. Slice vertically, never horizontally.**
12
-
13
- ```
14
- BAD (horizontal):
15
- PR 1: Build all database models
16
- PR 2: Build all API endpoints
17
- PR 3: Build all UI components
18
- PR 4: Wire everything together (and pray)
19
-
20
- GOOD (vertical):
21
- PR 1: User can create a payment (schema + endpoint + minimal UI) - testable
22
- PR 2: User can view payment status (query + endpoint + UI) - testable
23
- PR 3: Payment retry on failure (logic + endpoint + UI feedback) - testable
24
- PR 4: Admin can void a payment (auth + logic + UI) - testable
25
- ```
26
-
27
- Each vertical slice delivers working functionality the customer can see. Each slice is independently revertible.
28
-
29
- **2. The slice checklist.** Before starting any slice:
30
-
31
- - [ ] Slice is in `decisions.md` with acceptance criteria (happy + unhappy path)
32
- - [ ] Blast radius declared: which files, which systems, which users affected
33
- - [ ] Rollback path identified: "revert this PR" or something more specific
34
- - [ ] No dependency on an unmerged slice (if dependent, state it and merge in order)
35
-
36
- **3. The implementation loop.** For each slice, in this order:
37
-
38
- ```
39
- Read existing code in the area (search before creating)
40
- → Characterise what is already there (their tests, their runner)
41
- → Implement the minimal working path
42
- → On-site proof (below)
43
- → Cleanup pass (dedupe, simplify - behaviour unchanged)
44
- → Self-review against acceptance criteria
45
- → Commit with a message the client's team can read
46
- → Update decisions.md + delivery.md
47
- ```
48
-
49
- **On-site proof.** A green check on your laptop is not delivery. Before the slice is done:
50
-
51
- - Run **their** test command, typecheck, or smallest proving path. Write the command and the result in `delivery.md`.
52
- - If the signer in `success.md` cannot reject this slice on a screen they already use, it is not proven.
53
- - Staging they operate beats a local demo. If you have no staging: `unknown - ask:` who owns an environment, then stop pretending it shipped.
54
- - Model in the path: `eval-pack` until `evals.md` says SHIP. Do not skip because "it looked right in chat."
55
-
56
- Do not prove it with a textbook ritual. The proof is whatever this client already believes, plus one new receipt they can replay.
57
-
58
- **4. Size discipline.** Each slice targets:
59
-
60
- | Metric | Target | Why |
61
- |--------|--------|-----|
62
- | Lines changed | 100-300 | Reviewable in one sitting |
63
- | Time to implement | 30-90 minutes | Testable before context decays |
64
- | Files touched | 1-5 | Blast radius stays containable |
65
- | Tests added | ≥1 per new behaviour | Proves the slice works; guards against regression |
66
-
67
- A slice larger than 300 lines → split before implementing. "It's all connected" means the design needs work, not the slice limit.
68
-
69
- **5. Stakeholder visibility rhythm.** Every 2-3 slices, something the customer can see:
70
-
71
- - A working endpoint they can hit
72
- - A UI change they can click
73
- - A metric that moved
74
- - A risk that was retired
75
-
76
- Technical progress invisible to stakeholders is trust decay. `delivery.md` gets updated after every visible slice.
77
-
78
- **6. The scope trap.** Mid-slice discoveries - "this module also needs updating," "I should refactor this while I'm here":
79
-
80
- - If it's in `decisions.md`: do it as a separate slice.
81
- - If it's NOT in `decisions.md`: log it as a scope receipt (see `hold-scope.md`), don't touch it.
82
- - The hardest discipline: leaving ugly code alone when it's not in your slice.
83
-
84
- ## Artifact
85
-
86
- **`decisions.md`** - each slice logged with: what was implemented, what was tested, what was deferred.
87
-
88
- **`delivery.md`** - each visible slice with business-language description of what it delivers.
89
-
90
- ## Checkpoint
91
-
92
- After each slice: tests pass (state the command and result), acceptance criteria met, blast radius as declared. After every 2-3 slices: stakeholder visibility confirmed - what did they see, and what's their signal?
93
-
94
- ## Principles
95
-
96
- - Vertical slices, always. Horizontal layers are untestable until assembled.
97
- - 100-300 lines per slice. Larger means split first.
98
- - Every slice is independently revertible. If it isn't, the design is coupled.
99
- - Visible progress every 2-3 slices. Technical progress alone is trust decay.
100
- - The ugly code outside your slice stays ugly. That's discipline, not laziness.