fdeops 3.7.7 → 3.7.8
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 +60 -5
- package/bin/install.js +10 -0
- package/package.json +1 -1
- package/skills/fde/SKILL.md +1 -0
- package/skills/fde/references/land.md +1 -0
- package/templates/.fde/stakeholders.md +8 -0
package/bin/fde.js
CHANGED
|
@@ -169,6 +169,45 @@ function sectionBody(md, heading) {
|
|
|
169
169
|
return body.join('\n').trim()
|
|
170
170
|
}
|
|
171
171
|
|
|
172
|
+
// Append `entry` as the last line of a "## Heading" section, creating the
|
|
173
|
+
// section at end-of-file if it doesn't exist yet. Plain fs.appendFileSync
|
|
174
|
+
// would land the entry after ANY later section the agent added (e.g. a
|
|
175
|
+
// "## Notes" heading appended after "## Signal history"), silently moving a
|
|
176
|
+
// signal token outside the section the reader scans - this keeps it inside
|
|
177
|
+
// regardless of what follows.
|
|
178
|
+
function appendUnderSection(md, heading, entry) {
|
|
179
|
+
const lines = md.split('\n')
|
|
180
|
+
const start = lines.findIndex(l => new RegExp('^#{1,6}\\s+' + heading + '\\b', 'i').test(l.trim()))
|
|
181
|
+
if (start === -1) {
|
|
182
|
+
const sep = md.length && !md.endsWith('\n') ? '\n' : ''
|
|
183
|
+
return `${md}${sep}\n## ${heading}\n\n${entry}\n`
|
|
184
|
+
}
|
|
185
|
+
let end = lines.length
|
|
186
|
+
for (let i = start + 1; i < lines.length; i++) {
|
|
187
|
+
if (/^#{1,6}\s/.test(lines[i].trim())) { end = i; break }
|
|
188
|
+
}
|
|
189
|
+
const before = lines.slice(0, end)
|
|
190
|
+
const after = lines.slice(end)
|
|
191
|
+
while (before.length > start + 1 && before[before.length - 1].trim() === '') before.pop()
|
|
192
|
+
before.push(entry)
|
|
193
|
+
if (after.length) before.push('')
|
|
194
|
+
return before.concat(after).join('\n')
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Shared by cmdLog and cmdDebrief so the two writers can't drift (the earlier
|
|
198
|
+
// bug: fde log's format and fde debrief's format both existed, only one of
|
|
199
|
+
// them matched what extractStakeholders actually read). A contact entry
|
|
200
|
+
// carrying a [signal:x] token - however it got there - lands inside
|
|
201
|
+
// "## Signal history"; everything else is a plain end-of-file append.
|
|
202
|
+
function appendLogEntry(eng, type, entry) {
|
|
203
|
+
const p = path.join(eng, LOG_FILES[type])
|
|
204
|
+
if (type === 'contact' && /\[signal:(red|amber|green)\]/i.test(entry)) {
|
|
205
|
+
fs.writeFileSync(p, appendUnderSection(readEng(eng, LOG_FILES[type]), 'Signal history', entry))
|
|
206
|
+
} else {
|
|
207
|
+
fs.appendFileSync(p, `\n${entry}\n`)
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
172
211
|
// phase / trust / top risk / freshness - identical heuristic for status + dashboard.
|
|
173
212
|
// Trust resolution: structured [signal:red|amber|green] tokens in stakeholders.md
|
|
174
213
|
// (written by `fde log contact --signal` and `fde debrief`) win - the latest dated
|
|
@@ -305,9 +344,20 @@ function extractStakeholders(eng) {
|
|
|
305
344
|
const notesIdx = colIndex(headers, /notes?/i)
|
|
306
345
|
|
|
307
346
|
const history = []
|
|
347
|
+
// Format-agnostic on token position: `fde log contact --signal` writes
|
|
348
|
+
// "[date] [signal:x] text" (token right after the date), but `fde debrief`
|
|
349
|
+
// appends the token at the END of whatever the agent wrote per the skill's
|
|
350
|
+
// own contact: convention - "[date] text [signal:x]". Both are subject-first
|
|
351
|
+
// once the token is stripped, so match the token anywhere on the line rather
|
|
352
|
+
// than requiring it immediately after the date; a debrief-written signal was
|
|
353
|
+
// silently invisible to per-stakeholder matching before this.
|
|
308
354
|
sectionBody(md, 'Signal history').split('\n').forEach(l => {
|
|
309
|
-
const
|
|
310
|
-
if (
|
|
355
|
+
const dm = l.trim().match(/^-\s*\[(\d{4}-\d{2}-\d{2})\]\s*(.*)$/i)
|
|
356
|
+
if (!dm) return
|
|
357
|
+
const sm = dm[2].match(/\[signal:(red|amber|green)\]/i)
|
|
358
|
+
if (!sm) return
|
|
359
|
+
const text = dm[2].replace(/\[signal:(red|amber|green)\]/i, '').trim()
|
|
360
|
+
history.push({ date: dm[1], signal: sm[1].toLowerCase(), text })
|
|
311
361
|
})
|
|
312
362
|
|
|
313
363
|
return rows.map(cs => {
|
|
@@ -620,7 +670,8 @@ function cmdLog(args) {
|
|
|
620
670
|
const eng = resolveEngagement()
|
|
621
671
|
if (!eng) { console.error('no engagement - run: fde resume --init <name>'); process.exit(2) }
|
|
622
672
|
const date = new Date().toISOString().slice(0, 10)
|
|
623
|
-
|
|
673
|
+
const entry = `- [${date}] ${signal ? `[signal:${signal}] ` : ''}${text}`
|
|
674
|
+
appendLogEntry(eng, type, entry)
|
|
624
675
|
console.log(`logged → ${LOG_FILES[type]}${signal ? ` (signal:${signal})` : ''}`)
|
|
625
676
|
}
|
|
626
677
|
|
|
@@ -676,8 +727,12 @@ function cmdDebrief(args) {
|
|
|
676
727
|
const m = bare.match(/^(decision|risk|delivery|contact):\s*(.+)$/i)
|
|
677
728
|
if (m) {
|
|
678
729
|
const type = m[1].toLowerCase()
|
|
679
|
-
|
|
680
|
-
|
|
730
|
+
const entry = `- [${date}] ${m[2]}`
|
|
731
|
+
if (dry) console.log(`→ ${LOG_FILES[type]} ${entry}`)
|
|
732
|
+
// appendLogEntry, not a blind append: a contact: line may carry an
|
|
733
|
+
// inline [signal:x] token (the skill's own convention) and must land
|
|
734
|
+
// inside "## Signal history" the same way `fde log --signal` does.
|
|
735
|
+
else appendLogEntry(eng, type, entry)
|
|
681
736
|
counts[type]++
|
|
682
737
|
} else ctxLines.push(line)
|
|
683
738
|
}
|
package/bin/install.js
CHANGED
|
@@ -142,6 +142,16 @@ function cmdAdapters(targetDir) {
|
|
|
142
142
|
console.log(` fdeops cross-platform adapters → ${dest}`)
|
|
143
143
|
console.log(' One brain (skills/fde/SKILL.md). These are thin pointers per tool.')
|
|
144
144
|
console.log('')
|
|
145
|
+
// The pointers below all point at ~/.claude/skills/fde/SKILL.md. Only the
|
|
146
|
+
// default install (bare `npx fdeops` / `node bin/install.js`) used to place
|
|
147
|
+
// that file - `adapters` alone wrote pointers to a brain that didn't exist
|
|
148
|
+
// yet, a dangling reference for anyone following the documented Cursor/Codex
|
|
149
|
+
// path. installSkills() is idempotent (safe to call every run).
|
|
150
|
+
if (!fs.existsSync(path.join(GLOBAL_SKILLS_DIR, 'fde', 'SKILL.md'))) {
|
|
151
|
+
installSkills()
|
|
152
|
+
console.log(' Skills → ~/.claude/skills/ (installed - the pointers below need this)')
|
|
153
|
+
console.log('')
|
|
154
|
+
}
|
|
145
155
|
for (const a of ADAPTER_TARGETS) {
|
|
146
156
|
if (!fs.existsSync(a.src)) { console.log(` skip ${a.label} (template missing)`); continue }
|
|
147
157
|
placePointer(path.join(dest, a.dest), fs.readFileSync(a.src, 'utf8'), a.label, a.appendable)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fdeops",
|
|
3
|
-
"version": "3.7.
|
|
3
|
+
"version": "3.7.8",
|
|
4
4
|
"description": "Field kit for engineers embedded in client work - a real CLI (recon, memory, portfolio), one @fde skill with field judgment on top, and hooks that make it automatic. Claude Code plugin and any agent that loads skills.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"fdeops": "bin/install.js",
|
package/skills/fde/SKILL.md
CHANGED
|
@@ -28,6 +28,7 @@ This is what makes fdeops a second brain instead of a chat window.
|
|
|
28
28
|
4. **No invented facts - ever.** People, names, quotes, meetings, and numbers exist only if the FDE said them or the repo shows them. Never invent a stakeholder, a conversation, or a source to make the narrative richer - one fabricated name poisons every real citation around it. A missing fact is written as `unknown - ask: <the question>`, nothing else.
|
|
29
29
|
5. **On exit:** before the session ends, append three lines to `context.md`: where we are, what changed today, the next step. The `session-stop` hook backstops this deterministically (hooks resolve the engagement through the workspace registry - no env var needed), but you write the meaningful version.
|
|
30
30
|
6. **One customer, one folder.** Never merge two engagements into one `.fde/`. Confirm which engagement applies when multiple exist.
|
|
31
|
+
7. **Never delete a code-read section when rewriting an artifact.** `stakeholders.md`'s `## Signal history` holds dated `[signal:...]` tokens that `fde status`/`fde receipts`/the dashboard read verbatim; `risks.md`'s `## Retired` is read the same way. Rewriting either file as an artifact (land, audit, stakeholder-radar) is fine - dropping one of these sections is not. Carry existing entries forward untouched.
|
|
31
32
|
|
|
32
33
|
## Data boundary (confirm before touching their code)
|
|
33
34
|
|
|
@@ -69,6 +69,7 @@ Before the end of day 1, ship one visible thing: a small bug fix, a cleanup the
|
|
|
69
69
|
|-----|------|--------|-------|
|
|
70
70
|
| <name> | sponsor / champion / resistor / veto / passed-over | green/amber/red | <evidence, day> |
|
|
71
71
|
```
|
|
72
|
+
If `stakeholders.md` already has a `## Signal history` section (it does from the template), **never delete or overwrite it** when you rewrite this file - it holds the dated `[signal:...]` tokens `fde log contact --signal` and `fde debrief` write, and `fde status`/`fde receipts`/the dashboard read only from that section. Edit the table above it freely; keep the section below intact.
|
|
72
73
|
|
|
73
74
|
**`trust-profile.md`** - sacred data (`<private>` tagged), fears heard, AI policy, approval chain. Sensitive: never loaded for status reads, never into subagent prompts.
|
|
74
75
|
|
|
@@ -8,3 +8,11 @@
|
|
|
8
8
|
|
|
9
9
|
**Trust signal:** green | amber | red
|
|
10
10
|
**Last trust check:**
|
|
11
|
+
|
|
12
|
+
## Signal history
|
|
13
|
+
|
|
14
|
+
<!-- `fde log contact --signal <color>` and `fde debrief` write dated tokens here.
|
|
15
|
+
fde status/receipts read the LATEST dated [signal:...] token in this section
|
|
16
|
+
to drive the trust column - do not delete this heading, and if you rewrite
|
|
17
|
+
this file as an artifact, keep this section's entries intact. -->
|
|
18
|
+
|