diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 56e6379..f101bc5 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -5,13 +5,13 @@ "url": "https://github.com/suboss87" }, "metadata": { - "description": "Second brain for Forward Deployed Engineers - one @fde skill, phase methods, and local .fde/ engagement memory." + "description": "Engagement memory for AI coding agents - one @fde skill, phase methods, and local .fde/ client memory." }, "plugins": [ { "name": "fdeops", "source": "./", - "description": "Second brain for Forward Deployed Engineers: one @fde entry point that routes by situation (land, discover, build, rescue, ship, close), does the phase work, and keeps per-customer engagement memory in local .fde/ files. Healthcare, fintech, and government overlays included." + "description": "Engagement memory for AI coding agents. One entry point that routes by situation (land, discover, build, rescue, ship, close), does the phase work, and keeps per-client memory - sponsor, promise, decision, acceptance, dated - in local .fde/ files. For Forward Deployed Engineers running several clients at once; healthcare, fintech, and government overlays included." } ] } diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index e159ca9..8d449f6 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "fdeops", - "description": "Second brain for Forward Deployed Engineers. One @fde skill - enter at day one, mid-project, or mid-fire; it routes and does the phase work; engagement memory lands in .fde/ as you confirm judgment.", - "version": "3.11.1", + "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", "category": "productivity", "tags": [ "community-managed" @@ -14,6 +14,8 @@ "repository": "https://github.com/suboss87/fdeops", "license": "MIT", "keywords": [ + "engagement-memory", + "agent-memory", "fde", "forward-deployed", "legacy-code", diff --git a/CHANGELOG.md b/CHANGELOG.md index d348a76..019d968 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog +## 3.12.0 — 2026-08-27 + +Vocabulary: standard words on the outside, so nothing has to be learned before it works. + +### Changed +- **The skill fires on client work, not on a password.** The frontmatter `description` - the only text a host reads before loading a skill - triggered on `@fde` plus a list of our own nouns ("hygiene", "receipts"), so an FDE who simply talked about their client got no memory. It now names the intents in the standard `Use when …` form: mentioning a client or sponsor, debriefing a meeting, asking what was agreed, prepping a readout, opening a session in a client workspace. `@fde` remains, as one trigger among several rather than the gate. +- **`fde tidy` is the verb; `fde garden` still works.** "Garden" was a metaphor only we used. Nothing is removed - the old name routes to the same code, and the alias is covered by a test. +- **`capture` and `preserve` left the human help.** Only hooks call them; listing them invited an FDE to run a snapshot by hand. +- **"Engagement memory for AI coding agents"** replaces "second brain" in the npm, plugin and marketplace descriptions - the phrase someone would actually search, and an accurate one. + +### Added +- **One glossary.** The six words that carry the method (fieldbook, brief vs reality, terrain, trust signal, receipts, vault) are defined once in the README instead of being met scattered and guessed. A gate keeps it in place, and a second gate keeps the description triggering on intent. + ## 3.11.1 — 2026-08-27 Adoption: one skill, and the CLI is never missing. diff --git a/README.md b/README.md index da2f050..b7f83d9 100644 --- a/README.md +++ b/README.md @@ -7,9 +7,9 @@ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) [![Node](https://img.shields.io/badge/node-%3E%3D18-brightgreen)](https://nodejs.org) -Skill packs teach your AI coding agent how to *build*. None of them remember who the client is, what you promised, or who agreed it was delivered. FDEOps adds that layer: a private fieldbook per engagement (`.fde/`), a field methodology from land to close, and one `@fde` skill that routes it. +**Engagement memory for AI coding agents.** Skill packs teach your agent how to *build*. None of them remember who the client is, what you promised, or who agreed it was delivered - close the window and it is gone. FDEOps adds that missing layer: a private record per client (`.fde/`), a field methodology from land to close, and one skill that routes it. -Built for Forward Deployed Engineers and anyone embedded in client work - consultants, agency developers, solutions architects, fractional CTOs. Feels like a second brain; behaves like a defensible record: dated, sourced, on your laptop. +Built for Forward Deployed Engineers and anyone embedded in client work - consultants, agency developers, solutions architects, fractional CTOs. Not notes: a defensible record - dated, sourced, on your laptop. ``` land discover plan build ship close @@ -58,9 +58,11 @@ npx fdeops resume # where we are **3. Work** in plain language: ```text -@fde New client. Payments platform. They want it live before the Q3 audit. +New client, Garvey. Payments platform. They want it live before the Q3 audit. ``` +No prefix needed - naming a client is enough. Say `@fde` when you want it explicitly. + **It is working if** `npx fdeops resume` prints this client's phase, trust signal, and next action - and prints the same thing tomorrow, from a new session, with no explaining. Full workflow: [docs/USAGE.md](docs/USAGE.md).
@@ -79,16 +81,16 @@ npx fdeops resume # where we are ## The week -`@fde` plus English. No cheat sheet. +**Say what happened, in your words.** There is nothing to memorise - no flags, no cheat sheet. The skill picks it up when you mention a client, debrief a meeting, or ask what was agreed; `@fde` is only the explicit way to summon it. | When | What you say | What you get | |------|--------------|--------------| | **Start of week** | `@fde` - or just open Claude Code | Fieldbook on disk either way. **Claude Code** injects trust, phase, next before you type. **Cursor / Codex / others:** say `@fde` or `resume` - nothing auto-loads. | -| **After a meeting** | `@fde` debrief these notes *(paste or attach)* | Proposed updates. You review, then confirm. | -| **Optional: pull** | `@fde` connect Granola *(once)* · `@fde` pull today's Acme transcript | You add that source MCP. We **pull** on request - no push, no sync. [mcp/recipes/](mcp/recipes/) | -| **Before a stakeholder meeting** | `@fde` prep me for tomorrow with the sponsor | Brief from what you already logged. | -| **Scope dispute** | `@fde` when did we agree to drop that? | Dated answers, or a clear gap. | -| **End of week** | `@fde` draft the sponsor update from the record | Status grounded in what happened. | +| **After a meeting** | here are my notes from the Acme call *(paste or attach)* | Proposed updates. You review, then confirm. | +| **Optional: pull** | connect Granola *(once)* · pull today's Acme transcript | You add that source MCP. We **pull** on request - no push, no sync. [mcp/recipes/](mcp/recipes/) | +| **Before a stakeholder meeting** | prep me for tomorrow with the sponsor | Brief from what you already logged. | +| **Scope dispute** | when did we agree to drop that? | Dated answers, or a clear gap. | +| **End of week** | draft the sponsor update from the record | Status grounded in what happened. | Same folder every time: `~/fde-engagements//.fde/`. @@ -149,6 +151,8 @@ One folder per client. Plain markdown, so you can grep it, diff it, copy it into A day-one fieldbook ships **empty** - headings and allowed values, no invented rows - so anything you read in it is something that actually happened. Schema: [docs/schema.md](docs/schema.md). +**Words used here, once:** *engagement* - one client's body of work, one folder. *Fieldbook* - that folder (`.fde/`), the record itself. *Brief vs reality* - what they said the problem was, and what it turned out to be. *Terrain* - their systems and org as you actually found them. *Trust signal* - green / amber / red on one relationship. *Receipts* - the dated line proving something was agreed. *Vault* - the Obsidian copy `fde vault` generates to read it all in one window. + --- ## The field methods diff --git a/bin/check.js b/bin/check.js index a0a83bb..402c364 100644 --- a/bin/check.js +++ b/bin/check.js @@ -286,6 +286,33 @@ if (!read('skills/fde/SKILL.md').includes('npx --yes fdeops')) { fail('skills/fde/SKILL.md must fall back to npx --yes fdeops when the CLI is not installed') } else ok('SKILL.md npx CLI fallback') +// The frontmatter description is the only text every host reads before deciding +// to load the skill. If it triggers on "@fde" alone, an FDE who just talks about +// their client gets no memory - so it must carry natural-intent triggers in the +// "Use when …" convention, and @fde must be one of several, never the gate. +{ + const fm = /^---\n([\s\S]*?)\n---/.exec(read('skills/fde/SKILL.md')) + const desc = fm ? (/^description:[^\S\n]*(.*)$/m.exec(fm[1]) || [])[1] || '' : '' + const triggers = desc.match(/Use when/g) || [] + if (triggers.length < 4) { + fail(`SKILL.md description needs several "Use when …" triggers so it fires on intent (found ${triggers.length})`) + } else if (/Use when the human says @fde or/.test(desc)) { + fail('SKILL.md description must not gate on @fde - name the client-work intents first') + } else ok('SKILL.md description triggers on intent') +} + +// Six invented words (fieldbook, terrain, reality, trust signal, receipts, +// vault) carry the method. They were never defined in one place, so a stranger +// met them scattered through the docs and guessed. +{ + const gloss = /Words used here[\s\S]{0,1200}/.exec(readme) + const missing = ['fieldbook', 'reality', 'terrain', 'trust signal', 'receipts', 'vault'] + .filter(w => !gloss || !new RegExp(w, 'i').test(gloss[0])) + if (!gloss) fail('README must define its invented words once (a "Words used here" line)') + else if (missing.length) fail(`README glossary is missing: ${missing.join(', ')}`) + else ok('README defines its own vocabulary') +} + if (!fs.existsSync(path.join(root, 'docs', 'USAGE.md'))) { fail('docs/USAGE.md missing') } else ok('docs/USAGE.md') diff --git a/bin/fde.js b/bin/fde.js index d696794..b85f61e 100755 --- a/bin/fde.js +++ b/bin/fde.js @@ -18,7 +18,7 @@ * fde debrief --smart propose routing from messy notes; --apply commits it * fde prep [label] grounded walk-in brief from existing .fde/ only * fde doctor deterministic memory lint (stale signals, gaps) - * fde garden [--apply] propose safe consolidations; apply only with --apply + * fde tidy [--apply] propose safe consolidations; apply only with --apply (was: garden) * fde ingest … stage → propose → apply pull sink (.inbox/; never auto-writes .fde/) * fde owner [set …] who keeps this engagement record * fde receipts "what did we agree?" - search memory with dates @@ -2488,18 +2488,18 @@ function cmdGarden(args) { // Gardener contract (from Rowboat note_curation): no new facts, no deleted substance, // reversible via git when healthy, confirm before apply. Mechanical only - no LLM rewrite. if (gitHealth.ok) { - console.log('GARDEN (contract: no new facts · no deleted substance · reversible via memory git)') + console.log('TIDY (contract: no new facts · no deleted substance · reversible via memory git)') } else if (gitHealth.reason === 'broken') { - console.log('GARDEN (contract: no new facts · no deleted substance · ⚠ memory git BROKEN — NOT reversible until ledger is repaired)') + console.log('TIDY (contract: no new facts · no deleted substance · ⚠ memory git BROKEN — NOT reversible until ledger is repaired)') } else { - console.log('GARDEN (contract: no new facts · no deleted substance · ⚠ memory not git-versioned — NOT reversible)') + console.log('TIDY (contract: no new facts · no deleted substance · ⚠ memory not git-versioned — NOT reversible)') } console.log(resumeTriage(eng)) if (!gitHealth.ok) { console.log( gitHealth.reason === 'broken' - ? '\n⚠ ledger is UNVERSIONED (corrupt .git). Repair before trusting garden apply: mv .fde/.git .fde/.git.broken && run any fde write to re-init.' - : '\n⚠ no memory git — garden apply cannot create a reversible commit until the ledger exists.' + ? '\n⚠ ledger is UNVERSIONED (corrupt .git). Repair before trusting tidy apply: mv .fde/.git .fde/.git.broken && run any fde write to re-init.' + : '\n⚠ no memory git — tidy apply cannot create a reversible commit until the ledger exists.' ) } const proposals = [] @@ -2539,18 +2539,18 @@ function cmdGarden(args) { }) } if (!proposals.length) { - console.log('\nNothing to garden.') + console.log('\nNothing to tidy.') return } console.log(`\n${proposals.length} proposal(s):`) proposals.forEach((p, i) => console.log(` ${i + 1}. [${p.kind}] ${p.text}`)) if (!apply) { - console.log('\nApply mechanical items only: fde garden --apply') + console.log('\nApply mechanical items only: fde tidy --apply') console.log('Manual items stay yours. Every apply commits to memory git when the ledger is healthy.') return } if (!gitHealth.ok && gitHealth.reason === 'broken') { - console.error('refusing garden --apply while memory git is broken - repair the ledger first') + console.error('refusing tidy --apply while memory git is broken - repair the ledger first') process.exit(1) } ensureMemoryGit(eng) @@ -2603,9 +2603,9 @@ function cmdGarden(args) { touched.add('context-archive.md') console.log(`applied: archived ${p.sessionBlocks.length} old session-end blocks → context-archive.md`) } - const hash = commitMemory(eng, 'garden', { files: [...touched] }) + const hash = commitMemory(eng, 'tidy', { files: [...touched] }) if (!applied) console.log('no mechanical proposals applied (manual items remain)') - else console.log(`garden done${hash ? ` @${hash}` : ''}`) + else console.log(`tidy done${hash ? ` @${hash}` : ''}`) } // Keep the first open-risk bullet per fingerprint; move later echoes under ## Retired. @@ -2647,7 +2647,7 @@ function applyRiskDedupe(eng, clusters) { const stamp = new Date().toISOString().slice(0, 10) const block = retiredLines.map(l => { const body = l.trim().replace(/^-\s*/, '') - return `- [${stamp}] (garden dedupe) ${body}` + return `- [${stamp}] (tidy dedupe) ${body}` }).join('\n') out = appendUnderSection(out, 'Retired', block) withFileLock(p, () => { atomicWriteFile(p, out.endsWith('\n') ? out : out + '\n') }) @@ -3150,14 +3150,13 @@ function printUsage() { fde prep [label] grounded walk-in brief from existing .fde/ only fde doctor lint engagement memory (stale signals, gaps) fde redact preview/remove lines containing a buried term (pass --apply to commit) - fde garden [--apply] propose safe consolidations (contract: no new facts; git-reversible) + fde tidy [--apply] propose safe consolidations (contract: no new facts; git-reversible) fde owner [set email] who keeps this engagement record fde receipts "what did we agree?" with dates - fde capture session-end memory snapshot (hooks use this) - fde preserve pre-compaction context snapshot (hook-internal; hooks use this) fde status [--all] current engagement status (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 ) + hooks call these; you do not: capture (session-end snapshot), preserve (pre-compaction snapshot) env FDEOPS_ENGAGEMENTS_ROOT override ~/fde-engagements (init/status/dashboard/registry) writes require a workspace bind (or FDEOPS_ENGAGEMENT) - folder-name match is read-only .fde/ is git-versioned locally for tamper-evident receipts (no remote, no telemetry) @@ -3176,6 +3175,8 @@ switch (cmd) { case 'prep': cmdPrep(args); break case 'doctor': cmdDoctor(); break case 'redact': cmdRedact(args); break + // `garden` was the name through 3.11.x; it keeps working. + case 'tidy': case 'garden': cmdGarden(args); break case 'owner': cmdOwner(args); break case 'receipts': cmdReceipts(args); break diff --git a/bin/install.js b/bin/install.js index e0c55c7..916d759 100755 --- a/bin/install.js +++ b/bin/install.js @@ -355,7 +355,7 @@ function cmdInstall(opts = {}) { // through to the CLI (fde.js reads process.argv itself, so require() is enough). const FDE_SUBCOMMANDS = [ 'demo', 'scan', 'resume', 'triage', 'log', 'debrief', 'ingest', 'prep', 'doctor', 'redact', - 'garden', 'owner', 'receipts', 'capture', 'preserve', 'status', 'dashboard', 'vault', 'help', + 'tidy', 'garden', 'owner', 'receipts', 'capture', 'preserve', 'status', 'dashboard', 'vault', 'help', ] const INSTALL_SUBCOMMANDS = ['init', 'adapters', 'install'] diff --git a/docs/REPO_LAYOUT.md b/docs/REPO_LAYOUT.md index 9f1bb1f..84038b1 100644 --- a/docs/REPO_LAYOUT.md +++ b/docs/REPO_LAYOUT.md @@ -6,7 +6,7 @@ | `adapters/` | Thin per-tool pointers (Codex/`AGENTS.md`, Gemini, Cursor, Copilot, local LLMs) - `node bin/install.js adapters ` | | `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 | -| `bin/fde.js` | Deterministic CLI - scan, resume, triage, log, debrief, ingest, prep, doctor, garden, redact, receipts, capture, preserve, status, dashboard | +| `bin/fde.js` | Deterministic CLI - scan, resume, triage, log, debrief, ingest, prep, doctor, tidy, redact, receipts, capture, preserve, status, dashboard, vault | | `mcp/` | Optional MCP sink (`fdeops-ingest`) + source **recipes** (`mcp/recipes/` — file, granola, notion); source MCPs remain user-configured | | `bin/lib/` | Shared memory / trust / render helpers used by the CLI | | `bin/check.js` | Structural + install smoke gate (`npm run check`) | diff --git a/mcp/fdeops-ingest/package.json b/mcp/fdeops-ingest/package.json index 826a004..b85da8a 100644 --- a/mcp/fdeops-ingest/package.json +++ b/mcp/fdeops-ingest/package.json @@ -1,6 +1,6 @@ { "name": "fdeops-ingest-mcp", - "version": "3.11.1", + "version": "3.12.0", "private": true, "description": "Thin stdio MCP sink for FDEOps ingest (stage → propose → apply). Zero runtime dependencies.", "bin": { diff --git a/package.json b/package.json index 19d461a..4f3c1ae 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "fdeops", - "version": "3.11.1", - "description": "Field kit for engineers embedded in client work - a real CLI (recon, memory, portfolio), one @fde skill with field judgment on top, and hooks that make it automatic. Claude Code plugin and any agent that loads skills.", + "version": "3.12.0", + "description": "Engagement memory 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 routing land-to-close methodology, a deterministic local CLI, and hooks that make it automatic. Claude Code plugin and any agent that loads skills.", "bin": { "fdeops": "bin/install.js", "fde": "bin/fde.js" @@ -30,6 +30,9 @@ "ai-agent", "skills", "plugin", + "engagement-memory", + "agent-memory", + "client-context", "fde", "forward-deployed", "forward-deployed-engineer", diff --git a/plugin.json b/plugin.json index a8ce33f..0514357 100644 --- a/plugin.json +++ b/plugin.json @@ -1,8 +1,8 @@ { "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json", "name": "fdeops", - "version": "3.11.1", - "description": "Engagement fieldbook for Forward Deployed Engineers: per-client memory in local .fde/ files, one @fde skill, land to close methodology. Local-only, no network.", + "version": "3.12.0", + "description": "Engagement memory for AI coding agents: per-client memory in local .fde/ files, one @fde skill, land to close methodology. Local-only, no network.", "author": { "name": "Subash Natarajan", "url": "https://github.com/suboss87" diff --git a/skills/fde/SKILL.md b/skills/fde/SKILL.md index dbf8572..543536e 100644 --- a/skills/fde/SKILL.md +++ b/skills/fde/SKILL.md @@ -1,6 +1,6 @@ --- name: fde -description: Engagement fieldbook for Forward Deployed Engineers. Use when the human says @fde or asks about client memory, debrief, prep, receipts, trust, hygiene, or sponsor status — route and run the local fde CLI; never ask them to type fde commands. Do not use for ordinary code edits, unit tests, refactors, or git commits. +description: Keeps engagement memory for client work - sponsor, promise, what shipped, who accepted it. Use when the human names a client, customer or stakeholder. Use when they debrief a meeting or paste notes from one. Use when they ask what was agreed, or want dates and receipts. Use when they prep a client meeting or readout, when scope or trust shifts, or when they say @fde. Route the phase and run the local fde CLI (or npx --yes fdeops if it is not installed); never ask them to type commands. Not for ordinary code edits, unit tests, refactors or commits. --- # @fde diff --git a/test/fde-cli.test.js b/test/fde-cli.test.js index 8139eac..f50af0f 100644 --- a/test/fde-cli.test.js +++ b/test/fde-cli.test.js @@ -1931,6 +1931,25 @@ test('garden proposes and applies duplicate open-risk consolidation', () => { assert.ok(openBullets.length < 4, `expected fewer open risks after dedupe, got ${openBullets.length}`) }) +test('tidy is the verb and garden still routes to it', () => { + const sandbox = makeSandbox('tidy-alias') + assert.equal(runFde(sandbox, ['resume', '--init', 'tidyco']).status, 0) + const tidy = runFde(sandbox, ['tidy']) + const garden = runFde(sandbox, ['garden']) + assert.equal(tidy.status, 0, tidy.stderr) + assert.equal(garden.status, 0, garden.stderr) + assert.equal(tidy.stdout, garden.stdout) + // An unknown verb prints usage and exits 1; the alias must not degrade to that. + assert.doesNotMatch(tidy.stdout, /deterministic core of fdeops/) + + const help = runFde(sandbox, ['help']) + assert.match(help.stdout, /fde tidy \[--apply\]/) + // capture/preserve are hook-internal - listing them as verbs invited an FDE to + // run a snapshot by hand and wonder why nothing happened. + assert.doesNotMatch(help.stdout, /^\s+fde capture\s{2,}/m) + assert.doesNotMatch(help.stdout, /^\s+fde preserve\s{2,}/m) +}) + test('doctor requires operating map from plan onward; silent on discover', () => { const sandbox = makeSandbox('opmap-doctor') assert.equal(runFde(sandbox, ['resume', '--init', 'opmap']).status, 0)