fdeops 5.1.12 → 5.1.13

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
@@ -2611,17 +2611,24 @@ function cmdReceipts(args) {
2611
2611
  const scaffold = new Set((templatesDir() ? readClean(templatesDir(), file) : '').split('\n').map(line => line.trim()))
2612
2612
  const decisionSources = new Map()
2613
2613
  if (file === 'decisions.md') for (const entry of datedDecisions(document)) {
2614
- for (let line = entry.line; line < entry.line + entry.text.split('\n').length; line++) decisionSources.set(line, entry.text)
2614
+ for (let line = entry.line; line < entry.line + entry.text.split('\n').length; line++) decisionSources.set(line, entry)
2615
2615
  }
2616
+ const seenEntries = new Set()
2616
2617
  document.split('\n').forEach((line, i) => {
2617
2618
  if (!line.toLowerCase().includes(term.toLowerCase())) return
2618
- const sourceText = decisionSources.get(i + 1) || line
2619
+ const entry = decisionSources.get(i + 1)
2620
+ if (entry && seenEntries.has(entry.line)) return
2621
+ if (entry) seenEntries.add(entry.line)
2622
+ const sourceText = entry ? entry.text : line
2619
2623
  const source = sourceReference(sourceText)
2620
2624
  if (!source && scaffold.has(line.trim())) return
2621
2625
  const sources = sourceReferences(sourceText)
2622
2626
  const attribution = masking.mask(sources.join('; '))
2623
2627
  const displayed = Buffer.byteLength(attribution) <= 320 ? attribution : context.clipMaskedUtf8(attribution, 240) + '… [sources truncated; use fde recall]'
2624
- 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' : ''}`
2628
+ const excerpt = masking.mask(entry ? entry.text : line.trim())
2629
+ const excerptLimit = entry ? 1400 : 160
2630
+ const body = context.clipMaskedUtf8(excerpt, excerptLimit) + (Buffer.byteLength(excerpt) > excerptLimit ? ' [excerpt truncated; use fde recall]' : '')
2631
+ const hit = ` ${file}:${entry ? entry.line : i + 1} ${body}${source ? ` [${sources.length > 1 ? 'sources' : 'source'}: ${displayed}]` : ' [source missing]'}${dirty.has(file) ? ' dirty file - review manual edits' : ''}`
2625
2632
  ;(recordFiles.includes(file) && source ? records : claims).push({ file, hit })
2626
2633
  })
2627
2634
  }
@@ -2643,8 +2650,9 @@ function cmdReceipts(args) {
2643
2650
  }
2644
2651
  return `Selected ${selected.length} of ${hits.length} matching lines; omitted matches require a narrower search.\n` + selected.join('\n')
2645
2652
  }
2646
- const sections = ['RECEIPTS: a cited record is not proof of customer approval. File line numbers refer to the redacted view. Latest and earliest matching lines are sampled; file order is not authority. Check conflicting records.']
2647
- if (records.length) sections.push('ON RECORD (dated, source-backed):\n' + select(records))
2653
+ const sections = ['RECEIPTS (dated evidence): a cited record is not proof of customer approval. File line numbers refer to the redacted view. Latest and earliest matching lines are sampled; file order is not authority. Check conflicting records.']
2654
+ if (records.length) sections.push('ON RECORD (source-backed; check stated dates and status):\n' + select(records))
2655
+ if (!records.length && claims.length) sections.push('No source-backed record matched. The notes below do not establish agreement; verify with the relevant decision owner when known.')
2648
2656
  if (claims.length) sections.push('CLAIMS & working notes (verify source and approval before citing):\n' + select(claims))
2649
2657
  if (!records.length && !claims.length) sections.push(`no record of "${term}" - a gap in the record, not proof of absence`)
2650
2658
  process.stdout.write(maskedSections(sections))
@@ -29,11 +29,12 @@ function datedDecisions(value) {
29
29
  for (let i = 0; i < lines.length; i++) {
30
30
  const bullet = lines[i].match(/^\s*[-*]\s*\[(\d{4}-\d{2}-\d{2})\]\s*(.+)/)
31
31
  const heading = lines[i].match(/^(#{2,3})\s+\[?(\d{4}-\d{2}-\d{2})(?:\]|\s+-)?\s+(.+)/)
32
+ const named = lines[i].match(/^(#{2,3})\s+((?:Decision:\s*.+|Scope change))\s+-\s+(\d{4}-\d{2}-\d{2})\s*$/i)
32
33
  if (bullet) entries.push({ date: bullet[1], text: lines[i].trim(), line: i + 1 })
33
- else if (heading) {
34
+ else if (heading || named) {
34
35
  let end = i + 1
35
36
  while (end < lines.length && !/^#{1,3}\s/.test(lines[end]) && !/^\s*[-*]\s*\[\d{4}-\d{2}-\d{2}\]/.test(lines[end])) end++
36
- entries.push({ date: heading[2], text: lines.slice(i, end).join('\n').trim(), line: i + 1 })
37
+ entries.push({ date: heading ? heading[2] : named[3], text: lines.slice(i, end).join('\n').trim(), line: i + 1 })
37
38
  i = end - 1
38
39
  }
39
40
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fdeops-ingest-mcp",
3
- "version": "5.1.12",
3
+ "version": "5.1.13",
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.12",
3
+ "version": "5.1.13",
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.12",
4
+ "version": "5.1.13",
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",
@@ -50,7 +50,7 @@ Scale this to the work: routine fixes reuse agreed scope, signer and acceptance
50
50
  | day-1 look at the repo | `fde scan` |
51
51
  | debrief / pasted notes for a bound record | `fde debrief --smart` → agent reconciliation → one plain-English review → Save this update? → `--apply`. `--smart` is a gate, not a brain. `references/debrief.md` |
52
52
  | prep me for … | `fde prep "<label>"` |
53
- | when did we agree | `fde receipts <term>` |
53
+ | did we agree / who decided / why did we | `fde receipts <term>` |
54
54
  | sponsor update / defend the number | `fde defend` |
55
55
  | successor / rotation / portable handoff | `fde handoff` (stdout; `--out new-file.md` only after export requested) |
56
56
  | they went quiet | Review evidence with `references/rescue.md`; confirm a signal change before `fde log contact "…" --signal amber\|green\|red` |
@@ -151,7 +151,7 @@ Work names (engage, diagnose, align, deliver, realize, transfer) are the same ma
151
151
  | Engagement ending, team needs to operate without you, write the runbook | runbook | `references/runbook.md` |
152
152
  | Something worked well and will apply to future engagements, encode the pattern | feedback | `references/encode-pattern.md` |
153
153
  | "Red-team this," "stress-test my plan," poke holes, challenge the plan, what am I missing | red-team | `references/red-team.md` |
154
- | "What did we agree about X?", scope dispute, receipts | - | run `fde receipts <term>`, answer with dates |
154
+ | "Did we agree to X?", "Who decided?", "Why did we choose X?", scope dispute | - | run `fde receipts <term>`; return the decision, person, date, source and later changes; use targeted recall for missing context |
155
155
 
156
156
  **Overlays - activate alongside any skill on signal, don't wait to be told:**
157
157
 
@@ -177,3 +177,9 @@ Ready to build: check that the supplied facts establish the outcome, constraints
177
177
  ### Keep open work visible
178
178
 
179
179
  For confirmed follow-ups, maintain `## Commitments` and `## Open questions` in the existing `context.md`. Use unchecked bullets for unresolved items and check them only after confirmed resolution. A commitment says who owes what to whom; include a source and `due: YYYY-MM-DD` or `review: YYYY-MM-DD` only when agreed. Preserve unresolved `unknown - ask:` questions here when they affect the next decision. Do not infer a promise from a suggestion. `resume` and `prep` surface these entries with sources; dates prompt a status check, not an invented escalation. For a meeting, select relevant entries and use targeted recall for supporting evidence. Users describe the follow-up naturally; maintain the record for them.
180
+
181
+ ### Answer from the record
182
+
183
+ A receipt is dated evidence for a claim, not automatic customer approval. For agreement questions, distinguish proposed work, an agreed decision, a later withdrawal and acceptance of a delivered result. Attribute the decision and rationale only when recorded; the person who wrote a note is not necessarily the decision-maker. If the search finds no agreement evidence, say “I found no recorded agreement in the material checked,” show relevant claims as claims, and name who can clarify only when their authority is known. Do not invent a rationale or treat absence as “never agreed.”
184
+
185
+ For meeting preparation, use `fde prep` as the starting packet, then select unresolved questions and commitments relevant to the meeting's purpose and participants. Include the recorded owner, due/review date and source where available; mark missing fields unknown. Retrieve supporting or conflicting context with targeted recall. Do not turn every old unknown into an agenda item, infer an overdue date, or hide a material blocker merely because its wording does not match the meeting label.