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 +26 -35
- package/adapters/AGENTS.md +1 -1
- package/adapters/GEMINI.md +1 -1
- package/adapters/LOCAL-LLM.md +1 -1
- package/adapters/README.md +1 -1
- package/adapters/copilot-instructions.md +1 -1
- package/adapters/cursor.fde.mdc +1 -1
- package/bin/check.js +17 -5
- package/bin/fde.js +150 -13
- package/bin/lib/render.js +2 -2
- package/bin/lib/trust.js +38 -3
- package/mcp/fdeops-ingest/package.json +1 -1
- package/package.json +1 -1
- package/plugin.json +1 -1
- package/skills/fde/SKILL.md +10 -11
- package/skills/fde/references/discover.md +32 -13
- package/skills/fde/references/eval-pack.md +1 -1
- package/skills/fde/references/plan.md +9 -6
- package/skills/fde/references/ship.md +120 -17
- package/skills/fde/references/test-assumptions.md +1 -1
- package/templates/.fde/assumptions.md +1 -1
- package/templates/.fde/reality.md +6 -1
- package/skills/fde/references/thin-slices.md +0 -100
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,
|
|
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
|
-
|
|
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
|
-
|
|
|
25
|
-
| Find the real problem | `/discover` |
|
|
26
|
-
|
|
|
27
|
-
| Ship
|
|
28
|
-
|
|
|
29
|
-
|
|
|
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
|
|
36
|
-
|
|
|
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
|
-
|
|
|
39
|
-
| Friday
|
|
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,
|
|
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
|
|
96
|
+
## All 30 Skills
|
|
104
97
|
|
|
105
|
-
The commands above are the entry points. Under the hood, `@fde` activates these
|
|
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) |
|
|
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
|
|
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 -
|
|
128
|
+
### Ship - On their repo, then live
|
|
136
129
|
|
|
137
130
|
| Skill | What It Does | Use When |
|
|
138
131
|
|--------|--------------|----------|
|
|
139
|
-
| [
|
|
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
|
-
###
|
|
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/ #
|
|
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
|
-
│ ├──
|
|
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 #
|
|
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,
|
|
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
|
|
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
|
package/adapters/AGENTS.md
CHANGED
|
@@ -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,
|
|
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.
|
package/adapters/GEMINI.md
CHANGED
|
@@ -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,
|
|
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.
|
package/adapters/LOCAL-LLM.md
CHANGED
|
@@ -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 (
|
|
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
|
|-------------|-----------|
|
package/adapters/README.md
CHANGED
|
@@ -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
|
|
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,
|
|
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.
|
package/adapters/cursor.fde.mdc
CHANGED
|
@@ -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,
|
|
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
|
|
225
|
-
fail('README must not embed
|
|
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 (
|
|
272
|
-
|
|
273
|
-
|
|
281
|
+
// Front-door map is the embed left-to-right (Land → Close). 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
|
|
976
|
+
const nameKey = signalSubjectKey(name)
|
|
889
977
|
let signal = null, matchedDate = null
|
|
890
|
-
if (
|
|
978
|
+
if (nameKey) {
|
|
891
979
|
for (const h of history) {
|
|
892
|
-
if (h.text
|
|
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
|
-
|
|
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 ${
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
|
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
|
-
//
|
|
52
|
-
//
|
|
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
|
-
|
|
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,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fdeops",
|
|
3
|
-
"version": "3.
|
|
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.
|
|
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",
|
package/skills/fde/SKILL.md
CHANGED
|
@@ -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,
|
|
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,
|
|
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
|
|
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
|
|
39
|
-
3. **Prove it
|
|
40
|
-
4. **If a model judges:** `evals.md` Verdict SHIP before
|
|
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.
|
|
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
|
-
|
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
**
|
|
154
|
-
**
|
|
155
|
-
**
|
|
156
|
-
**
|
|
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
|
|
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
|
|
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
|
|
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`,
|
|
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 `
|
|
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.
|
|
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
|
|
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
|
-
-
|
|
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 -
|
|
1
|
+
# ship - on their site, then live
|
|
2
2
|
|
|
3
|
-
**Enter when:**
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
51
|
-
| **Eval receipt** | **n/a** (no AI on this
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
**`
|
|
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
|
-
|
|
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,
|
|
287
|
+
Acme, brownfield. Plan Now has three changes, not "the payments rewrite."
|
|
192
288
|
|
|
193
|
-
|
|
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.
|