fdeops 4.0.2 → 4.0.4
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/adapters/AGENTS.md +2 -2
- package/adapters/GEMINI.md +2 -2
- package/adapters/LOCAL-LLM.md +1 -1
- package/adapters/copilot-instructions.md +2 -2
- package/adapters/cursor.fde.mdc +2 -2
- package/bin/fde.js +24 -11
- package/bin/lib/context.js +8 -1
- package/bin/lib/provenance.js +10 -0
- package/mcp/fdeops-ingest/package.json +1 -1
- package/package.json +1 -1
- package/plugin.json +1 -1
- package/skills/fde/SKILL.md +4 -4
package/adapters/AGENTS.md
CHANGED
|
@@ -14,9 +14,9 @@ Do **not** load `@fde` for a one-line typo in an unbound repo. On a bound client
|
|
|
14
14
|
|
|
15
15
|
## Engagement memory
|
|
16
16
|
|
|
17
|
-
Read and write engagement files under the workspace's bound engagement:
|
|
17
|
+
Read and write engagement files under the workspace's bound engagement: follow the skill’s entry rule to resolve it (binding created once with `fde resume --init <name>`; default `~/fde-engagements/<name>/.fde/`). `FDEOPS_ENGAGEMENT` (expand `~`) overrides when set. Use `./.fde/` only when the engagement approves it and it is gitignored.
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
Follow **First-use preferences** and **Entry (every session)** in `skills/fde/SKILL.md` for setup, context reuse and refresh. Use the CLI for deterministic work - `fde scan | log | receipts | status | dashboard` - instead of improvising shell.
|
|
20
20
|
|
|
21
21
|
## Voice
|
|
22
22
|
|
package/adapters/GEMINI.md
CHANGED
|
@@ -14,9 +14,9 @@ Do **not** load `@fde` for a one-line typo in an unbound repo. On a bound client
|
|
|
14
14
|
|
|
15
15
|
## Engagement memory
|
|
16
16
|
|
|
17
|
-
Read and write engagement files under the workspace's bound engagement:
|
|
17
|
+
Read and write engagement files under the workspace's bound engagement: follow the skill’s entry rule to resolve it (binding created once with `fde resume --init <name>`; default `~/fde-engagements/<name>/.fde/`). `FDEOPS_ENGAGEMENT` (expand `~`) overrides when set. Use `./.fde/` only when the engagement approves it and it is gitignored.
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
Follow **First-use preferences** and **Entry (every session)** in `skills/fde/SKILL.md` for setup, context reuse and refresh. Use the CLI for deterministic work - `fde scan | log | receipts | status | dashboard` - instead of improvising shell.
|
|
20
20
|
|
|
21
21
|
## Voice
|
|
22
22
|
|
package/adapters/LOCAL-LLM.md
CHANGED
|
@@ -7,7 +7,7 @@ The FDEOps CLI and offline dashboard need Node.js and Git, not a model. AI-assis
|
|
|
7
7
|
1. Download FDEOps, your agent host and your model while online. After that, the CLI operates offline. Model/provider configuration belongs to the host; FDEOps does not start or configure an inference server.
|
|
8
8
|
2. From the client workspace, run `node /path/to/fdeops/bin/fde.js resume --init my-client` to create and bind a local record.
|
|
9
9
|
3. Make `skills/fde/SKILL.md` and its references available to the host. Use the host's documented skill/file mechanism. Give it the FDEOps CLI path and permission to read the bound record and execute the requested commands.
|
|
10
|
-
4.
|
|
10
|
+
4. Follow **First-use preferences** and **Entry (every session)** in `skills/fde/SKILL.md`, then ask for the next action. Inspect the tool calls, cited records and any proposed writes before trusting the workflow.
|
|
11
11
|
|
|
12
12
|
Use `fde recall <topic>` for relevant evidence. `resume` and `recall` default to a 16 KiB output ceiling; `--max-bytes 4096` requests a smaller allowance. This limits FDEOps output, not the host's entire context window. Keep unrelated transcripts and tools out of the active context. Private blocks must stay out of direct file reads as well as prompts.
|
|
13
13
|
|
|
@@ -14,9 +14,9 @@ Do **not** load `@fde` for a one-line typo in an unbound repo. On a bound client
|
|
|
14
14
|
|
|
15
15
|
## Engagement memory
|
|
16
16
|
|
|
17
|
-
Read and write engagement files under the workspace's bound engagement:
|
|
17
|
+
Read and write engagement files under the workspace's bound engagement: follow the skill’s entry rule to resolve it (binding created once with `fde resume --init <name>`; default `~/fde-engagements/<name>/.fde/`). `FDEOPS_ENGAGEMENT` (expand `~`) overrides when set. Use `./.fde/` only when the engagement approves it and it is gitignored.
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
Follow **First-use preferences** and **Entry (every session)** in `skills/fde/SKILL.md` for setup, context reuse and refresh. Use the CLI for deterministic work - `fde scan | log | receipts | status | dashboard` - instead of improvising shell.
|
|
20
20
|
|
|
21
21
|
## Voice
|
|
22
22
|
|
package/adapters/cursor.fde.mdc
CHANGED
|
@@ -19,9 +19,9 @@ Do **not** load `@fde` for a one-line typo in an unbound repo. On a bound client
|
|
|
19
19
|
|
|
20
20
|
## Engagement memory
|
|
21
21
|
|
|
22
|
-
Read and write engagement files under the workspace's bound engagement:
|
|
22
|
+
Read and write engagement files under the workspace's bound engagement: follow the skill’s entry rule to resolve it (binding created once with `fde resume --init <name>`; default `~/fde-engagements/<name>/.fde/`). `FDEOPS_ENGAGEMENT` (expand `~`) overrides when set. Use `./.fde/` only when the engagement approves it and it is gitignored.
|
|
23
23
|
|
|
24
|
-
**
|
|
24
|
+
Follow **First-use preferences** and **Entry (every session)** in `skills/fde/SKILL.md` for setup, context reuse and refresh. The entry packet includes TRIAGE; use it for trust, phase, open risks and next action. Do not invent stakeholders or status.
|
|
25
25
|
|
|
26
26
|
You run the CLI for deterministic work - `fde scan | log | debrief | prep | doctor | receipts | status | dashboard` - instead of improvising shell or handing commands to the human.
|
|
27
27
|
|
package/bin/fde.js
CHANGED
|
@@ -36,7 +36,7 @@ const { createMemoryApi } = require('./lib/memory')
|
|
|
36
36
|
const { createTrustApi } = require('./lib/trust')
|
|
37
37
|
const vault = require('./lib/vault')
|
|
38
38
|
const context = require('./lib/context')
|
|
39
|
-
const { sourceReference, hasSource, datedDecisions } = require('./lib/provenance')
|
|
39
|
+
const { sourceReference, sourceReferences, hasSource, datedDecisions } = require('./lib/provenance')
|
|
40
40
|
const { deliverySummary } = require('./lib/delivery-gaps')
|
|
41
41
|
|
|
42
42
|
const HOME = os.homedir()
|
|
@@ -67,8 +67,13 @@ function maskReport(value) {
|
|
|
67
67
|
if (value && typeof value === 'object') return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, maskReport(item)]))
|
|
68
68
|
return value
|
|
69
69
|
}
|
|
70
|
-
function maskedSections(sections, maxBytes) {
|
|
71
|
-
|
|
70
|
+
function maskedSections(sections, maxBytes = context.DEFAULT_BYTES, heading = '') {
|
|
71
|
+
// Reserve identity before sharing the remaining budget between excerpts.
|
|
72
|
+
// Pathological path lengths remain explicitly clipped rather than exhausting policy space.
|
|
73
|
+
const maskedHeading = masking.mask(heading)
|
|
74
|
+
const prefix = heading ? (Buffer.byteLength(maskedHeading) <= 1024 ? maskedHeading
|
|
75
|
+
: context.clipMaskedUtf8(maskedHeading, 900) + '\n[Identity path truncated; use fde resume --bind to inspect.]') + '\n\n' : ''
|
|
76
|
+
return prefix + context.boundedSections(sections.map(text => masking.mask(text)), maxBytes - Buffer.byteLength(prefix))
|
|
72
77
|
}
|
|
73
78
|
const DEBRIEF_MAX_BYTES = 256 * 1024
|
|
74
79
|
const CODE_EXT = ['.js', '.ts', '.tsx', '.jsx', '.py', '.java', '.go', '.rb', '.cs', '.php']
|
|
@@ -1459,12 +1464,12 @@ function cmdResume(args) {
|
|
|
1459
1464
|
process.stdout.write(maskedSections([
|
|
1460
1465
|
policy ? `CLIENT POLICY - trust-profile.md\n${policy}` : '',
|
|
1461
1466
|
preferences.work ? `WORKING PREFERENCES: ${preferences.work}. Starting help: ${preferences.start}.\n${setup.nextStep(preferences)}\nThis is a starting preference, not a client fact; current instructions and engagement state take precedence.` : '',
|
|
1462
|
-
|
|
1467
|
+
intro,
|
|
1463
1468
|
success ? `CURRENT GOALS & ACCEPTANCE - success.md\n${success}` : '',
|
|
1464
1469
|
risks ? `OPEN RISKS - risks.md\n${extractRisks(eng).map(r => r.text).join('\n') || '(none recorded)'}` : '',
|
|
1465
1470
|
`VALUE LEDGER - delivery.md\n${parseValueLedger(eng).rows.map(r => formatValueLedgerLine(r) + '; source: ' + (r.evidence || '(missing)')).join('\n') || '(none recorded)'}`,
|
|
1466
1471
|
`WORKING CONTEXT - context.md\n${ctx ? resumeView(ctx) : '(no context.md yet)'}`,
|
|
1467
|
-
], maxBytes))
|
|
1472
|
+
], maxBytes, `ENGAGEMENT: ${eng}`))
|
|
1468
1473
|
}
|
|
1469
1474
|
|
|
1470
1475
|
// Token discipline: context.md grows every session (the session-stop hook
|
|
@@ -2083,7 +2088,10 @@ function withDebriefRecords(eng, apply, files = ['decisions.md', 'risks.md', 'de
|
|
|
2083
2088
|
} catch (_) { failed.push(path.basename(target)) }
|
|
2084
2089
|
}
|
|
2085
2090
|
if (failed.length) throw new Error(`${error.message}; recovery could not restore ${failed.join(', ')}. Inspect these records and the pending proposal before retrying.`)
|
|
2086
|
-
|
|
2091
|
+
const recovery = snapshots.get(path.join(eng, DEBRIEF_PROPOSE))
|
|
2092
|
+
? 'The proposal is retained; inspect it with fde debrief --review before retrying.'
|
|
2093
|
+
: 'No pending proposal was retained; review your input before retrying.'
|
|
2094
|
+
throw new Error(`${error.message}; no record changes kept. ${recovery}`)
|
|
2087
2095
|
} finally { debriefTransactionActive = false }
|
|
2088
2096
|
}
|
|
2089
2097
|
return lockAt(0)
|
|
@@ -2156,7 +2164,7 @@ function routeDebriefInput(eng, input, { dry, force, sealed = [], allowReplay =
|
|
|
2156
2164
|
})
|
|
2157
2165
|
}
|
|
2158
2166
|
const repeats = repeatedDebriefStatements(eng, input)
|
|
2159
|
-
if (repeats.length && !dry && !allowReplay) throw new Error('source-backed statement already recorded; review the existing record and newer next action. Explicitly confirm a repeat with --allow-replay, or remove the repeated statement from
|
|
2167
|
+
if (repeats.length && !dry && !allowReplay) throw new Error('source-backed statement already recorded; review the existing record and newer next action. Explicitly confirm a repeat with --allow-replay, or remove the repeated statement from your input or pending proposal.')
|
|
2160
2168
|
const d = new Date()
|
|
2161
2169
|
const date = d.toISOString().slice(0, 10)
|
|
2162
2170
|
const counts = { decision: 0, risk: 0, delivery: 0, contact: 0, next: 0, signer: 0 }
|
|
@@ -2547,15 +2555,20 @@ function cmdReceipts(args) {
|
|
|
2547
2555
|
const records = [], claims = []
|
|
2548
2556
|
for (const file of [...recordFiles, ...workingFiles]) {
|
|
2549
2557
|
const document = readClean(eng, file)
|
|
2558
|
+
const scaffold = new Set((templatesDir() ? readClean(templatesDir(), file) : '').split('\n').map(line => line.trim()))
|
|
2550
2559
|
const decisionSources = new Map()
|
|
2551
2560
|
if (file === 'decisions.md') for (const entry of datedDecisions(document)) {
|
|
2552
|
-
|
|
2553
|
-
for (let line = entry.line; line < entry.line + entry.text.split('\n').length; line++) decisionSources.set(line, source)
|
|
2561
|
+
for (let line = entry.line; line < entry.line + entry.text.split('\n').length; line++) decisionSources.set(line, entry.text)
|
|
2554
2562
|
}
|
|
2555
2563
|
document.split('\n').forEach((line, i) => {
|
|
2556
2564
|
if (!line.toLowerCase().includes(term.toLowerCase())) return
|
|
2557
|
-
const
|
|
2558
|
-
const
|
|
2565
|
+
const sourceText = decisionSources.get(i + 1) || line
|
|
2566
|
+
const source = sourceReference(sourceText)
|
|
2567
|
+
if (!source && scaffold.has(line.trim())) return
|
|
2568
|
+
const sources = sourceReferences(sourceText)
|
|
2569
|
+
const attribution = masking.mask(sources.join('; '))
|
|
2570
|
+
const displayed = Buffer.byteLength(attribution) <= 320 ? attribution : context.clipMaskedUtf8(attribution, 240) + '… [sources truncated; use fde recall]'
|
|
2571
|
+
const hit = ` ${file}:${i + 1} ${context.clipMaskedUtf8(masking.mask(line.trim()), 160)}${source ? ` [${sources.length > 1 ? 'sources' : 'source'}: ${displayed}]` : ' [source missing]'}${dirty.has(file) ? ' dirty file - review manual edits' : ''}`
|
|
2559
2572
|
;(recordFiles.includes(file) && source ? records : claims).push({ file, hit })
|
|
2560
2573
|
})
|
|
2561
2574
|
}
|
package/bin/lib/context.js
CHANGED
|
@@ -14,6 +14,13 @@ function clipUtf8(text, bytes) {
|
|
|
14
14
|
while (end > 0 && (buf[end] & 0xc0) === 0x80) end--
|
|
15
15
|
return buf.subarray(0, end).toString('utf8')
|
|
16
16
|
}
|
|
17
|
+
// Masking aliases must stay whole so they remain recognizable and reversible.
|
|
18
|
+
function clipMaskedUtf8(text, bytes) {
|
|
19
|
+
const clipped = clipUtf8(text, bytes)
|
|
20
|
+
if (clipped === text) return text
|
|
21
|
+
const open = clipped.lastIndexOf('[[')
|
|
22
|
+
return open > clipped.lastIndexOf(']]') ? clipped.slice(0, open) : clipped
|
|
23
|
+
}
|
|
17
24
|
function budgetArgs(args, defaultBytes = DEFAULT_BYTES) {
|
|
18
25
|
const rest = [...args]
|
|
19
26
|
let maxBytes = defaultBytes
|
|
@@ -91,4 +98,4 @@ function recallSections(documents, query, maxHits = 12, outputText = text => tex
|
|
|
91
98
|
sections: selected.map(h => `${h.file}:${h.line}-${h.end} (lines in redacted view)\n${h.text}`),
|
|
92
99
|
}
|
|
93
100
|
}
|
|
94
|
-
module.exports = { DEFAULT_BYTES, clipUtf8, budgetArgs, boundedSections, recallSections }
|
|
101
|
+
module.exports = { DEFAULT_BYTES, clipUtf8, clipMaskedUtf8, budgetArgs, boundedSections, recallSections }
|
package/bin/lib/provenance.js
CHANGED
|
@@ -40,3 +40,13 @@ function datedDecisions(value) {
|
|
|
40
40
|
return entries.sort((a, b) => a.date.localeCompare(b.date) || a.line - b.line)
|
|
41
41
|
}
|
|
42
42
|
module.exports.datedDecisions = datedDecisions
|
|
43
|
+
|
|
44
|
+
// Display every explicit attribution without selecting a primary source.
|
|
45
|
+
// Keep classification through sourceReference unchanged.
|
|
46
|
+
function sourceReferences(value) {
|
|
47
|
+
const explicit = String(value || '').match(/\[source:[^\]\n]+\]/gi) || []
|
|
48
|
+
if (explicit.length) return [...new Set(explicit.map(sourceReference).filter(Boolean))]
|
|
49
|
+
const fallback = sourceReference(value)
|
|
50
|
+
return fallback ? [fallback] : []
|
|
51
|
+
}
|
|
52
|
+
module.exports.sourceReferences = sourceReferences
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fdeops",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.4",
|
|
4
4
|
"description": "Client delivery tools for Forward Deployed Engineers. One @fde skill, local Markdown engagement records, and an offline dashboard for decisions, evidence, approvals, and next actions.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"fdeops": "bin/install.js",
|
package/plugin.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
|
|
3
3
|
"name": "fdeops",
|
|
4
|
-
"version": "4.0.
|
|
4
|
+
"version": "4.0.4",
|
|
5
5
|
"description": "Forward deployed engineering skills for AI coding agents. One @fde skill for the client work around the code. You confirm; then it lands in .fde/ on your laptop.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Subash Natarajan",
|
package/skills/fde/SKILL.md
CHANGED
|
@@ -23,7 +23,7 @@ A one-line typo or compile error in a file that will not ship. On a bound client
|
|
|
23
23
|
|
|
24
24
|
| What's happening | Sentence to say | You run | Then read |
|
|
25
25
|
|---------|-----------------|---------|-----------|
|
|
26
|
-
| **The brief is wrong** | "If this works, who in their company would have to agree that it worked?" |
|
|
26
|
+
| **The brief is wrong** | "If this works, who in their company would have to agree that it worked?" | Current entry packet (see Entry below), then discover | `references/discover.md` |
|
|
27
27
|
| **They went quiet** | "Is this a process gap, or a trust problem?" | `fde log contact "…" --signal amber\|red\|green` | `references/rescue.md` |
|
|
28
28
|
| **When did we agree?** | Don't argue from memory. Search the record. | `fde receipts <term>` | - |
|
|
29
29
|
| **What's the outcome?** | A number nobody signed is claimed, not delivered. | `fde status` | `references/readout.md` |
|
|
@@ -64,7 +64,7 @@ Use `work` to tailor the help: single = focus on the bound client; multiple = po
|
|
|
64
64
|
|
|
65
65
|
## Entry (every session)
|
|
66
66
|
|
|
67
|
-
1. `fde resume` (16 KiB by default, 4 KiB with compact setup; a byte ceiling, not a model token count).
|
|
67
|
+
1. After the first-use setup check above, use one current `fde resume` packet (16 KiB by default, 4 KiB with compact setup; a byte ceiling, not a model token count). At each new user turn or task, run `fde resume` unless a fresh session-hook packet was supplied for that entry. Within this entry, reuse that hook packet or a packet from a CLI call made during the current turn/task only if its `ENGAGEMENT:` identity is visible, matches the current client binding, and freshness is certain. Never reuse a packet carried over from an earlier user turn or task: external edits may have changed the record. If the packet is absent, its identity or freshness is uncertain, the binding or engagement state changed since it was loaded (including your own writes or setup/masking changes), or the user asks for a refresh or “where are we,” run `fde resume` before using the context. Do not repeat an immediate entry call solely because the skill, an adapter, or a slash command was loaded. Read client constraints first, then signer, goals, risks, delivery ledger and current context; never substitute a recursive read of `.fde/` or raw transcripts. If truncated or a decision needs evidence, run `fde recall <specific topic>`; narrow the query rather than loading the whole history. `--max-bytes 4096` reduces the allowance for smaller models. `--full` only when the complete log is explicitly needed.
|
|
68
68
|
2. **NO ENGAGEMENT:** ask "What should we call this client?" then **you** init. Pasted notes → debrief after bind.
|
|
69
69
|
3. Playback 2-3 lines. `hygiene:` → offer `fde doctor`; **never auto-rewrite**.
|
|
70
70
|
4. Route. Read **one** `references/*.md`. Confirm, then write.
|
|
@@ -90,7 +90,7 @@ Writes need a bind (`FDEOPS_ENGAGEMENT` or registry). Never install fdeops on in
|
|
|
90
90
|
|
|
91
91
|
## The memory contract
|
|
92
92
|
|
|
93
|
-
1. **On entry:**
|
|
93
|
+
1. **On entry:** follow **Entry (every session)** above for the current packet and refresh rules. Retrieve additional evidence through targeted `fde recall` when needed.
|
|
94
94
|
2. **Deliverable = memory.** The work *is* the `.fde/` file. The reference names which one.
|
|
95
95
|
3. **Evidence.** Without a supplied source, a decision or measurement remains CLAIM. Use `[source: meeting YYYY-MM-DD]`, a PR/URL, transcript ID, or artifact path. The automatic log date is not attribution. ON RECORD means a source was supplied, not that it was authenticated or the customer approved. Never invent a source, signer, or acceptance.
|
|
96
96
|
4. **No invented facts.** People, quotes, meetings, numbers: they said it or the repo shows it. Else `unknown - ask: <question>`.
|
|
@@ -211,7 +211,7 @@ Ready to build with no `terrain.md` / plan: discover or plan first. Takeover wit
|
|
|
211
211
|
- Same six stages at any scale. Overlays carry the industry. Greenfield and brownfield change the first move inside ship, not the map.
|
|
212
212
|
- Ground loop on a bound client: name → characterise → verify in the agreed environment → authorize release → log. A coding pack may write the function. `@fde` still owns done. When they disagree, their repo and the signer win.
|
|
213
213
|
- Customer delivery needs a replayable acceptance check in the agreed environment; reuse existing criteria for routine fixes. Missing evidence means unproven, not an observed test failure. Never equate implementation-complete with deployed or customer-accepted.
|
|
214
|
-
- Read
|
|
214
|
+
- Read the current entry packet before speaking; follow **Entry (every session)** above. One sharp question - never a barrage.
|
|
215
215
|
- Never invent people, meetings, or numbers - `unknown - ask:` beats a polished lie.
|
|
216
216
|
- Every phase ends with its artifact written. No artifact, no "done."
|
|
217
217
|
- Evidence on every claim. The FDE will be challenged on these files.
|