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 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
- You need an AI coding agent that supports skills. The installation commands and FDEOps CLI require **Node.js 18+ and Git** on your machine.
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 your terminal and select your agent in the installer:
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
- Then start a conversation with your agent:
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 files = walk(cwd, CONF_EXT.concat(['.md']), 5000)
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 => /test|spec/i.test(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 = files.filter(f => /test|spec/i.test(f)).length
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(' (clean scan - ask what the last engineer wished they had known)')
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(' delivery picture (existing record; this proposal does not certify it):')
1998
- console.log(` - measurement: ${measured ? 'recorded; check the value ledger' : 'missing - record the observed result'}`)
1999
- console.log(` - evidence: ${evidenced ? 'recorded; review its source' : 'missing - cite the test, artifact, or source'}`)
2000
- console.log(` - customer approval: ${accepted ? 'recorded for a prior outcome; not this proposal' : 'missing - request explicit acceptance after evidence review'}`)
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) { console.error('no engagement - run: fde resume --init <name>'); process.exit(2) }
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
@@ -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} measured outcome${claimed === 1 ? '' : 's'} awaiting acceptance`, 'Ask the acceptance owner to review the measured outcome', 'delivery.md')
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')
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fdeops-ingest-mcp",
3
- "version": "5.1.3",
3
+ "version": "5.1.4",
4
4
  "private": true,
5
5
  "description": "Thin stdio MCP sink for FDEOps ingest (stage \u2192 propose \u2192 apply). Zero runtime dependencies.",
6
6
  "bin": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fdeops",
3
- "version": "5.1.3",
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.3",
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": "7403c49ceef1c3d5347efbef9c45af3bd0f5927d595b2df3d4e35eadfd154344",
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": "7403c49ceef1c3d5347efbef9c45af3bd0f5927d595b2df3d4e35eadfd154344",
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.