fdeops 3.9.8 → 3.9.10
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 +12 -2
- package/adapters/README.md +2 -0
- package/bin/fde.js +60 -13
- package/bin/lib/trust.js +10 -1
- package/hooks/session-start +3 -2
- package/package.json +2 -1
- package/skills/fde/SKILL.md +14 -11
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**Your AI coding agent forgets your client every morning. fdeops remembers.**
|
|
4
4
|
|
|
5
|
-
[](https://www.npmjs.com/package/fdeops)
|
|
5
|
+
[](https://www.npmjs.com/package/fdeops)
|
|
6
6
|
[](https://github.com/suboss87/fdeops/actions)
|
|
7
7
|
[](LICENSE)
|
|
8
8
|
[](https://nodejs.org)
|
|
@@ -92,7 +92,17 @@ npx fdeops resume # prints a short "where we are" for this clien
|
|
|
92
92
|
|
|
93
93
|
fdeops complements repo memory: CLAUDE.md holds how the *code* works; the fieldbook holds how the *client engagement* works.
|
|
94
94
|
|
|
95
|
-
|
|
95
|
+
### Switch coding agents anytime
|
|
96
|
+
|
|
97
|
+
The fieldbook lives on disk at `~/fde-engagements/<client>/.fde/` - not inside Claude, Cursor, or any other tool. Change AI coding agents and the **same client record** is still there.
|
|
98
|
+
|
|
99
|
+
On the new tool:
|
|
100
|
+
|
|
101
|
+
1. Install `@fde` for that tool (plugin, `npx skills add suboss87/fdeops`, or `npx fdeops adapters .` - see [adapters/](adapters/README.md))
|
|
102
|
+
2. Open a workspace already bound with `npx fdeops resume --init <client>` (or bind once if this checkout is new)
|
|
103
|
+
3. Talk with `@fde` or run `npx fdeops resume`
|
|
104
|
+
|
|
105
|
+
Same fieldbook. **Claude Code** gets the fullest ride (session start/stop hooks). Elsewhere the memory and CLI are the same; context usually loads when you ask `@fde` / `resume`, not automatically. Details: [docs/install.md](docs/install.md).
|
|
96
106
|
|
|
97
107
|
<details>
|
|
98
108
|
<summary><strong>Phase verbs</strong> (land → close)</summary>
|
package/adapters/README.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
**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.*
|
|
4
4
|
|
|
5
|
+
**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).
|
|
6
|
+
|
|
5
7
|
## What goes where
|
|
6
8
|
|
|
7
9
|
| Tool | File in your engagement workspace | Source template |
|
package/bin/fde.js
CHANGED
|
@@ -541,6 +541,7 @@ const {
|
|
|
541
541
|
nextActionLine,
|
|
542
542
|
computeSignals,
|
|
543
543
|
resumeTriage,
|
|
544
|
+
countOpenRisks,
|
|
544
545
|
} = createTrustApi({
|
|
545
546
|
fs, path, readClean, readEng, parseMdTable, sectionBody, SIGNAL_LEDGER, memoryDirtyManual,
|
|
546
547
|
})
|
|
@@ -921,8 +922,8 @@ function cmdResume(args) {
|
|
|
921
922
|
console.log(`NO ENGAGEMENT for this workspace.\nexisting: ${list}\ncreate + bind one: fde resume --init <client-name>`)
|
|
922
923
|
process.exit(2)
|
|
923
924
|
}
|
|
924
|
-
// Monday-morning
|
|
925
|
-
|
|
925
|
+
// Monday-morning: triage + proactive hygiene (silent when clean), then memory.
|
|
926
|
+
printTriageBlock(eng)
|
|
926
927
|
console.log(`\nENGAGEMENT: ${eng}\n`)
|
|
927
928
|
// readClean, not fs.readFileSync: this output is what an agent loads as
|
|
928
929
|
// context, so it goes through the same <private> redaction as the dashboard.
|
|
@@ -1046,7 +1047,18 @@ function setContextPhase(eng, phase) {
|
|
|
1046
1047
|
md = md.replace(/\*\*Last updated:\*\*\s*.*/i, `**Last updated:** ${today}`)
|
|
1047
1048
|
}
|
|
1048
1049
|
withFileLock(p, () => { atomicWriteFile(p, md.endsWith('\n') ? md : md + '\n') })
|
|
1049
|
-
|
|
1050
|
+
const hash = commitMemory(eng, `phase ${phase}`, { files: ['context.md'] })
|
|
1051
|
+
// Proactive warn at the moment it matters - don't wait for Monday hygiene.
|
|
1052
|
+
if (phase === 'ship' || phase === 'close') {
|
|
1053
|
+
const open = countOpenRisks(eng)
|
|
1054
|
+
if (open > 0) {
|
|
1055
|
+
process.stderr.write(
|
|
1056
|
+
`⚠ phase → ${phase} with ${open} open risk(s) still live - retire, hand off, or keep them intentional\n` +
|
|
1057
|
+
' say "@fde clean up the fieldbook" to walk the list (or: fde doctor)\n'
|
|
1058
|
+
)
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
return hash
|
|
1050
1062
|
}
|
|
1051
1063
|
|
|
1052
1064
|
|
|
@@ -1341,7 +1353,8 @@ function cmdTriage() {
|
|
|
1341
1353
|
console.error('no engagement - run: fde resume --init <name>')
|
|
1342
1354
|
process.exit(2)
|
|
1343
1355
|
}
|
|
1344
|
-
|
|
1356
|
+
// Session-start hooks call this - hygiene is proactive here (silent when clean).
|
|
1357
|
+
printTriageBlock(eng)
|
|
1345
1358
|
const owner = readOwner(eng) || writeOwnerIfMissing(eng)
|
|
1346
1359
|
const head = memoryHead(eng)
|
|
1347
1360
|
if (owner || head) {
|
|
@@ -1417,18 +1430,29 @@ function findDuplicateOpenRisks(eng) {
|
|
|
1417
1430
|
return [...byKey.values()].filter(g => g.length >= 2)
|
|
1418
1431
|
}
|
|
1419
1432
|
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1433
|
+
// Deterministic fieldbook hygiene - shared by doctor + session TRIAGE.
|
|
1434
|
+
// Silent when clean OR brand-new (no dated work yet). Never auto-rewrites.
|
|
1435
|
+
// High-value moments: week-start (via triage), ship/close, after real work accrues.
|
|
1436
|
+
function collectDoctorIssues(eng) {
|
|
1423
1437
|
const issues = []
|
|
1424
1438
|
const s = computeSignals(eng)
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1439
|
+
const datedBlob = [
|
|
1440
|
+
readEng(eng, 'decisions.md'), readEng(eng, 'delivery.md'),
|
|
1441
|
+
readEng(eng, 'risks.md'), readEng(eng, 'stakeholders.md'),
|
|
1442
|
+
].join('\n')
|
|
1443
|
+
const hasDatedWork = /\[\d{4}-\d{2}-\d{2}\]/.test(datedBlob)
|
|
1444
|
+
// Day-1 empty templates are not hygiene failures - nagging there trains people to ignore doctor.
|
|
1445
|
+
const fresh = !hasDatedWork && (s.phase === '?' || s.phase === 'unset') && !s.openRisks
|
|
1446
|
+
|
|
1447
|
+
if (s.memoryWarn) issues.push(s.memoryWarn)
|
|
1448
|
+
if (fresh) return issues
|
|
1449
|
+
|
|
1450
|
+
if (s.phase === '?' || s.phase === 'unset') {
|
|
1451
|
+
if (hasDatedWork) {
|
|
1452
|
+
issues.push('phase is unset but dated work exists - run: fde log phase <land|discover|plan|build|ship|close>')
|
|
1453
|
+
}
|
|
1429
1454
|
}
|
|
1430
1455
|
if (s.stale) issues.push(`trust signal is STALE (${s.signalAge}d) - reconfirm with fde log contact ... --signal`)
|
|
1431
|
-
if (s.memoryWarn) issues.push(s.memoryWarn)
|
|
1432
1456
|
if (!readOwner(eng)) issues.push('no .owner - run any write or: fde owner set you@firm.com')
|
|
1433
1457
|
if (!fs.existsSync(path.join(eng, '.git'))) issues.push('memory not git-versioned - next write will init, or re-run resume --init')
|
|
1434
1458
|
const success = readClean(eng, 'success.md')
|
|
@@ -1448,8 +1472,31 @@ function cmdDoctor() {
|
|
|
1448
1472
|
`${dupes.length} duplicate open-risk cluster(s) (e.g. "${sample}${sample.length >= 60 ? '…' : ''}") - consolidate or retire echoes in risks.md`
|
|
1449
1473
|
)
|
|
1450
1474
|
}
|
|
1451
|
-
|
|
1475
|
+
return issues
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
// Lean line for session-start TRIAGE - count + top issue + NL cue. Omitted when clean.
|
|
1479
|
+
function hygieneTriageLines(eng) {
|
|
1480
|
+
const issues = collectDoctorIssues(eng)
|
|
1481
|
+
if (!issues.length) return []
|
|
1482
|
+
const top = issues[0].replace(/\s+/g, ' ').trim().slice(0, 72)
|
|
1483
|
+
return [
|
|
1484
|
+
` hygiene: ${issues.length} issue(s) — ${top}${issues[0].length > 72 ? '…' : ''}`,
|
|
1485
|
+
' → say "@fde clean up the fieldbook" when ready (agent runs fde doctor; nothing auto-rewrites)',
|
|
1486
|
+
]
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
function printTriageBlock(eng) {
|
|
1452
1490
|
console.log(resumeTriage(eng))
|
|
1491
|
+
for (const line of hygieneTriageLines(eng)) console.log(line)
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
function cmdDoctor() {
|
|
1495
|
+
const eng = resolveEngagement()
|
|
1496
|
+
if (!eng) { console.error('no engagement - run: fde resume --init <name>'); process.exit(2) }
|
|
1497
|
+
const issues = collectDoctorIssues(eng)
|
|
1498
|
+
console.log(`FDE DOCTOR - ${engagementSlugFromPath(eng)}`)
|
|
1499
|
+
printTriageBlock(eng)
|
|
1453
1500
|
if (!issues.length) {
|
|
1454
1501
|
console.log('\nOK - no structural issues (judgment still yours)')
|
|
1455
1502
|
process.exit(0)
|
package/bin/lib/trust.js
CHANGED
|
@@ -70,7 +70,16 @@ function createTrustApi(deps) {
|
|
|
70
70
|
const t = raw.trim()
|
|
71
71
|
if (!t || t.startsWith('<!--') || /^#{1,6}\s/.test(t)) continue
|
|
72
72
|
if (/risk\s*\|\s*status|mitigation/i.test(t) || /^\|?[\s|:-]+$/.test(t)) continue
|
|
73
|
-
|
|
73
|
+
// Bullet risk with substance (skip empty "- " stubs).
|
|
74
|
+
if (/^[-*]/.test(t)) {
|
|
75
|
+
if (t.replace(/^[-*]\s+/, '').trim()) n++
|
|
76
|
+
continue
|
|
77
|
+
}
|
|
78
|
+
// Table row: first cell must have risk text (day-1 "| | open | |" placeholders don't count).
|
|
79
|
+
if (/^\|/.test(t) && t.length > 12) {
|
|
80
|
+
const riskCell = t.split('|').map(c => c.trim())[1] || ''
|
|
81
|
+
if (riskCell) n++
|
|
82
|
+
}
|
|
74
83
|
}
|
|
75
84
|
return n
|
|
76
85
|
}
|
package/hooks/session-start
CHANGED
|
@@ -138,8 +138,9 @@ CONTENT=""
|
|
|
138
138
|
# Lean pointer only - never cat SKILL.md. Methods load on @fde / skill trigger.
|
|
139
139
|
CONTENT="${CONTENT}fdeops: engagement fieldbook active. Human speaks plain language with @fde - you (the agent) run the local fde CLI for memory plumbing; never ask the human to type fde commands. Load skills/fde/SKILL.md when @fde triggers.\n\n"
|
|
140
140
|
|
|
141
|
-
# Same TRIAGE block as `fde resume` / `fde triage`
|
|
142
|
-
#
|
|
141
|
+
# Same TRIAGE block as `fde resume` / `fde triage` (includes proactive hygiene
|
|
142
|
+
# when the fieldbook has doctor issues; silent when clean). Monday morning must
|
|
143
|
+
# not depend on the model remembering to run a CLI command. Prefer the installed
|
|
143
144
|
# fde binary; fall back to the plugin/repo copy of bin/fde.js.
|
|
144
145
|
resolve_fde() {
|
|
145
146
|
if command -v fde >/dev/null 2>&1; then
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fdeops",
|
|
3
|
-
"version": "3.9.
|
|
3
|
+
"version": "3.9.10",
|
|
4
4
|
"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.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"fdeops": "bin/install.js",
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
"scripts": {
|
|
10
10
|
"check": "node bin/check.js && npm test",
|
|
11
11
|
"test": "node --test test/*.test.js",
|
|
12
|
+
"test:skill-routing": "node evals/skill-routing/check.js && node evals/skill-routing/live-smoke.js",
|
|
12
13
|
"prepublishOnly": "node bin/check.js && npm test"
|
|
13
14
|
},
|
|
14
15
|
"files": [
|
package/skills/fde/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: fde
|
|
3
|
-
description:
|
|
3
|
+
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.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# @fde
|
|
@@ -74,8 +74,10 @@ When NOT to interrogate or challenge: unambiguous one-liners, mechanical ops, FD
|
|
|
74
74
|
| "Prep me for the meeting with …" / walk-in brief | `fde prep "<short label>"` - present the brief in plain language; do not invent facts missing from `.fde/` |
|
|
75
75
|
| "When did we agree…?" / scope dispute | `fde receipts <term>` - answer with dates; no hit = gap, not proof |
|
|
76
76
|
| "Draft the sponsor update" / how are we doing | `fde status` then follow `references/status.md` for the narrative |
|
|
77
|
-
| "Log that they went quiet" / trust signal | `fde log contact "…" --signal amber\|green\|red
|
|
77
|
+
| "Log that they went quiet" / trust signal | `fde log contact "…" --signal amber\|green\|red`. If they already named the color ("log that as amber"), that is the confirm — write it. If they only described the situation, playback the color once, then write. |
|
|
78
78
|
| Want the HTML fieldbook | `fde dashboard` |
|
|
79
|
+
| "Clean up the fieldbook" / hygiene / memory feels messy | `fde doctor` - walk issues in plain language; propose fixes; never auto-rewrite without confirm. Contradictions need judgment (brief vs reality) - doctor is structural; you handle meaning. |
|
|
80
|
+
| "Scrub this secret / redact that token" (buried line, not just last write) | `fde redact <term>` preview, then `fde redact <term> --apply` after confirm. Undo is last-write only; redact is for buried lines. Remind them to rotate the real credential. |
|
|
79
81
|
|
|
80
82
|
**The debrief verb.** Highest-frequency loop. When the FDE shares notes or says "debrief": **you** run the smart path (write notes to a temp file if needed). Show the proposed routing in plain language. Only `--apply` (or pipe prefixed lines) after they confirm. Never ask them to run the CLI. Detail: `references/debrief.md`.
|
|
81
83
|
|
|
@@ -85,23 +87,24 @@ CLI missing → use the manual fallbacks inside each reference (still you write
|
|
|
85
87
|
|
|
86
88
|
## Proactive intelligence (run on every session start)
|
|
87
89
|
|
|
88
|
-
|
|
90
|
+
Session-start already injects **TRIAGE** (deterministic, zero model tokens). When the fieldbook is dirty, TRIAGE includes a `hygiene:` line - that is the proactive doctor. Silent when clean.
|
|
91
|
+
|
|
92
|
+
After you see TRIAGE + bounded `context.md`, open with a brief state playback - like a senior colleague who reviewed the file before the meeting started.
|
|
89
93
|
|
|
90
94
|
**Always open with a 2-3 line state summary:**
|
|
91
95
|
|
|
92
96
|
> "Last session you shipped the payment retry slice. Plan is 3/5 tasks done. Denise saw the demo Tuesday - signal is green. One thing worth noting: [finding, or 'nothing flagged - where do you want to pick up?']"
|
|
93
97
|
|
|
94
|
-
**What to
|
|
98
|
+
**What to surface (in order, at most ONE finding):**
|
|
95
99
|
|
|
96
|
-
1. **
|
|
97
|
-
2.
|
|
98
|
-
3.
|
|
99
|
-
4. **Contradictions between files.** Reality.md vs. brief.md. Delivery.md vs. success.md.
|
|
100
|
+
1. **If TRIAGE has `hygiene:`** - that is the finding. Offer: "Fieldbook has N hygiene issues - want me to walk them?" On yes: run `fde doctor`, explain in plain language, propose fixes; never auto-rewrite.
|
|
101
|
+
2. Else optionally note: artifact staleness, open risks overdue, or brief↔reality tension - only if it changes today's move.
|
|
102
|
+
3. If nothing flagged: one line, ask where to pick up.
|
|
100
103
|
|
|
101
104
|
**Rules:**
|
|
102
|
-
-
|
|
103
|
-
-
|
|
104
|
-
-
|
|
105
|
+
- Don't re-run a second invented audit when hygiene already spoke.
|
|
106
|
+
- Don't barrage. Don't accuse. Don't rewrite memory without confirm.
|
|
107
|
+
- Full contradiction cleanup ("audit the sources before trusting the index") is an `@fde` conversation - doctor is the structural gate; you supply judgment.
|
|
105
108
|
- If the concern is minor and won't change the next 3 moves - skip it.
|
|
106
109
|
|
|
107
110
|
This is what makes fdeops a peer, not a notebook. The peer reviewed the file before you sat down.
|