fdeops 3.9.20 → 3.10.2
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 +106 -87
- package/adapters/LOCAL-LLM.md +1 -1
- package/adapters/README.md +1 -1
- package/bin/check.js +211 -4
- package/bin/fde.js +379 -26
- package/bin/install.js +223 -20
- package/bin/lib/memory.js +2 -2
- package/bin/lib/render.js +2 -0
- package/mcp/README.md +2 -4
- package/mcp/fdeops-ingest/README.md +7 -3
- package/mcp/fdeops-ingest/package.json +1 -1
- package/mcp/fdeops-ingest/server.js +59 -28
- package/mcp/recipes/README.md +9 -8
- package/mcp/recipes/file.md +7 -7
- package/mcp/recipes/granola.md +16 -20
- package/mcp/recipes/notion.md +16 -18
- package/mcp/recipes/slack.md +61 -0
- package/mcp.json +10 -0
- package/package.json +4 -2
- package/plugin.json +21 -0
- package/skills/fde/SKILL.md +4 -4
- package/skills/fde/references/assumption-audit.md +10 -0
- package/skills/fde/references/build.md +13 -1
- package/skills/fde/references/business-case.md +10 -0
- package/skills/fde/references/close.md +11 -1
- package/skills/fde/references/discover.md +10 -0
- package/skills/fde/references/ingest-connect.md +16 -18
- package/skills/fde/references/ingest.md +5 -4
- package/skills/fde/references/land.md +20 -0
- package/skills/fde/references/options-analysis.md +10 -0
- package/skills/fde/references/plan.md +10 -0
- package/skills/fde/references/scope-defense.md +10 -0
- package/skills/fde/references/ship.md +10 -0
- package/skills/fde/references/stakeholder-radar.md +21 -0
- package/skills/fde/references/status.md +12 -2
- package/templates/.fde/delivery.md +3 -3
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
[](LICENSE)
|
|
8
8
|
[](https://nodejs.org)
|
|
9
9
|
|
|
10
|
-
**Memory + methodology + skills, in one kit.** Skill packs teach your AI agent how to build
|
|
10
|
+
**Memory + methodology + skills, in one kit.** Skill packs - BMAD, Spec-Kit, [mattpocock/skills](https://github.com/mattpocock/skills) - teach your AI coding agent how to *build*. None of them remember who the client is, what you promised them, or who agreed it was delivered. FDEOps adds the missing layer: a private fieldbook per engagement (`.fde/`), a field methodology (land → close), and one `@fde` skill that routes it all.
|
|
11
11
|
|
|
12
12
|
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, yours).
|
|
13
13
|
|
|
@@ -19,69 +19,82 @@ Built for Forward Deployed Engineers, and anyone embedded in client work: consul
|
|
|
19
19
|
written as a side effect of the work
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
Talk in plain language with `@fde`. The AI coding agent runs the plumbing. You confirm anything that enters the record.
|
|
23
23
|
|
|
24
24
|
---
|
|
25
25
|
|
|
26
26
|
## The week
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
`@fde` plus English. No cheat sheet.
|
|
29
29
|
|
|
30
30
|
| When | What you say | What you get |
|
|
31
31
|
|------|--------------|--------------|
|
|
32
|
-
| **Start of week** |
|
|
33
|
-
| **After a meeting** | `@fde` debrief these notes *(paste or attach
|
|
34
|
-
| **
|
|
35
|
-
| **Before a stakeholder meeting** | `@fde` prep me for tomorrow
|
|
36
|
-
| **
|
|
37
|
-
| **End of week** | `@fde` draft the sponsor update from the record | Status grounded in what
|
|
32
|
+
| **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. |
|
|
33
|
+
| **After a meeting** | `@fde` debrief these notes *(paste or attach)* | Proposed updates. You review, then confirm. |
|
|
34
|
+
| **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/) |
|
|
35
|
+
| **Before a stakeholder meeting** | `@fde` prep me for tomorrow with the sponsor | Brief from what you already logged. |
|
|
36
|
+
| **Scope dispute** | `@fde` when did we agree to drop that? | Dated answers, or a clear gap. |
|
|
37
|
+
| **End of week** | `@fde` draft the sponsor update from the record | Status grounded in what happened. |
|
|
38
38
|
|
|
39
|
-
Same
|
|
39
|
+
Same folder every time: `~/fde-engagements/<client>/.fde/`.
|
|
40
40
|
|
|
41
41
|
---
|
|
42
42
|
|
|
43
|
-
##
|
|
44
|
-
|
|
45
|
-
**1. Install** (pick one)
|
|
43
|
+
## See it
|
|
46
44
|
|
|
47
45
|
```bash
|
|
48
|
-
npx
|
|
46
|
+
npx fdeops demo
|
|
49
47
|
```
|
|
50
48
|
|
|
49
|
+
Real commands on a fake client: messy notes → you confirm → cold reload → prep → receipts → fieldbook page. Nothing of yours is read. Lives in `~/fde-engagements/.demo/`. Remove with `npx fdeops demo --clean`.
|
|
50
|
+
|
|
51
|
+
One recorded session — kickoff notes, next morning, “when did we agree?” weeks later. CLI output; typing pace is staged. Re-record: [`media/record-session.sh`](media/record-session.sh).
|
|
52
|
+
|
|
53
|
+
<p align="center"><img alt="A real fdeops session: messy kickoff notes routed into dated memory after you confirm, then a cold session that already knows the client, a grounded sponsor-meeting brief, and dated receipts" src="media/session.gif" width="900" /></p>
|
|
54
|
+
|
|
55
|
+
Two things a chat window cannot do: **nothing is written until you confirm**, and `<private>` lands sealed as `(private - redacted)` — never in `resume`, `prep`, `receipts`, or the dashboard.
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Quickstart
|
|
60
|
+
|
|
61
|
+
**1. Install.** Claude Code: session hooks (context before you type). Everywhere else: the same fieldbook, loaded when you ask.
|
|
62
|
+
|
|
51
63
|
```text
|
|
52
|
-
/plugin marketplace add suboss87/fdeops
|
|
64
|
+
/plugin marketplace add suboss87/fdeops
|
|
53
65
|
/plugin install fdeops@fdeops
|
|
54
66
|
```
|
|
55
67
|
|
|
56
|
-
**2. Bind once** - inside the client workspace (setup only; not a daily habit):
|
|
57
|
-
|
|
58
68
|
```bash
|
|
59
|
-
npx
|
|
69
|
+
npx skills add suboss87/fdeops # Cursor, Codex, skills-compatible hosts
|
|
60
70
|
```
|
|
61
71
|
|
|
62
|
-
**
|
|
72
|
+
**2. Bind once** in the client workspace:
|
|
63
73
|
|
|
64
74
|
```bash
|
|
65
|
-
npx fdeops resume
|
|
75
|
+
npx fdeops resume --init garvey # ~/fde-engagements/garvey + bind this checkout
|
|
76
|
+
npx fdeops resume # where we are
|
|
66
77
|
```
|
|
67
78
|
|
|
68
|
-
**3. Work**
|
|
79
|
+
**3. Work**
|
|
69
80
|
|
|
70
81
|
```text
|
|
71
|
-
@fde
|
|
82
|
+
@fde New client. Payments platform. They want it live before the Q3 audit.
|
|
72
83
|
```
|
|
73
84
|
|
|
74
|
-
`@fde` routes and
|
|
85
|
+
`@fde` routes and drafts; you confirm judgment. Workflow: [docs/USAGE.md](docs/USAGE.md).
|
|
86
|
+
|
|
87
|
+
Working: `npx fdeops resume` prints this client's phase, trust, and next action. Tomorrow that file is still there. Auto-load at session start is Claude Code; elsewhere you invoke `@fde`.
|
|
75
88
|
|
|
76
89
|
<details>
|
|
77
|
-
<summary
|
|
90
|
+
<summary>Other install paths · scan · env</summary>
|
|
78
91
|
|
|
79
|
-
- **
|
|
80
|
-
- **Local LLMs
|
|
81
|
-
- **
|
|
82
|
-
- **
|
|
83
|
-
- **Requires:**
|
|
84
|
-
- **
|
|
92
|
+
- **Adapters:** `npx fdeops adapters .` — [adapters/](adapters/README.md)
|
|
93
|
+
- **Local LLMs:** load `skills/fde/SKILL.md` — [guide](adapters/LOCAL-LLM.md)
|
|
94
|
+
- **Air-gapped:** `git clone https://github.com/suboss87/fdeops.git && cd fdeops && node bin/install.js`
|
|
95
|
+
- **No install:** `npx fdeops demo` · `npx fdeops scan` (heuristic recon, not findings)
|
|
96
|
+
- **Requires:** Node.js >= 18
|
|
97
|
+
- **Override:** `FDEOPS_ENGAGEMENT` — [docs/install.md](docs/install.md)
|
|
85
98
|
|
|
86
99
|
</details>
|
|
87
100
|
|
|
@@ -89,38 +102,48 @@ npx fdeops resume # prints a short "where we are" for this clien
|
|
|
89
102
|
|
|
90
103
|
## How it works
|
|
91
104
|
|
|
92
|
-
- **You** describe the situation with `@fde` (or plain language once the skill is loaded)
|
|
93
|
-
- **
|
|
94
|
-
- **Local CLI**
|
|
95
|
-
- **
|
|
105
|
+
- **You** describe the situation with `@fde` (or plain language once the skill is loaded).
|
|
106
|
+
- **Hooks (Claude Code)** load where you left off and snapshot on the way out. Other hosts: same CLI and files; you call `@fde` / `resume`.
|
|
107
|
+
- **Local CLI** — writes, receipts, status. Zero model tokens. The AI coding agent runs it; you do not live in the CLI. [docs/USAGE.md](docs/USAGE.md)
|
|
108
|
+
- **Pull (optional)** — FDEOps is the sink. Paste is the daily path. A source MCP you add (Granola, Slack, Notion, …) can fetch text; `@fde connect …` walks config. No push, no sync, no tokens in `.fde/`. [mcp/recipes/](mcp/recipes/)
|
|
109
|
+
|
|
110
|
+
`CLAUDE.md` is how the *code* works. The fieldbook is how the *engagement* works. The record lives at `~/fde-engagements/<client>/.fde/` — not inside any vendor. Change hosts, install `@fde` on the new one, bind if needed, keep talking.
|
|
111
|
+
|
|
112
|
+
<details>
|
|
113
|
+
<summary>Phase verbs (land → close)</summary>
|
|
96
114
|
|
|
97
|
-
|
|
115
|
+
| Verb | When |
|
|
116
|
+
|------|------|
|
|
117
|
+
| **land** | First days — brief, stakeholders, success |
|
|
118
|
+
| **discover** | The brief is wrong — evidence from the repo |
|
|
119
|
+
| **plan** | Sequence backwards from done, PR-sized |
|
|
120
|
+
| **build** | Blast radius, log what shipped |
|
|
121
|
+
| **ship** | Pre-flight, canary, rollback |
|
|
122
|
+
| **close** | Handoff, retro, receipts that survive you |
|
|
98
123
|
|
|
99
|
-
|
|
124
|
+
Overlays (AI, fintech, healthcare, gov) fire on signal. [docs/skills.md](docs/skills.md)
|
|
100
125
|
|
|
101
|
-
|
|
126
|
+
</details>
|
|
102
127
|
|
|
103
|
-
|
|
128
|
+
---
|
|
104
129
|
|
|
105
|
-
|
|
106
|
-
2. Open a workspace already bound with `npx fdeops resume --init <client>` (or bind once if this checkout is new)
|
|
107
|
-
3. Talk with `@fde` or run `npx fdeops resume`
|
|
130
|
+
## The field methods
|
|
108
131
|
|
|
109
|
-
|
|
132
|
+
You never pick one. You describe the situation; `@fde` routes. **37 methods**, six domains — each a method (thinking, artifact, checkpoint), not a tip sheet. [docs/skills.md](docs/skills.md) · [docs/skills-reference.md](docs/skills-reference.md)
|
|
110
133
|
|
|
111
134
|
<details>
|
|
112
|
-
<summary
|
|
135
|
+
<summary>All 37 methods</summary>
|
|
113
136
|
|
|
114
|
-
|
|
|
115
|
-
|
|
116
|
-
| **
|
|
117
|
-
| **
|
|
118
|
-
| **
|
|
119
|
-
| **
|
|
120
|
-
| **
|
|
121
|
-
| **
|
|
137
|
+
| Domain | Methods |
|
|
138
|
+
|--------|---------|
|
|
139
|
+
| **1. Embed & Trust** | [land](skills/fde/references/land.md) · [audit](skills/fde/references/audit.md) · [stakeholder-radar](skills/fde/references/stakeholder-radar.md) · [trust-engineering](skills/fde/references/trust-engineering.md) · [scope-defense](skills/fde/references/scope-defense.md) |
|
|
140
|
+
| **2. Discover & Diagnose** | [discover](skills/fde/references/discover.md) · [assumption-audit](skills/fde/references/assumption-audit.md) · [use-case-scoring](skills/fde/references/use-case-scoring.md) · [sketch](skills/fde/references/sketch.md) |
|
|
141
|
+
| **3. Plan & Align** | [plan](skills/fde/references/plan.md) · [business-case](skills/fde/references/business-case.md) · [options-analysis](skills/fde/references/options-analysis.md) · [initiative-triage](skills/fde/references/initiative-triage.md) |
|
|
142
|
+
| **4. Build & Guard** | [build](skills/fde/references/build.md) · [incremental-build](skills/fde/references/incremental-build.md) · [test-on-legacy](skills/fde/references/test-on-legacy.md) · [blast-radius](skills/fde/references/blast-radius.md) · [debug](skills/fde/references/debug.md) · [rescue](skills/fde/references/rescue.md) · [security-audit](skills/fde/references/security-audit.md) · [observability](skills/fde/references/observability.md) |
|
|
143
|
+
| **5. Ship & Verify** | [ship](skills/fde/references/ship.md) · [review](skills/fde/references/review.md) · [rollback-drill](skills/fde/references/rollback-drill.md) · [qa-live](skills/fde/references/qa-live.md) |
|
|
144
|
+
| **6. Operate & Close** | [status](skills/fde/references/status.md) · [demo-prep](skills/fde/references/demo-prep.md) · [debrief](skills/fde/references/debrief.md) · [exec-narrative](skills/fde/references/exec-narrative.md) · [dashboard](skills/fde/references/dashboard.md) · [multi-customer-ops](skills/fde/references/multi-customer-ops.md) · [close](skills/fde/references/close.md) · [handoff-engineering](skills/fde/references/handoff-engineering.md) · [pattern-extract](skills/fde/references/pattern-extract.md) · [red-team](skills/fde/references/red-team.md) · [ingest](skills/fde/references/ingest.md) · [ingest-connect](skills/fde/references/ingest-connect.md) |
|
|
122
145
|
|
|
123
|
-
Overlays
|
|
146
|
+
Overlays: [ai](skills/fde/references/ai.md) · [artifacts](skills/fde/references/artifacts.md) · [fintech](skills/fde/references/fintech.md) · [healthcare](skills/fde/references/healthcare.md) · [gov](skills/fde/references/gov.md)
|
|
124
147
|
|
|
125
148
|
</details>
|
|
126
149
|
|
|
@@ -128,24 +151,24 @@ Overlays for regulated domains (AI, fintech, healthcare, government) activate on
|
|
|
128
151
|
|
|
129
152
|
## Engagement memory (`.fde/`)
|
|
130
153
|
|
|
131
|
-
|
|
154
|
+
One folder per client. Plain markdown. Grep it, copy it, defend it.
|
|
132
155
|
|
|
133
156
|
| File | Holds |
|
|
134
157
|
|------|-------|
|
|
135
|
-
| `context.md` | Where you are
|
|
136
|
-
| `brief.md` / `success.md` | What they asked
|
|
137
|
-
| `reality.md` / `terrain.md` | The real problem; the
|
|
138
|
-
| `stakeholders.md` |
|
|
158
|
+
| `context.md` | Where you are |
|
|
159
|
+
| `brief.md` / `success.md` | What they asked; what “done” is and who signs |
|
|
160
|
+
| `reality.md` / `terrain.md` | The real problem; the map |
|
|
161
|
+
| `stakeholders.md` | `[signal:green\|amber\|red]` |
|
|
139
162
|
| `trust-profile.md` | Sacred data, AI policy, approval chain |
|
|
140
|
-
| `decisions.md` / `risks.md` / `delivery.md` |
|
|
163
|
+
| `decisions.md` / `risks.md` / `delivery.md` | Dated choices; live risks; what shipped and how it rolls back |
|
|
141
164
|
|
|
142
|
-
|
|
165
|
+
Schema: [docs/schema.md](docs/schema.md).
|
|
143
166
|
|
|
144
167
|
---
|
|
145
168
|
|
|
146
169
|
## Fieldbook UI
|
|
147
170
|
|
|
148
|
-
|
|
171
|
+
Local HTML: trust, phase, next, the record. `@fde` dashboard, or `npx fdeops dashboard` (`--all` for the portfolio).
|
|
149
172
|
|
|
150
173
|
<p align="center"><img width="1336" height="624" alt="fdeops Fieldbook in the browser" src="https://github.com/user-attachments/assets/5683614c-7730-4a3a-860d-185053a377eb" /></p>
|
|
151
174
|
|
|
@@ -153,54 +176,50 @@ Open the system of record in a browser - trust, phase, next action, and the full
|
|
|
153
176
|
|
|
154
177
|
## Who this is for
|
|
155
178
|
|
|
156
|
-
| You are
|
|
157
|
-
|
|
158
|
-
| **Forward Deployed Engineer** | The
|
|
159
|
-
| **Consultant
|
|
160
|
-
| **Solutions architect
|
|
161
|
-
| **Agency
|
|
162
|
-
| **Fractional CTO
|
|
179
|
+
| You are | What this is |
|
|
180
|
+
|---------|----------------|
|
|
181
|
+
| **Forward Deployed Engineer** | The job this was built for — first meeting through handoff |
|
|
182
|
+
| **Consultant / contractor on site** | The engagement stops resetting every morning |
|
|
183
|
+
| **Solutions architect** | Politics and architecture in the same record |
|
|
184
|
+
| **Agency, 3–5 clients** | One `.fde/` each — they stop blurring |
|
|
185
|
+
| **Fractional CTO on client work** | System of record for the embed, and the billable trail |
|
|
163
186
|
|
|
164
187
|
---
|
|
165
188
|
|
|
166
189
|
## Your data stays yours
|
|
167
190
|
|
|
168
|
-
- **Local only.**
|
|
169
|
-
- **Plain markdown.** No database
|
|
170
|
-
- **No new data path.** The
|
|
171
|
-
- **Nothing
|
|
172
|
-
- **Know
|
|
191
|
+
- **Local only.** `git` + files. No network, no telemetry, no account. Air-gapped is fine.
|
|
192
|
+
- **Plain markdown.** No database.
|
|
193
|
+
- **No new data path.** The model sees client code only when you point the AI coding agent at it. `<private>` is redacted from CLI, dashboard, and hooks — do not open raw private blocks with file tools.
|
|
194
|
+
- **Nothing unreviewed.** Draft → you confirm. `fde debrief --dry-run` shows routing first.
|
|
195
|
+
- **Know the sync surface.** `~/fde-engagements` is in `$HOME`. iCloud/Dropbox is an NDA incident waiting. `resume --init` warns. [PRIVACY.md](PRIVACY.md) before the first NDA.
|
|
173
196
|
|
|
174
|
-
|
|
197
|
+
[PRIVACY.md](PRIVACY.md) · [SECURITY.md](SECURITY.md)
|
|
175
198
|
|
|
176
199
|
---
|
|
177
200
|
|
|
178
201
|
## Principles
|
|
179
202
|
|
|
180
|
-
- **The artifact is the memory**
|
|
181
|
-
- **Methods, not autonomy**
|
|
182
|
-
- **Brief is a hypothesis**
|
|
183
|
-
- **Evidence on every claim**
|
|
184
|
-
- **One customer, one folder**
|
|
203
|
+
- **The artifact is the memory** — producing the work and recording it are one action
|
|
204
|
+
- **Methods, not autonomy** — the kit says what to check; judgment stays yours
|
|
205
|
+
- **Brief is a hypothesis** — discover before building the wrong thing
|
|
206
|
+
- **Evidence on every claim** — these files get defended in the room
|
|
207
|
+
- **One customer, one folder** — context never bleeds
|
|
185
208
|
|
|
186
209
|
---
|
|
187
210
|
|
|
188
211
|
## Updating
|
|
189
212
|
|
|
190
|
-
|
|
191
|
-
# Plugin / skills install: re-run the install command from Quickstart
|
|
192
|
-
# From a git clone:
|
|
193
|
-
cd fdeops && git pull && node bin/install.js
|
|
194
|
-
```
|
|
213
|
+
Re-run the Quickstart install, or from a clone: `git pull && node bin/install.js`
|
|
195
214
|
|
|
196
215
|
---
|
|
197
216
|
|
|
198
217
|
## Contributing
|
|
199
218
|
|
|
200
|
-
|
|
219
|
+
**[Subash Natarajan](https://www.linkedin.com/in/subashn/)**. [Issues](https://github.com/suboss87/fdeops/issues) · [CONTRIBUTING.md](CONTRIBUTING.md)
|
|
201
220
|
|
|
202
|
-
Thanks to builders whose craft
|
|
221
|
+
Thanks to builders whose craft sharpened the thinking, among them [Andrej Karpathy](https://karpathy.ai/)'s engineering guidelines and the [agentic engineering workflow](https://github.com/pawel-cell/micky-podcast-agentic-engineering) notes from David Ondrej / Michael Shimeles. FDEOps is handcrafted for field work; resemblance is craft, not a fork.
|
|
203
222
|
|
|
204
|
-
**What we won't build:** SaaS sync
|
|
223
|
+
**What we won't build:** SaaS sync; Slack/Notion/Granola connectors or **push** inside the CLI; CRM as core; hardware capture; generic code-craft packs (TDD/review live elsewhere). You may **pull** via *your* MCP. The `fde` CLI stays local-only.
|
|
205
224
|
|
|
206
|
-
[FDE Methodology](FDE-METHODOLOGY.md)
|
|
225
|
+
[FDE Methodology](FDE-METHODOLOGY.md) · [SECURITY.md](SECURITY.md) · [PRIVACY.md](PRIVACY.md) · [Repo layout](docs/REPO_LAYOUT.md) · [Skills matrix](docs/skills.md) · MIT
|
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 (37 methods, 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 the method - the
|
|
34
|
+
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.
|
package/bin/check.js
CHANGED
|
@@ -76,6 +76,29 @@ for (const f of requiredReferences) {
|
|
|
76
76
|
}
|
|
77
77
|
ok('phase references')
|
|
78
78
|
|
|
79
|
+
// A method reference teaches by showing one engagement, not by describing a shape:
|
|
80
|
+
// every judgment-heavy reference carries a worked example that names the memory
|
|
81
|
+
// file the work lands in. Prose-only guidance drifts into advice nobody can apply.
|
|
82
|
+
const exampleReferences = [
|
|
83
|
+
'land.md', 'discover.md', 'plan.md', 'build.md', 'ship.md', 'close.md',
|
|
84
|
+
'status.md', 'stakeholder-radar.md', 'options-analysis.md', 'business-case.md',
|
|
85
|
+
'assumption-audit.md', 'scope-defense.md',
|
|
86
|
+
]
|
|
87
|
+
for (const f of exampleReferences) {
|
|
88
|
+
const p = path.join(root, 'skills', 'fde', 'references', f)
|
|
89
|
+
if (!fs.existsSync(p)) { fail(`missing method reference skills/fde/references/${f}`); continue }
|
|
90
|
+
const body = fs.readFileSync(p, 'utf8')
|
|
91
|
+
const section = (body.match(/\n## Worked example\n([\s\S]*?)(?=\n## |$)/) || [])[1]
|
|
92
|
+
if (!section) {
|
|
93
|
+
fail(`references/${f} missing ## Worked example`)
|
|
94
|
+
} else if (section.trim().length < 400) {
|
|
95
|
+
fail(`references/${f} worked example is too thin to teach anything`)
|
|
96
|
+
} else if (!/`[a-z-]+\.md`/.test(section)) {
|
|
97
|
+
fail(`references/${f} worked example never names the memory file the work lands in`)
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
ok(`worked examples (${exampleReferences.length} references)`)
|
|
101
|
+
|
|
79
102
|
const router = read('skills/fde/SKILL.md')
|
|
80
103
|
if (!router.includes('memory contract')) fail('SKILL.md must define the memory contract')
|
|
81
104
|
// every references/<name>.md the router mentions must exist
|
|
@@ -88,6 +111,94 @@ for (const refFile of mentioned) {
|
|
|
88
111
|
}
|
|
89
112
|
ok(`router dispatch (${mentioned.length} reference targets verified) + memory contract`)
|
|
90
113
|
|
|
114
|
+
// Public claims must match the router. The docs advertise a method count and a
|
|
115
|
+
// per-domain list; both drifted from SKILL.md once (ingest / ingest-connect
|
|
116
|
+
// routed but undocumented), and a number nobody can verify is worse than none.
|
|
117
|
+
{
|
|
118
|
+
// Every routing row must parse. A row this misses is a method that could go
|
|
119
|
+
// undocumented for free, so an unparsed row is a hard failure, not a silent skip.
|
|
120
|
+
const routed = new Set()
|
|
121
|
+
const routing = (router.split('## Routing - 6 domains')[1] || '').split('**Overlays')[0]
|
|
122
|
+
const methodCell = line => (line.split('|')[2] || '').trim().replace(/\s*\([^)]*\)\s*$/, '')
|
|
123
|
+
for (const line of routing.split('\n')) {
|
|
124
|
+
if (!/^\|/.test(line)) continue
|
|
125
|
+
// A row that names a method but no reference would route that method while
|
|
126
|
+
// nothing requires anyone to document it - shape checks can only police rows
|
|
127
|
+
// they recognise, so name a method here and you must name its reference.
|
|
128
|
+
if (!/references\/[a-z0-9-]+\.md/.test(line)) {
|
|
129
|
+
const orphan = methodCell(line)
|
|
130
|
+
// a method name, not a `-` placeholder (CLI-only rows) or a `---` separator
|
|
131
|
+
if (/^[a-z][a-z0-9]*(-[a-z0-9]+)*$/.test(orphan)) {
|
|
132
|
+
fail(`SKILL.md routes '${orphan}' without naming a reference: ${line.trim().slice(0, 80)}`)
|
|
133
|
+
}
|
|
134
|
+
continue
|
|
135
|
+
}
|
|
136
|
+
// Candidate rows are selected on the reference name in ANY form, then the
|
|
137
|
+
// shape is enforced - a row this cannot read must fail, never be skipped,
|
|
138
|
+
// or a method could go undocumented by being written unusually.
|
|
139
|
+
if (!/`references\/[a-z0-9-]+\.md`/.test(line)) {
|
|
140
|
+
fail(`SKILL.md routing row must name its reference as \`references/<name>.md\`: ${line.trim().slice(0, 80)}`)
|
|
141
|
+
continue
|
|
142
|
+
}
|
|
143
|
+
// | You hear | <method> | <cell mentioning references/*.md> |
|
|
144
|
+
const method = methodCell(line)
|
|
145
|
+
if (!/^[a-z0-9-]+$/.test(method)) {
|
|
146
|
+
fail(`check.js cannot read the method name in a SKILL.md routing row: ${line.trim().slice(0, 80)}`)
|
|
147
|
+
continue
|
|
148
|
+
}
|
|
149
|
+
routed.add(method)
|
|
150
|
+
}
|
|
151
|
+
if (!routed.size) fail('check.js could not parse the SKILL.md routing table')
|
|
152
|
+
|
|
153
|
+
// docs/skills-reference.md is the canonical per-method list: one row per
|
|
154
|
+
// method inside the six domain tables, ending at the Overlays section.
|
|
155
|
+
const reference = read('docs/skills-reference.md')
|
|
156
|
+
const documented = new Set()
|
|
157
|
+
let documentedRows = 0
|
|
158
|
+
for (const line of reference.split('### Overlays')[0].split('\n')) {
|
|
159
|
+
const m = line.match(/^\|\s*\[([a-z0-9-]+)\]\(\.\.\/skills\/fde\/references\/([a-z0-9-]+\.md)\)/)
|
|
160
|
+
if (!m) continue
|
|
161
|
+
documentedRows++
|
|
162
|
+
documented.add(m[1])
|
|
163
|
+
// A link nobody followed is the same unverifiable claim this gate exists for:
|
|
164
|
+
// the target must exist, and it must be the method the text names.
|
|
165
|
+
if (m[2] !== `${m[1]}.md`) {
|
|
166
|
+
fail(`docs/skills-reference.md links [${m[1]}] at references/${m[2]}`)
|
|
167
|
+
} else if (!fs.existsSync(path.join(root, 'skills', 'fde', 'references', m[2]))) {
|
|
168
|
+
fail(`docs/skills-reference.md links references/${m[2]}, which does not exist`)
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
if (documented.size !== documentedRows) {
|
|
172
|
+
fail(`docs/skills-reference.md lists ${documentedRows} method rows for ${documented.size} methods - a duplicate row inflates the count`)
|
|
173
|
+
}
|
|
174
|
+
const undocumented = [...routed].filter(name => !documented.has(name))
|
|
175
|
+
if (undocumented.length) {
|
|
176
|
+
fail(`SKILL.md routes skill(s) missing from docs/skills-reference.md: ${undocumented.join(', ')}`)
|
|
177
|
+
}
|
|
178
|
+
// and the other direction: a documented method nothing routes to is a method
|
|
179
|
+
// the agent can never reach, advertised anyway.
|
|
180
|
+
const unrouted = [...documented].filter(name => !routed.has(name))
|
|
181
|
+
if (unrouted.length) {
|
|
182
|
+
fail(`docs/skills-reference.md documents method(s) SKILL.md never routes to: ${unrouted.join(', ')}`)
|
|
183
|
+
}
|
|
184
|
+
for (const rel of ['docs/skills.md', 'docs/skills-reference.md']) {
|
|
185
|
+
const body = read(rel)
|
|
186
|
+
// `-` is a word boundary, so \bingest\b matches inside `ingest-connect`:
|
|
187
|
+
// a method could disappear from the docs behind a hyphenated sibling.
|
|
188
|
+
const absent = [...documented].filter(name => !new RegExp(`(?<![\\w-])${name}(?![\\w-])`).test(body))
|
|
189
|
+
if (absent.length) fail(`${rel} does not list method(s): ${absent.join(', ')}`)
|
|
190
|
+
// every count claim, not just the first: an earlier sentence must not shadow
|
|
191
|
+
// a stale headline (or the reverse).
|
|
192
|
+
const claims = [...body.matchAll(/(\d+)\s+methods/g)].map(m => Number(m[1]))
|
|
193
|
+
if (!claims.length) fail(`${rel} must state how many methods it documents`)
|
|
194
|
+
const wrong = [...new Set(claims.filter(n => n !== documented.size))]
|
|
195
|
+
if (wrong.length) {
|
|
196
|
+
fail(`${rel} claims ${wrong.join('/')} methods; ${documented.size} are documented`)
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
ok(`public method count is verifiable (${documented.size} documented, ${routed.size} routed)`)
|
|
200
|
+
}
|
|
201
|
+
|
|
91
202
|
const install = read('bin/install.js')
|
|
92
203
|
if (install.includes('scaffoldFdeInProject(process.cwd())')) {
|
|
93
204
|
fail('install.js must not auto-scaffold .fde in customer cwd')
|
|
@@ -104,8 +215,34 @@ if (read('package.json').includes('postinstall')) {
|
|
|
104
215
|
}
|
|
105
216
|
|
|
106
217
|
const readme = read('README.md')
|
|
107
|
-
|
|
108
|
-
|
|
218
|
+
// The README shows one recording: media/session.gif, a real CLI session.
|
|
219
|
+
// media/demo.gif is a hand-typed mock kept for history - it must never be embedded,
|
|
220
|
+
// or the front door shows output no command actually produced.
|
|
221
|
+
if (readme.includes('demo.gif')) fail('README must not embed media/demo.gif (staged mock, not real CLI output)')
|
|
222
|
+
else ok('README no staged demo gif')
|
|
223
|
+
|
|
224
|
+
if (!readme.includes('media/session.gif')) {
|
|
225
|
+
fail('README must embed media/session.gif (the recorded session is the front door)')
|
|
226
|
+
} else if (!readme.includes('media/record-session.sh')) {
|
|
227
|
+
fail('README must link media/record-session.sh next to the recording, so it can be re-recorded')
|
|
228
|
+
} else {
|
|
229
|
+
const gifPath = path.join(root, 'media', 'session.gif')
|
|
230
|
+
const rec = path.join(root, 'media', 'record-session.sh')
|
|
231
|
+
if (!fs.existsSync(gifPath) || fs.statSync(gifPath).size < 50000) fail('media/session.gif missing or too small')
|
|
232
|
+
else if (!fs.existsSync(rec)) fail('media/record-session.sh missing - the recording must be reproducible')
|
|
233
|
+
else if (!fs.existsSync(path.join(root, 'media', 'session.cast'))) fail('media/session.cast missing - keep the source recording next to the gif')
|
|
234
|
+
else ok('README recorded session (gif + reproducible recorder + cast)')
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Every repo-relative README link and image must resolve, or the front door 404s.
|
|
238
|
+
const brokenLinks = []
|
|
239
|
+
for (const m of readme.matchAll(/(?:\]\(|src=")([^)"#\s]+)(?:\)|")/g)) {
|
|
240
|
+
const target = m[1]
|
|
241
|
+
if (/^(https?:|mailto:|#|\/)/.test(target)) continue
|
|
242
|
+
if (!fs.existsSync(path.join(root, target))) brokenLinks.push(target)
|
|
243
|
+
}
|
|
244
|
+
if (brokenLinks.length) fail(`README links to missing paths: ${brokenLinks.join(', ')}`)
|
|
245
|
+
else ok('README links all resolve')
|
|
109
246
|
|
|
110
247
|
for (const section of [
|
|
111
248
|
'How it works',
|
|
@@ -180,7 +317,15 @@ if (!fs.existsSync(path.join(root, 'docs', 'schema.md'))) fail('docs/schema.md m
|
|
|
180
317
|
else ok('docs/schema.md')
|
|
181
318
|
|
|
182
319
|
if (!fs.existsSync(path.join(root, 'SECURITY.md'))) fail('SECURITY.md missing')
|
|
183
|
-
else
|
|
320
|
+
else {
|
|
321
|
+
// A reporting section that only offers GitHub Security Advisories is a dead end
|
|
322
|
+
// whenever private reporting is off for the repo (that page 403s), which is how
|
|
323
|
+
// issue #9 sat unreported. Require a channel that does not depend on a repo setting.
|
|
324
|
+
const sec = read('SECURITY.md')
|
|
325
|
+
if (!/[\w.+-]+@[\w-]+\.[\w.]+/.test(sec)) {
|
|
326
|
+
fail('SECURITY.md must give a reporting channel that works when GitHub private reporting is off (an email address)')
|
|
327
|
+
} else ok('SECURITY.md (reachable reporting channel)')
|
|
328
|
+
}
|
|
184
329
|
|
|
185
330
|
const exampleFiles = ['reality.md', 'decisions.md', 'delivery.md', 'stakeholders.md', 'assumptions.md']
|
|
186
331
|
for (const f of exampleFiles) {
|
|
@@ -354,13 +499,75 @@ if (!fs.existsSync(path.join(root, 'mcp', 'fdeops-ingest', 'server.js'))) {
|
|
|
354
499
|
} else if (!fs.existsSync(path.join(root, 'skills', 'fde', 'references', 'ingest-connect.md'))) {
|
|
355
500
|
fail('skills/fde/references/ingest-connect.md missing')
|
|
356
501
|
} else {
|
|
357
|
-
for (const recipe of ['file.md', 'granola.md', 'notion.md']) {
|
|
502
|
+
for (const recipe of ['file.md', 'granola.md', 'slack.md', 'notion.md']) {
|
|
358
503
|
if (!fs.existsSync(path.join(root, 'mcp', 'recipes', recipe))) fail(`mcp/recipes/${recipe} missing`)
|
|
359
504
|
}
|
|
360
505
|
if (!read('README.md').includes('mcp/recipes')) fail('README must point at mcp/recipes for connect clarity')
|
|
361
506
|
ok('ingest MCP + connect recipes + skill reference')
|
|
362
507
|
}
|
|
363
508
|
|
|
509
|
+
// Agent Plugins 1.0.0 conformance (agent-plugins.org/specification).
|
|
510
|
+
// plugin.json has a closed schema: an illegal field is fatal to the whole plugin,
|
|
511
|
+
// and an mcp.json whose $schema version differs from plugin.json silently disables MCP.
|
|
512
|
+
const AP_VERSION = '1.0.0'
|
|
513
|
+
const AP_PLUGIN_SCHEMA = `https://agent-plugins.org/schemas/${AP_VERSION}/plugin.schema.json`
|
|
514
|
+
const AP_MCP_SCHEMA = `https://agent-plugins.org/schemas/${AP_VERSION}/mcp.schema.json`
|
|
515
|
+
const AP_MANIFEST_FIELDS = [
|
|
516
|
+
'$schema', 'name', 'version', 'description', 'author',
|
|
517
|
+
'homepage', 'repository', 'license', 'keywords', 'extensions',
|
|
518
|
+
]
|
|
519
|
+
|
|
520
|
+
const apManifest = JSON.parse(read('plugin.json'))
|
|
521
|
+
const apUnknown = Object.keys(apManifest).filter(k => !AP_MANIFEST_FIELDS.includes(k))
|
|
522
|
+
if (apManifest.$schema !== AP_PLUGIN_SCHEMA) {
|
|
523
|
+
fail(`plugin.json $schema must be ${AP_PLUGIN_SCHEMA}`)
|
|
524
|
+
} else if (apUnknown.length) {
|
|
525
|
+
fail(`plugin.json has non-portable field(s) ${apUnknown.join(', ')} - client-specific data belongs under extensions`)
|
|
526
|
+
} else if (!/^(?!.*(?:--|\.\.))[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?$/.test(apManifest.name)) {
|
|
527
|
+
fail(`plugin.json name "${apManifest.name}" violates Agent Plugins name constraints`)
|
|
528
|
+
} else if (apManifest.version !== pkg.version) {
|
|
529
|
+
fail(`version mismatch package.json ${pkg.version} vs plugin.json ${apManifest.version}`)
|
|
530
|
+
} else ok('agent plugins manifest')
|
|
531
|
+
|
|
532
|
+
const apMcp = JSON.parse(read('mcp.json'))
|
|
533
|
+
const apServers = apMcp.mcpServers || {}
|
|
534
|
+
if (apMcp.$schema !== AP_MCP_SCHEMA) {
|
|
535
|
+
fail(`mcp.json $schema must be ${AP_MCP_SCHEMA} (a version mismatch with plugin.json disables MCP)`)
|
|
536
|
+
} else if (Object.keys(apMcp).some(k => k !== '$schema' && k !== 'mcpServers')) {
|
|
537
|
+
fail('mcp.json permits only $schema and mcpServers')
|
|
538
|
+
} else {
|
|
539
|
+
for (const [name, srv] of Object.entries(apServers)) {
|
|
540
|
+
if (srv.type !== 'stdio') fail(`mcp.json ${name}: fdeops ships stdio servers only (local-only core)`)
|
|
541
|
+
else if (!srv.command || (/[\s/]/.test(srv.command) && !srv.command.startsWith('./'))) {
|
|
542
|
+
fail(`mcp.json ${name}: command must be one executable token or a ./-relative path`)
|
|
543
|
+
} else if ((srv.args || []).some(a => path.isAbsolute(a))) {
|
|
544
|
+
fail(`mcp.json ${name}: args must use \${PLUGIN_ROOT}, never an absolute path`)
|
|
545
|
+
} else if (Object.keys(srv.env || {}).some(k => k === 'PLUGIN_ROOT' || k === 'PLUGIN_DATA')) {
|
|
546
|
+
fail(`mcp.json ${name}: env must not set PLUGIN_ROOT/PLUGIN_DATA (client-supplied)`)
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
const ingest = apServers['fdeops-ingest']
|
|
550
|
+
const rootArg = ingest && (ingest.args || []).find(a => String(a).startsWith('${PLUGIN_ROOT}/'))
|
|
551
|
+
const target = rootArg ? String(rootArg).replace('${PLUGIN_ROOT}/', '') : ''
|
|
552
|
+
if (!ingest) fail('mcp.json must declare the fdeops-ingest stdio server')
|
|
553
|
+
else if (!rootArg) fail('mcp.json fdeops-ingest: args must include a ${PLUGIN_ROOT}-relative server path')
|
|
554
|
+
else if (!fs.existsSync(path.join(root, target))) fail(`mcp.json fdeops-ingest points at missing ${target}`)
|
|
555
|
+
else ok('agent plugins mcp config')
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// A manifest that ships only in git and not in the npm tarball is the worst kind of drift.
|
|
559
|
+
for (const manifest of ['plugin.json', 'mcp.json']) {
|
|
560
|
+
if (!(pkg.files || []).includes(manifest)) fail(`package.json files must include ${manifest}`)
|
|
561
|
+
else ok(`${manifest} published to npm`)
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
for (const entry of fs.readdirSync(path.join(root, 'skills'))) {
|
|
565
|
+
const dir = path.join(root, 'skills', entry)
|
|
566
|
+
if (!fs.statSync(dir).isDirectory()) continue
|
|
567
|
+
if (!fs.existsSync(path.join(dir, 'SKILL.md'))) fail(`skills/${entry}/ has no SKILL.md - clients skip it`)
|
|
568
|
+
else ok(`skill ${entry} discoverable`)
|
|
569
|
+
}
|
|
570
|
+
|
|
364
571
|
if (!fs.existsSync(path.join(root, '.github', 'ISSUE_TEMPLATE', 'bug_report.yml'))) {
|
|
365
572
|
fail('GitHub issue template missing')
|
|
366
573
|
} else ok('issue templates')
|