fdeops 3.22.1 → 3.22.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/bin/fde.js CHANGED
@@ -1610,20 +1610,38 @@ function smartProposeText(input) {
1610
1610
  return out.join('\n') + (out.length ? '\n' : '')
1611
1611
  }
1612
1612
 
1613
- // "Priya signs off" is the most expensive sentence in a kickoff and used to land
1614
- // in context.md as a note. signer: fills the success.md line the whole kit
1615
- // keys on, and logs the person as a contact so prep/status can see them.
1616
- const SIGNER_RX = /^(?<who>[A-Z][\w.'-]+(?:\s+[A-Z][\w.'-]+){0,3}(?:\s*\([^)]{1,40}\))?)\s+(?:signs?(?:\s+off)?|approves|has (?:the )?final say|can say yes|owns the decision|is the (?:sponsor|signer|decision[- ]maker))\b/
1613
+ // Kickoff English, not only "signer: Priya". "Helena signs off", "Anand Mehta
1614
+ // has final say", "Finance controller (Helena) signs off" all have to fill
1615
+ // success.md - that is the line Monday's RECORD reads.
1616
+ const SIGNER_VERB = '(?:signs?(?:\\s+off)?|approves|has (?:the )?final say|can say yes|owns the decision|is the (?:sponsor|signer|decision[- ]maker))'
1617
+ const SIGNER_NAME = '([A-Z][\\w.\'-]+(?:\\s+[A-Z][\\w.\'-]+){0,3})'
1618
+ const NOT_A_PERSON = /^(The|This|That|It|We|They|She|He|Staging|Budget|Prod|Production|Nobody|Someone|Everyone|Finance|Legal|Security|Platform|Engineering)\b/
1619
+ const ROLE_TOKEN = /\b(VP|SVP|EVP|CTO|CFO|COO|CEO|CISO|Eng|Engineer|Director|Lead|Head|Manager|Controller|Ops|Legal|Finance|Sponsor)\b/i
1620
+
1621
+ function looksLikePersonName(s) {
1622
+ const t = String(s || '').trim()
1623
+ if (!t || NOT_A_PERSON.test(t) || ROLE_TOKEN.test(t)) return false
1624
+ return /^[A-Z][\w.'-]+(?:\s+[A-Z][\w.'-]+){0,2}$/.test(t)
1625
+ }
1617
1626
 
1618
1627
  function signerFromLine(text) {
1619
- const t = String(text || '').trim()
1620
- const m = t.match(SIGNER_RX)
1621
- if (!m) return ''
1622
- const who = m.groups.who.trim()
1623
- // "Staging exists" / "The API is slow" also match "Capital Word + verb"; a
1624
- // sentence-initial common noun is not a person.
1625
- if (/^(The|This|That|It|We|They|Staging|Budget|Prod|Production|Nobody|Someone|Everyone)\b/.test(who)) return ''
1626
- return who
1628
+ const t = String(text || '').replace(/^[-*+]\s+/, '').trim()
1629
+ if (!t) return ''
1630
+ // "Priya (VP Eng) signs off" → Priya. "Finance controller (Helena) signs off" → Helena.
1631
+ const titled = t.match(new RegExp('\\b' + SIGNER_NAME + '\\s+\\(' + SIGNER_NAME + '\\)\\s+' + SIGNER_VERB + '\\b'))
1632
+ if (titled) {
1633
+ const before = titled[1].trim()
1634
+ const inside = titled[2].trim()
1635
+ if (looksLikePersonName(before) && ROLE_TOKEN.test(inside)) return before
1636
+ if (looksLikePersonName(inside)) return inside
1637
+ if (looksLikePersonName(before)) return before
1638
+ }
1639
+ const paren = t.match(new RegExp('\\(' + SIGNER_NAME + '\\)\\s+' + SIGNER_VERB + '\\b'))
1640
+ if (paren && looksLikePersonName(paren[1])) return paren[1].trim()
1641
+ const named = t.match(new RegExp('\\b' + SIGNER_NAME + '\\s+' + SIGNER_VERB + '\\b'))
1642
+ if (!named) return ''
1643
+ const who = named[1].trim()
1644
+ return looksLikePersonName(who) ? who : ''
1627
1645
  }
1628
1646
 
1629
1647
  function setSigner(eng, who) {
@@ -2134,7 +2152,7 @@ function cmdReceipts(args) {
2134
2152
  agreed.map(h => (h.match(/^\s*([^:]+):/) || [])[1]).filter(f => f && dirtySet.has(f))
2135
2153
  )]
2136
2154
  if (agreed.length) {
2137
- console.log('ON RECORD (dated - defensible):')
2155
+ console.log('ON RECORD (dated):')
2138
2156
  agreed.forEach(h => {
2139
2157
  const file = (h.match(/^\s*([^:]+):/) || [])[1]
2140
2158
  console.log(h + (file && dirtySet.has(file) ? ' ⚠ dirty file' : ''))
@@ -2620,7 +2638,7 @@ function hasValueBucket(eng) {
2620
2638
  // "pending review", "TBD.", "n/a (blocked)" and "..." are all the same thing an
2621
2639
  // FDE means by an empty cell - nagging about them teaches people to ignore doctor.
2622
2640
  const PENDING_CELL_RE =
2623
- /^(?:pending|tbd|to ?be ?(?:measured|confirmed|determined)|n\s*\/\s*a|na|none|unknown|not measured|\?+|\.{2,}|…|-+|-+|-+)(?:[^\w].*)?$/i
2641
+ /^(?:pending|tbd|to ?be ?(?:measured|confirmed|determined)|n\s*\/\s*a|na|none|unknown|not(?:\s+yet)?\s+measured|unmeasured|awaiting|\?+|\.{2,}|…|-+)(?:[^\w].*)?$/i
2624
2642
 
2625
2643
  function parseValueLedger(eng) {
2626
2644
  // Last section with actual rows, not merely the last non-empty one: a template
@@ -2704,7 +2722,7 @@ function engagementTouchesAI(eng) {
2704
2722
  ].join('\n'))
2705
2723
  // No bare "prompt": "prompt response" / "prompt payment" is ordinary delivery
2706
2724
  // English and would fail every non-AI ship on a missing eval receipt.
2707
- return /\b(llm|rag|embedding|model card|model output|model drift|agentic|openai|anthropic|vector database|vector db|fine-tun\w*|hallucinat\w*)\b|\bmodel inference\b|\binference (?:api|endpoint|server|engine)\b|\b(?:system|model|user)\s+prompts?\b|\bprompt (?:engineering|injection|template)/i.test(blob)
2725
+ return /\bAI in scope\b|\b(llm|rag|embedding|model card|model output|model drift|agentic|openai|anthropic|vector database|vector db|fine-tun\w*|hallucinat\w*)\b|\bmodel inference\b|\binference (?:api|endpoint|server|engine)\b|\b(?:system|model|user)\s+prompts?\b|\bprompt (?:engineering|injection|template)/i.test(blob)
2708
2726
  }
2709
2727
 
2710
2728
  // The repo says AI even when the record does not. Read-only, capped, local: the
@@ -3618,6 +3636,10 @@ function printUsage() {
3618
3636
  }
3619
3637
 
3620
3638
  const [cmd, ...args] = process.argv.slice(2)
3639
+ if (args.includes('--help') || args.includes('-h') || cmd === 'help' || cmd === '--help' || cmd === '-h') {
3640
+ printUsage()
3641
+ process.exit(0)
3642
+ }
3621
3643
  switch (cmd) {
3622
3644
  case 'demo': cmdDemo(args); break
3623
3645
  case 'scan': cmdScan(); break
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fdeops-ingest-mcp",
3
- "version": "3.22.1",
3
+ "version": "3.22.2",
4
4
  "private": true,
5
5
  "description": "Thin stdio MCP sink for FDEOps ingest (stage → propose → apply). Zero runtime dependencies.",
6
6
  "bin": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fdeops",
3
- "version": "3.22.1",
3
+ "version": "3.22.2",
4
4
  "description": "Forward deployed engineering skills for AI coding agents. One @fde skill for the client work around the code: who can say yes, what went live, whether they signed off. Dated markdown on your laptop. You confirm each write.",
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": "3.22.1",
4
+ "version": "3.22.2",
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",