Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "fdeops",
"description": "Engagement memory for AI coding agents. One @fde skill routes the client work - land, discover, plan, build, ship, close - and the record of it (sponsor, promise, decision, acceptance, dated) lands in local .fde/ files as you confirm judgment. For Forward Deployed Engineers running several clients at once.",
"version": "3.12.0",
"description": "Engagement memory for AI coding agents. One @fde skill is the client record - brief wrong, they went quiet, when did we agree, what they got - and the dated memory (sponsor, promise, decision, acceptance) lands in local .fde/ files as you confirm judgment. For Forward Deployed Engineers running several clients at once.",
"version": "3.13.0",
"category": "productivity",
"tags": [
"community-managed"
Expand Down
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# Changelog

## 3.13.0 — 2026-08-28

Four-day front: the skill is the engagement record, not a land-to-close operating system.

### Changed
- **Four days first** — the brief is wrong, they went quiet, when did we agree, what did they get. `@fde` leads with those moments; the six-domain router stays behind them.
- **First chat binds** — name the client (`@fde this is Acme`); the agent runs `fde resume --init`. The FDE never types the CLI. Terminal `--init` remains the fallback.
- **Generic SDLC left the router** — `build`, `debug`, `observability`, `qa-live`, `security-audit`, and `test-on-legacy` live in `skills/fde/archive/sdlc/`. Routed count is 31 methods + 5 overlays. Coding, tests, and commits stay in the host agent.
- **Friday status leads with the value ledger** — promised → measured → accepted, then trust. A number nobody signed is claimed, not delivered.
- **README** teaches the job, then a 30-second install (plugin or `npx skills add --skill fde`). Method catalog is a details block.

## 3.12.0 — 2026-08-27

Vocabulary: standard words on the outside, so nothing has to be learned before it works.
Expand Down
209 changes: 101 additions & 108 deletions README.md

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion adapters/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@ You are the AI coding agent for a **Forward Deployed Engineer (FDE)** - the huma

## Entry

When the FDE types **`@fde`** (or describes an engagement situation - new customer, mid-project takeover, production fire, quiet stakeholder, ready to ship), load the skill and route.
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.

Do **not** load `@fde` for ordinary code edits, TypeScript, unit tests, refactors, or git commits.

- Skill (single source of truth): `~/.claude/skills/fde/SKILL.md`
- **Never ask the FDE to pick a skill.** Read the situation, route silently, do the work.
- **Never ask the FDE to type `fde …` commands.** You run the local CLI; they confirm judgment in chat.

## Engagement memory

Expand Down
5 changes: 4 additions & 1 deletion adapters/GEMINI.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@ Context for Gemini CLI when assisting a **Forward Deployed Engineer (FDE)** - th

## Entry

When the FDE types **`@fde`** or describes an engagement situation (new customer, mid-project takeover, production fire, quiet stakeholder, ready to ship), load the skill and route.
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.

Do **not** load `@fde` for ordinary code edits, TypeScript, unit tests, refactors, or git commits.

- Skill (single source of truth): `~/.claude/skills/fde/SKILL.md`
- **Never ask the FDE to pick a skill.** Read the situation, route silently, do the work.
- **Never ask the FDE to type `fde …` commands.** You run the local CLI; they confirm judgment in chat.

## Engagement memory

Expand Down
2 changes: 1 addition & 1 deletion adapters/LOCAL-LLM.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ The model reads SKILL.md, routes to the right skill, and produces artifacts in y

## Model size recommendations

The methodology is detailed (37 methods, routing logic, evidence format, memory contract). Larger models handle it better:
The methodology is detailed (31 methods, routing logic, evidence format, memory contract). Larger models handle it better:

| Model class | Experience |
|-------------|-----------|
Expand Down
4 changes: 2 additions & 2 deletions adapters/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

**One brain, thin adapters.** fdeops has a single source of truth - the `@fde` skill at `skills/fde/SKILL.md` and the `fde` CLI. Each AI coding tool discovers it through a small pointer file in the place that tool already looks. No forked logic, no five copies to maintain - every adapter says the same thing: *route via `@fde`, read/write `.fde/` memory, talk like a peer, never touch what isn't yours.*

**Switching tools:** the fieldbook does not live in the agent. It lives at `~/fde-engagements/<client>/.fde/`. Point a new tool at a bound workspace, drop adapters (or install the skill/plugin for that tool), and the same client record opens. Auto session hooks are Claude Code–first; elsewhere load via `@fde` / `fde resume`. See [README § What works where](../README.md#what-works-where).
**Switching tools:** the fieldbook does not live in the agent. It lives at `~/fde-engagements/<client>/.fde/`. Point a new tool at a bound workspace, drop adapters (or install the skill/plugin for that tool), and the same client record opens. Auto session hooks are Claude Code–first; elsewhere load via `@fde` / `fde resume`. See [README § Switch coding agents](../README.md#switch-coding-agents-anytime).

## What goes where

Expand Down Expand Up @@ -31,4 +31,4 @@ Defaults to the current directory if no path is given. Existing files are never

## The principle

The adapter only tells the tool **where the brain is and how to behave**. All the method - the 37 methods, the overlays, the memory contract - lives 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.
The adapter only tells the tool **where the brain is and how to behave**. All the method - the 31 methods, the overlays, the memory contract - lives 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.
5 changes: 4 additions & 1 deletion adapters/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@ You are the AI coding agent for a **Forward Deployed Engineer (FDE)** - the huma

## Entry

When the FDE types **`@fde`** or describes an engagement situation (new customer, mid-project takeover, production fire, quiet stakeholder, ready to ship), load the skill and route.
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.

Do **not** load `@fde` for ordinary code edits, TypeScript, unit tests, refactors, or git commits.

- Skill (single source of truth): `~/.claude/skills/fde/SKILL.md`
- **Never ask the FDE to pick a skill.** Read the situation, route silently, do the work.
- **Never ask the FDE to type `fde …` commands.** You run the local CLI; they confirm judgment in chat.

## Engagement memory

Expand Down
6 changes: 4 additions & 2 deletions adapters/cursor.fde.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ You are the AI coding agent for a **Forward Deployed Engineer (FDE)** - the huma

## Entry

When the FDE types **`@fde`** or describes an engagement situation in plain language (new customer, meeting notes, prep for a stakeholder meeting, scope dispute, ready to ship), load the skill and route.
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.

- Skill (single source of truth): `~/.claude/skills/fde/SKILL.md`
Do **not** load `@fde` for ordinary code edits, unit tests, refactors, or git commits.

- Skill (single source of truth): `~/.claude/skills/fde/SKILL.md` (or the copy this install placed)
- **Never ask the FDE to pick a skill.** Read the situation, route silently, do the work.
- **Never ask the FDE to type `fde …` commands.** You run the local CLI; they confirm judgment in chat.

Expand Down
6 changes: 3 additions & 3 deletions bin/check.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ ok('skills structure')

// v3: one skill + phase references (progressive disclosure)
const requiredReferences = [
'land.md', 'discover.md', 'audit.md', 'plan.md', 'build.md', 'review.md',
'debug.md', 'rescue.md', 'ship.md', 'sketch.md', 'close.md', 'dashboard.md',
'land.md', 'discover.md', 'audit.md', 'plan.md', 'review.md',
'rescue.md', 'ship.md', 'sketch.md', 'close.md', 'dashboard.md',
'debrief.md', 'status.md', 'demo-prep.md',
'healthcare.md', 'fintech.md', 'gov.md',
'ai.md', 'eval-pack.md',
Expand All @@ -80,7 +80,7 @@ ok('phase references')
// every judgment-heavy reference carries a worked example that names the memory
// file the work lands in. Prose-only guidance drifts into advice nobody can apply.
const exampleReferences = [
'land.md', 'discover.md', 'plan.md', 'build.md', 'ship.md', 'close.md',
'land.md', 'discover.md', 'plan.md', 'ship.md', 'close.md',
'status.md', 'stakeholder-radar.md', 'options-analysis.md', 'business-case.md',
'assumption-audit.md', 'scope-defense.md',
]
Expand Down
73 changes: 56 additions & 17 deletions bin/fde.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
* fde receipts <term> "what did we agree?" - search memory with dates
* fde capture session-end snapshot → context.md (hooks use this)
* fde preserve pre-compaction context snapshot (hook-internal; hooks use this)
* fde status [--all] current engagement (default) or full portfolio (--all)
* fde status [--all] value ledger first, then trust (pass --all for portfolio)
* fde dashboard [--all] current engagement fieldbook (default) or all (--all)
* fde vault derived Obsidian vault of the fieldbook (disposable; --redacted)
*/
Expand Down Expand Up @@ -1223,7 +1223,7 @@ function cmdResume(args) {
.filter(d => !d.startsWith('.') && fs.existsSync(path.join(ENGAGEMENTS_ROOT, d, '.fde')))
.join(', ') || '(none yet)'
: '(none yet)'
console.log(`NO ENGAGEMENT for this workspace.\nexisting: ${list}\ncreate + bind one: fde resume --init <client-name>`)
console.log(`NO ENGAGEMENT for this workspace.\nexisting: ${list}\nAsk the human the client name (one question), then run: fde resume --init <client-name>\nDo not tell them to type that command.`)
process.exit(2)
}
// Monday-morning: triage + proactive hygiene (silent when clean), then memory.
Expand Down Expand Up @@ -2295,21 +2295,59 @@ function hasValueBucket(eng) {
const PENDING_CELL_RE =
/^(?:pending|tbd|to ?be ?(?:measured|confirmed|determined)|n\s*\/\s*a|na|none|unknown|not measured|\?+|\.{2,}|…|-+|—+|–+)(?:[^\w].*)?$/i

function claimedValueRows(eng) {
function parseValueLedger(eng) {
const ledger = stripTemplateNoise(sectionBody(readClean(eng, 'delivery.md'), 'Value ledger') || '')
const table = parseMdTable(ledger)
if (!table) return { claimed: 0, columnMissing: false }
const mIdx = colIndex(table.headers, /measured/i)
if (mIdx === -1) return { claimed: 0, columnMissing: false }
const aIdx = colIndex(table.headers, /accept/i)
let claimed = 0
if (!table) return { rows: [], columnMissing: false }
const idx = {
slice: colIndex(table.headers, /slice/i),
promised: colIndex(table.headers, /promised/i),
measured: colIndex(table.headers, /measured/i),
accepted: colIndex(table.headers, /accept/i),
}
const cell = (row, i) => (i === -1 ? '' : String(row[i] || '').trim())
const rows = []
for (const row of table.rows) {
const measured = String(row[mIdx] || '').trim()
if (!measured || PENDING_CELL_RE.test(measured)) continue
const accepted = aIdx === -1 ? '' : String(row[aIdx] || '').trim()
if (!accepted || PENDING_CELL_RE.test(accepted)) claimed++
const slice = cell(row, idx.slice)
const promised = cell(row, idx.promised)
const measured = cell(row, idx.measured)
const accepted = cell(row, idx.accepted)
if (!slice && !promised && !measured) continue
const measuredPending = !measured || PENDING_CELL_RE.test(measured)
const acceptedPending = idx.accepted === -1 || !accepted || PENDING_CELL_RE.test(accepted)
let state = 'unmeasured'
if (!measuredPending && acceptedPending) state = 'claimed'
else if (!measuredPending) state = 'accepted'
rows.push({ slice, promised, measured, accepted, state })
}
return { claimed, columnMissing: aIdx === -1 }
return { rows, columnMissing: idx.accepted === -1 }
}

function claimedValueRows(eng) {
const { rows, columnMissing } = parseValueLedger(eng)
return { claimed: rows.filter(r => r.state === 'claimed').length, columnMissing }
}

function formatValueLedgerLine(r) {
const name = r.slice || 'value'
let body = r.promised || ''
if (r.state !== 'unmeasured' && r.measured) {
if (!body) body = r.measured
else if (!body.includes(r.measured)) body = `${body} → ${r.measured}`
}
const head = body ? `${name}: ${body}` : name
if (r.state === 'accepted') return `${head} · accepted by ${r.accepted}`
if (r.state === 'claimed') return `${head} · claimed, not yet accepted`
return `${head} · not yet measured`
}

function valueLedgerStatusLines(eng, opts = {}) {
const { rows } = parseValueLedger(eng)
if (!rows.length) return [' value: none yet']
const cap = opts.compact ? 1 : 8
const lines = rows.slice(0, cap).map(r => ` ${formatValueLedgerLine(r)}`)
if (rows.length > cap) lines.push(` … ${rows.length - cap} more in delivery.md`)
return lines
}

// AI in scope for ship/close hygiene — delivery/decisions/trust evidence only.
Expand Down Expand Up @@ -2669,7 +2707,7 @@ function cmdStatus(args) {
if (!fs.existsSync(eng)) continue
const s = computeSignals(eng)
const note = [s.memoryWarn, (s.dirtyFiles && s.dirtyFiles.length) ? `dirty:${s.dirtyFiles.length}` : '', s.reason || s.topRisk].filter(Boolean).join(' · ').slice(0, 70)
rows.push({ name: d, phase: s.phase, trust: s.trust, signalAge: s.signalAge, stale: s.stale, updated: s.updated, reason: note, memoryWarn: s.memoryWarn, dirtyFiles: s.dirtyFiles })
rows.push({ name: d, phase: s.phase, trust: s.trust, signalAge: s.signalAge, stale: s.stale, updated: s.updated, reason: note, memoryWarn: s.memoryWarn, dirtyFiles: s.dirtyFiles, valueLines: valueLedgerStatusLines(eng, { compact: true }) })
}
} else {
const eng = resolveEngagement()
Expand All @@ -2679,13 +2717,14 @@ function cmdStatus(args) {
}
const s = computeSignals(eng)
const note = [s.memoryWarn, (s.dirtyFiles && s.dirtyFiles.length) ? `dirty:${s.dirtyFiles.length}` : '', s.reason || s.topRisk].filter(Boolean).join(' · ').slice(0, 70)
rows.push({ name: engagementSlugFromPath(eng), phase: s.phase, trust: s.trust, signalAge: s.signalAge, stale: s.stale, updated: s.updated, reason: note, memoryWarn: s.memoryWarn, dirtyFiles: s.dirtyFiles })
rows.push({ name: engagementSlugFromPath(eng), phase: s.phase, trust: s.trust, signalAge: s.signalAge, stale: s.stale, updated: s.updated, reason: note, memoryWarn: s.memoryWarn, dirtyFiles: s.dirtyFiles, valueLines: valueLedgerStatusLines(eng) })
}
if (!rows.length) { console.log('no engagements yet'); return }
const order = { RED: 0, amber: 1, green: 2 }
rows.sort((a, b) => order[a.trust] - order[b.trust])
console.log((all ? 'FDE PORTFOLIO' : 'FDE STATUS') + ' - trust-first triage (heuristic: red > amber > green)\n')
console.log((all ? 'FDE PORTFOLIO' : 'FDE STATUS') + ' - value first, then trust\n')
for (const r of rows) {
for (const line of r.valueLines) console.log(line)
// "amber?" = structured signal went stale (>21d) - reconfirm before trusting it
const label = r.trust + (r.stale ? '?' : '')
const sig = r.signalAge != null ? `signal ${r.signalAge}d old${r.stale ? ' (STALE - reconfirm)' : ''} ` : ''
Expand Down Expand Up @@ -3153,7 +3192,7 @@ function printUsage() {
fde tidy [--apply] propose safe consolidations (contract: no new facts; git-reversible)
fde owner [set email] who keeps this engagement record
fde receipts <term> "what did we agree?" with dates
fde status [--all] current engagement status (pass --all for full portfolio)
fde status [--all] value ledger, then trust (pass --all for full portfolio)
fde dashboard [--all] current engagement fieldbook (pass --all for every client)
fde vault derived Obsidian vault of every engagement (--current for one, --redacted for a shared screen, --out <dir>)
hooks call these; you do not: capture (session-end snapshot), preserve (pre-compaction snapshot)
Expand Down
3 changes: 2 additions & 1 deletion docs/REPO_LAYOUT.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

| Path | Purpose |
|------|---------|
| `skills/fde/` | **The one skill** - router (`SKILL.md`) + 37 methods, 5 overlays, and AI companion `eval-pack` under `references/` - installed to `~/.claude/skills/` |
| `skills/fde/` | **The one skill** - router (`SKILL.md`) + 31 routed methods, 5 overlays, and AI companion `eval-pack` under `references/` - installed to `~/.claude/skills/` |
| `skills/fde/archive/sdlc/` | Archived SDLC methods (`build`, `debug`, `observability`, `qa-live`, `security-audit`, `test-on-legacy`) - kept for history, **not routed** |
| `adapters/` | Thin per-tool pointers (Codex/`AGENTS.md`, Gemini, Cursor, Copilot, local LLMs) - `node bin/install.js adapters <dir>` |
| `templates/.fde/` | Core memory templates for `fde resume --init` (phase artifacts are created by phases on demand; `evals.md` is optional) |
| `examples/` | Fictional walkthroughs with sample `.fde/` files |
Expand Down
Loading
Loading