fdeops 5.1.3 → 5.1.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/README.md +7 -3
- package/bin/check.js +0 -9
- package/bin/fde.js +31 -10
- package/bin/lib/delivery-gaps.js +1 -1
- package/mcp/fdeops-ingest/package.json +1 -1
- package/package.json +1 -1
- package/plugin.json +1 -1
- package/skills/fde/references/close.md +2 -0
- package/skills/handoff/.fde-generated.json +1 -1
- package/skills/handoff/references/close.md +2 -0
- package/skills/runbook/.fde-generated.json +1 -1
- package/skills/runbook/references/close.md +2 -0
package/README.md
CHANGED
|
@@ -12,19 +12,23 @@ Use it for a single integration, a small client project, or work within a larger
|
|
|
12
12
|
|
|
13
13
|
[Get started](#quick-start) · [Choose a task](#task-skills) · [Keep a customer record](#keep-a-customer-record) · [Documentation](docs/README.md)
|
|
14
14
|
|
|
15
|
+
<img width="1000" height="586" alt="fdeops-flow" src="https://github.com/user-attachments/assets/12bbec6a-d0b3-4d03-81aa-4b842731a044" />
|
|
16
|
+
|
|
15
17
|
## Quick start
|
|
16
18
|
|
|
17
|
-
|
|
19
|
+
Use FDEOps with an AI coding agent that supports skills. Start with one task, or let `fde` coordinate a customer project.
|
|
18
20
|
|
|
19
21
|
### Work on a customer project
|
|
20
22
|
|
|
21
|
-
Run this in
|
|
23
|
+
Run this in the terminal where your coding agent runs, then select your agent in the installer:
|
|
22
24
|
|
|
23
25
|
```bash
|
|
24
26
|
npx skills add suboss87/fdeops --skill fde
|
|
25
27
|
```
|
|
26
28
|
|
|
27
|
-
|
|
29
|
+
This installation method uses Node.js (which includes `npx`) and Git. If either is missing, ask your agent to help with setup. The FDEOps CLI requires Node.js 18 or later. See [installation options](docs/install.md) for your agent.
|
|
30
|
+
|
|
31
|
+
Then select `fde` in your agent and start a conversation. In agents that support `@fde`, use:
|
|
28
32
|
|
|
29
33
|
```text
|
|
30
34
|
@fde this is client01. Their support team reads incoming requests,
|
package/bin/check.js
CHANGED
|
@@ -291,15 +291,6 @@ for (const phrase of badPhrases) {
|
|
|
291
291
|
fail(`README must not contain hype phrase: ${phrase}`)
|
|
292
292
|
}
|
|
293
293
|
}
|
|
294
|
-
// fdeops stands on its own. The README must never frame it as a derivative -
|
|
295
|
-
// a fork, port, or rebuild of another project.
|
|
296
|
-
const derivativeFraming = [
|
|
297
|
-
/\b(fork of|forked from|port of|rebuild of|reimplementation of|based on)\s+[A-Z]/,
|
|
298
|
-
/\b(inspired by|built on top of|powered by)\s+[A-Z][A-Za-z0-9-]+/,
|
|
299
|
-
]
|
|
300
|
-
for (const rx of derivativeFraming) {
|
|
301
|
-
if (rx.test(readme)) fail(`README must not frame fdeops as derivative: ${rx}`)
|
|
302
|
-
}
|
|
303
294
|
if (/docs\/internal|PMF_360/i.test(readme)) {
|
|
304
295
|
fail('README must not link docs/internal or PMF_360')
|
|
305
296
|
}
|
package/bin/fde.js
CHANGED
|
@@ -76,7 +76,15 @@ function maskedSections(sections, maxBytes = context.DEFAULT_BYTES, heading = ''
|
|
|
76
76
|
return prefix + context.boundedSections(sections.map(text => masking.mask(text)), maxBytes - Buffer.byteLength(prefix))
|
|
77
77
|
}
|
|
78
78
|
const DEBRIEF_MAX_BYTES = 256 * 1024
|
|
79
|
-
const CODE_EXT = ['.js', '.ts', '.tsx', '.jsx', '.py', '.java', '.go', '.rb', '.cs', '.php']
|
|
79
|
+
const CODE_EXT = ['.js', '.mjs', '.cjs', '.ts', '.mts', '.cts', '.tsx', '.jsx', '.py', '.java', '.go', '.rb', '.cs', '.php']
|
|
80
|
+
function isTestCodeFile(file, cwd) {
|
|
81
|
+
if (!CODE_EXT.includes(path.extname(file))) return false
|
|
82
|
+
const parts = path.relative(cwd, file).split(path.sep)
|
|
83
|
+
const name = path.basename(parts.pop(), path.extname(file))
|
|
84
|
+
return parts.some(part => /^(?:tests?|specs?|__tests__)$/i.test(part))
|
|
85
|
+
|| /(?:^|[._-])(?:test|spec)(?:[._-]|$)/i.test(name)
|
|
86
|
+
|| /(?:Tests?|Specs?)$/.test(name)
|
|
87
|
+
}
|
|
80
88
|
const CONF_EXT = CODE_EXT.concat(['.env', '.yaml', '.yml', '.json'])
|
|
81
89
|
// Bare "inference" is banned here: TypeScript codebases are full of "type
|
|
82
90
|
// inference" comments and the false positives poison the day-1 questions.
|
|
@@ -1252,7 +1260,11 @@ function cmdScan() {
|
|
|
1252
1260
|
out.push('='.repeat(60))
|
|
1253
1261
|
|
|
1254
1262
|
// stack + age
|
|
1255
|
-
const
|
|
1263
|
+
const candidates = walk(cwd, CONF_EXT.concat(['.md']), 5001)
|
|
1264
|
+
const files = candidates.slice(0, 5000)
|
|
1265
|
+
out.push(`COVERAGE ${files.length} supported files selected${candidates.length > 5000 ? ' - 5,000-file limit reached; scan is partial' : ''}`)
|
|
1266
|
+
out.push(' Hidden/generated directories, files over 1 MiB and unsupported formats are not inspected.')
|
|
1267
|
+
out.push(' Findings are capped per section. Absence of findings does not establish test coverage or production readiness.')
|
|
1256
1268
|
const extCount = {}
|
|
1257
1269
|
for (const f of files) { const e = path.extname(f); extCount[e] = (extCount[e] || 0) + 1 }
|
|
1258
1270
|
const langs = Object.entries(extCount).sort((a, b) => b[1] - a[1]).slice(0, 4).map(([e, n]) => `${e}:${n}`).join(' ')
|
|
@@ -1268,7 +1280,7 @@ function cmdScan() {
|
|
|
1268
1280
|
const counts = {}
|
|
1269
1281
|
churn.split('\n').filter(Boolean).forEach(f => { counts[f] = (counts[f] || 0) + 1 })
|
|
1270
1282
|
const top = Object.entries(counts).sort((a, b) => b[1] - a[1]).slice(0, 8)
|
|
1271
|
-
const testFiles = files.filter(f =>
|
|
1283
|
+
const testFiles = files.filter(f => isTestCodeFile(f, cwd))
|
|
1272
1284
|
if (top.length === 0) out.push(' (no commits in the last 90 days)')
|
|
1273
1285
|
for (const [f, n] of top) {
|
|
1274
1286
|
const base = path.basename(f).replace(/\.[^.]+$/, '')
|
|
@@ -1317,7 +1329,7 @@ function cmdScan() {
|
|
|
1317
1329
|
if (!reverts && readmeHits.length === 0) out.push(' none visible')
|
|
1318
1330
|
|
|
1319
1331
|
// test landscape
|
|
1320
|
-
const testCount =
|
|
1332
|
+
const testCount = codeFiles.filter(f => isTestCodeFile(f, cwd)).length
|
|
1321
1333
|
out.push(`\nTEST LANDSCAPE ${testCount} test file(s) across ${codeFiles.length} code files`)
|
|
1322
1334
|
|
|
1323
1335
|
// day-1 questions - each one earned by a finding above, skipped when empty
|
|
@@ -1330,7 +1342,7 @@ function cmdScan() {
|
|
|
1330
1342
|
if (tmp.length) asks.push("Which of these 'temporary' fixes are now load-bearing contracts?")
|
|
1331
1343
|
asks.length
|
|
1332
1344
|
? asks.slice(0, 5).forEach((q, i) => out.push(` ${i + 1}. ${q}`))
|
|
1333
|
-
: out.push('
|
|
1345
|
+
: out.push(codeFiles.length ? ' No additional questions from these heuristics; inspect the untested paths with the team.' : ' No supported code files inspected; identify the application files before relying on this scan.')
|
|
1334
1346
|
|
|
1335
1347
|
out.push('\n' + '-'.repeat(60))
|
|
1336
1348
|
out.push("Facts only - interpretation is the FDE's (or @fde's) job.")
|
|
@@ -1969,6 +1981,7 @@ function printDebriefReview(text, eng) {
|
|
|
1969
1981
|
}
|
|
1970
1982
|
}
|
|
1971
1983
|
console.log('REVIEW (one screen - confirm once, then apply)\n')
|
|
1984
|
+
console.log('PENDING UPDATE - not yet saved. Review these proposed changes:\n')
|
|
1972
1985
|
const order = [
|
|
1973
1986
|
['decided', buckets.decided],
|
|
1974
1987
|
['stated asks', buckets.asked],
|
|
@@ -1994,10 +2007,14 @@ function printDebriefReview(text, eng) {
|
|
|
1994
2007
|
const measured = recorded.some(row => row.state !== 'unmeasured')
|
|
1995
2008
|
const evidenced = recorded.some(row => !row.evidenceMissing)
|
|
1996
2009
|
const accepted = recorded.some(row => row.state === 'accepted')
|
|
1997
|
-
console.log('
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2010
|
+
console.log('\nSAVED RECORD - before this update; pending changes above are not included:')
|
|
2011
|
+
if (!recorded.length) {
|
|
2012
|
+
console.log(' No delivery results saved yet. Review the proposed delivery above before applying.')
|
|
2013
|
+
} else {
|
|
2014
|
+
console.log(` - result: ${measured ? 'recorded; check its scope in the value ledger' : 'not yet recorded'}`)
|
|
2015
|
+
console.log(` - evidence: ${evidenced ? 'recorded; review its source' : 'not yet recorded'}`)
|
|
2016
|
+
console.log(` - customer approval: ${accepted ? 'recorded for a prior result; not this proposal' : 'not yet recorded'}`)
|
|
2017
|
+
}
|
|
2001
2018
|
console.log(' Saving this update confirms your record, not customer acceptance.\n')
|
|
2002
2019
|
}
|
|
2003
2020
|
|
|
@@ -2610,7 +2627,11 @@ function cmdHandoff(args, label = 'Handoff') {
|
|
|
2610
2627
|
out = path.resolve(parsed.args[1])
|
|
2611
2628
|
}
|
|
2612
2629
|
const eng = resolveEngagement()
|
|
2613
|
-
if (!eng) {
|
|
2630
|
+
if (!eng) {
|
|
2631
|
+
const skill = label === 'Handoff' ? 'handoff' : 'readout'
|
|
2632
|
+
console.error(`This command exports an existing customer record; none is selected.\nFor a standalone draft, ask your agent to use the ${skill} skill with supplied notes; no record is required.\nTo keep an ongoing customer record, run: fde resume --init <name>`)
|
|
2633
|
+
process.exit(2)
|
|
2634
|
+
}
|
|
2614
2635
|
const success = stripTemplateNoise(readClean(eng, 'success.md'))
|
|
2615
2636
|
const signer = ((success.match(/^\*\*Stakeholder who signs off:\*\*[^\S\n]*(.*)$/m) || [])[1] || '').trim()
|
|
2616
2637
|
const ledger = parseValueLedger(eng).rows
|
package/bin/lib/delivery-gaps.js
CHANGED
|
@@ -17,7 +17,7 @@ function deliverySummary(e) {
|
|
|
17
17
|
const unmeasured = rows.filter(r => r.state === 'unmeasured').length
|
|
18
18
|
if (unmeasured) add('measurement', `${unmeasured} outcome${unmeasured === 1 ? '' : 's'} not yet measured`, 'Agree how to measure the outcome and collect the result', 'delivery.md')
|
|
19
19
|
const claimed = rows.filter(r => r.state === 'claimed').length
|
|
20
|
-
if (claimed) add('acceptance', `${claimed}
|
|
20
|
+
if (claimed) add('acceptance', `${claimed} reported result${claimed === 1 ? '' : 's'} awaiting acceptance`, 'Ask the acceptance owner to review the reported result and its evidence', 'delivery.md')
|
|
21
21
|
if (signals.trust === 'amber') add('trust', 'Check in with the customer: trust is watch', 'Check the customer concern and agree the next step', 'stakeholders.md')
|
|
22
22
|
if (signals.stale) add('stale-trust', 'Reconfirm the dated trust signal', 'Check whether the recorded customer signal still applies', 'stakeholders.md')
|
|
23
23
|
if (!e.hasNext) add('next', 'Set the next action', 'Set one next action with an owner and completion check', 'context.md')
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fdeops",
|
|
3
|
-
"version": "5.1.
|
|
3
|
+
"version": "5.1.4",
|
|
4
4
|
"description": "Forward deployed engineering skills for AI coding agents. Use focused task skills or @fde for discovery, implementation, verification and handoff, with local engagement records.",
|
|
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": "5.1.
|
|
4
|
+
"version": "5.1.4",
|
|
5
5
|
"description": "Forward deployed engineering skills for AI coding agents. Use focused task skills or @fde for discovery, implementation, verification and handoff, with local engagement records.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Subash Natarajan",
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
**Context:** apply [task context and evidence](task-context.md) before using the named records below.
|
|
4
4
|
|
|
5
|
+
For a standalone handoff draft, use the supplied notes and project evidence; no customer record is required. The `fde handoff` CLI exports an existing record. Use it only when a record is selected, not to create one merely for a draft.
|
|
6
|
+
|
|
5
7
|
**Enter when:** the engagement is ending - the customer team must run this without the FDE.
|
|
6
8
|
|
|
7
9
|
**Read first:** for standalone work, use the supplied permitted operating notes, evidence and ownership; no engagement binding or CLI command is required. For a bound engagement, use bounded `fde handoff` or `fde resume`, then targeted `fde recall` for missing evidence. Never initialize records merely to draft a handoff. Build the picture through relevant excerpts, not a full-directory load. Consult `terrain.md` only for code paths needed by the successor.
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"version": 1,
|
|
4
4
|
"files": {
|
|
5
5
|
"SKILL.md": "9f08605914670f2c0bd09631d3d40c079e7f63174d1a2607e60ea6eca5d54d93",
|
|
6
|
-
"references/close.md": "
|
|
6
|
+
"references/close.md": "b3dbc1ecef12ee1c9d298e32a21d87ce61c9f6c88039d7a8b19970c6e3f21254",
|
|
7
7
|
"references/encode-pattern.md": "3be7bf9d0f69af31659423c54d6023a4af1556e2ce200fb025bf64d0757de4d8",
|
|
8
8
|
"references/task-context.md": "28684273e9d6edf1d05fb02c96caf4223e19b73e7ae840bdec2ff0bf06e3bc27"
|
|
9
9
|
}
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
**Context:** apply [task context and evidence](task-context.md) before using the named records below.
|
|
4
4
|
|
|
5
|
+
For a standalone handoff draft, use the supplied notes and project evidence; no customer record is required. The `fde handoff` CLI exports an existing record. Use it only when a record is selected, not to create one merely for a draft.
|
|
6
|
+
|
|
5
7
|
**Enter when:** the engagement is ending - the customer team must run this without the FDE.
|
|
6
8
|
|
|
7
9
|
**Read first:** for standalone work, use the supplied permitted operating notes, evidence and ownership; no engagement binding or CLI command is required. For a bound engagement, use bounded `fde handoff` or `fde resume`, then targeted `fde recall` for missing evidence. Never initialize records merely to draft a handoff. Build the picture through relevant excerpts, not a full-directory load. Consult `terrain.md` only for code paths needed by the successor.
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"version": 1,
|
|
4
4
|
"files": {
|
|
5
5
|
"SKILL.md": "5488cddb607cb05face73ad1a838dd87babb00716544793f36997ca15dcb1560",
|
|
6
|
-
"references/close.md": "
|
|
6
|
+
"references/close.md": "b3dbc1ecef12ee1c9d298e32a21d87ce61c9f6c88039d7a8b19970c6e3f21254",
|
|
7
7
|
"references/encode-pattern.md": "3be7bf9d0f69af31659423c54d6023a4af1556e2ce200fb025bf64d0757de4d8",
|
|
8
8
|
"references/runbook.md": "1233aa1943da8db6d3c7624b7a54b3a1afc340b97e16f95d70b42cdbf521b63c",
|
|
9
9
|
"references/task-context.md": "28684273e9d6edf1d05fb02c96caf4223e19b73e7ae840bdec2ff0bf06e3bc27"
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
**Context:** apply [task context and evidence](task-context.md) before using the named records below.
|
|
4
4
|
|
|
5
|
+
For a standalone handoff draft, use the supplied notes and project evidence; no customer record is required. The `fde handoff` CLI exports an existing record. Use it only when a record is selected, not to create one merely for a draft.
|
|
6
|
+
|
|
5
7
|
**Enter when:** the engagement is ending - the customer team must run this without the FDE.
|
|
6
8
|
|
|
7
9
|
**Read first:** for standalone work, use the supplied permitted operating notes, evidence and ownership; no engagement binding or CLI command is required. For a bound engagement, use bounded `fde handoff` or `fde resume`, then targeted `fde recall` for missing evidence. Never initialize records merely to draft a handoff. Build the picture through relevant excerpts, not a full-directory load. Consult `terrain.md` only for code paths needed by the successor.
|