fdeops 3.7.3 → 3.7.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/bin/fde.js +45 -13
- package/hooks/pre-compact +8 -2
- package/hooks/session-start +8 -2
- package/hooks/session-stop +8 -2
- package/package.json +1 -1
package/bin/fde.js
CHANGED
|
@@ -93,9 +93,17 @@ function resolveEngagement() {
|
|
|
93
93
|
// 1) explicit env (back-compat: accept old FDEOS_ENGAGEMENT too)
|
|
94
94
|
const env = (process.env.FDEOPS_ENGAGEMENT || process.env.FDEOS_ENGAGEMENT || '').replace(/^~/, HOME).trim()
|
|
95
95
|
if (env && fs.existsSync(env)) return env
|
|
96
|
-
// 2) workspace registry binding (written by resume --init)
|
|
96
|
+
// 2) workspace registry binding (written by resume --init). Match the cwd OR
|
|
97
|
+
// any ancestor of it - FDEs run commands from src/, packages/api/, etc., not
|
|
98
|
+
// just the repo root where they bound. Nearest (deepest) registered ancestor
|
|
99
|
+
// wins, exactly like git searching upward for .git. `startsWith(workspace +
|
|
100
|
+
// sep)` requires a true path-boundary ancestor, so /work/repo-2 never matches
|
|
101
|
+
// a binding on /work/repo. Kept in lockstep with registry_engagement_dir in
|
|
102
|
+
// hooks/session-start, session-stop, pre-compact.
|
|
97
103
|
const cwd = process.cwd()
|
|
98
|
-
const reg = readRegistry()
|
|
104
|
+
const reg = readRegistry()
|
|
105
|
+
.filter(r => cwd === r.workspace || cwd.startsWith(r.workspace + path.sep))
|
|
106
|
+
.sort((a, b) => b.workspace.length - a.workspace.length)[0]
|
|
99
107
|
if (reg) {
|
|
100
108
|
const p = path.join(ENGAGEMENTS_ROOT, reg.slug, '.fde')
|
|
101
109
|
if (fs.existsSync(p)) return p
|
|
@@ -667,18 +675,42 @@ function cmdReceipts(args) {
|
|
|
667
675
|
const eng = resolveEngagement()
|
|
668
676
|
if (!eng) { console.error('no engagement - run: fde resume --init <name>'); process.exit(2) }
|
|
669
677
|
const rx = new RegExp(term.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'i')
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
678
|
+
// "receipts" answers "what did we AGREE?" - so dated, agreed records are the
|
|
679
|
+
// receipt. brief.md is the client's hypothesis and reality.md/context.md are
|
|
680
|
+
// working notes; a hit there is a CLAIM, not an agreement. Keeping them in the
|
|
681
|
+
// same list let an FDE cite a sales promise as a receipt - so they get a
|
|
682
|
+
// separate, clearly-labelled section that is never mistaken for the record.
|
|
683
|
+
const AGREEMENTS = ['decisions.md', 'delivery.md', 'success.md', 'risks.md', 'stakeholders.md']
|
|
684
|
+
const CLAIMS = ['brief.md', 'reality.md', 'context.md']
|
|
685
|
+
const collect = files => {
|
|
686
|
+
const hits = []
|
|
687
|
+
for (const f of files) {
|
|
688
|
+
if (!fs.existsSync(path.join(eng, f))) continue
|
|
689
|
+
// readClean, not raw read: receipts must not grep sealed <private> notes
|
|
690
|
+
// back out. Redaction can shift line numbers past a multi-line block; the
|
|
691
|
+
// file:line is advisory - not leaking a sealed secret is worth that.
|
|
692
|
+
readClean(eng, f).split('\n').forEach((l, i) => {
|
|
693
|
+
if (rx.test(l)) hits.push(` ${f}:${i + 1} ${l.trim().slice(0, 160)}`)
|
|
694
|
+
})
|
|
695
|
+
}
|
|
696
|
+
return hits
|
|
697
|
+
}
|
|
698
|
+
const agreed = collect(AGREEMENTS)
|
|
699
|
+
const claimed = collect(CLAIMS)
|
|
700
|
+
if (agreed.length) {
|
|
701
|
+
console.log('AGREED (dated record - defensible):')
|
|
702
|
+
agreed.forEach(h => console.log(h))
|
|
703
|
+
}
|
|
704
|
+
if (claimed.length) {
|
|
705
|
+
if (agreed.length) console.log('')
|
|
706
|
+
console.log('CLAIMS & working notes (stated, NOT an agreement - verify before citing):')
|
|
707
|
+
claimed.forEach(h => console.log(h))
|
|
708
|
+
}
|
|
709
|
+
if (!agreed.length && !claimed.length) {
|
|
710
|
+
console.log(`no record of "${term}" - nothing was ever logged about it. A gap in the record, not proof of absence: if it WAS agreed, log it now, dated today.`)
|
|
711
|
+
} else if (!agreed.length) {
|
|
712
|
+
console.log('\n(no dated agreement matched - only unverified claims above. If this was agreed, log it: fde log decision "...")')
|
|
680
713
|
}
|
|
681
|
-
if (!found) console.log(`no record of "${term}" - nothing was ever logged about it. A gap in the record, not proof of absence: if it WAS agreed, log it now, dated today.`)
|
|
682
714
|
}
|
|
683
715
|
|
|
684
716
|
function cmdCapture() {
|
package/hooks/pre-compact
CHANGED
|
@@ -18,9 +18,15 @@ resolve_engagement_dir() {
|
|
|
18
18
|
registry_engagement_dir() {
|
|
19
19
|
local reg="$HOME/fde-engagements/.registry" slug
|
|
20
20
|
[ -f "$reg" ] || return 1
|
|
21
|
-
slug=$(awk 'BEGIN{ws=ENVIRON["PWD"]}
|
|
21
|
+
slug=$(awk 'BEGIN{ws=ENVIRON["PWD"]; best=-1}
|
|
22
22
|
{ i = match($0, / [^ ]*$/)
|
|
23
|
-
if (i > 0
|
|
23
|
+
if (i > 0) {
|
|
24
|
+
wpath = substr($0, 1, i - 1)
|
|
25
|
+
if (ws == wpath || index(ws, wpath "/") == 1) {
|
|
26
|
+
if (length(wpath) > best) { best = length(wpath); slug = substr($0, i + 1) }
|
|
27
|
+
}
|
|
28
|
+
} }
|
|
29
|
+
END { if (best >= 0) print slug }' "$reg" 2>/dev/null)
|
|
24
30
|
[ -z "$slug" ] && return 1
|
|
25
31
|
[ -d "$HOME/fde-engagements/$slug/.fde" ] && printf '%s\n' "$HOME/fde-engagements/$slug/.fde" && return 0
|
|
26
32
|
return 1
|
package/hooks/session-start
CHANGED
|
@@ -30,9 +30,15 @@ resolve_engagement_dir() {
|
|
|
30
30
|
registry_engagement_dir() {
|
|
31
31
|
local reg="$HOME/fde-engagements/.registry" slug
|
|
32
32
|
[ -f "$reg" ] || return 1
|
|
33
|
-
slug=$(awk 'BEGIN{ws=ENVIRON["PWD"]}
|
|
33
|
+
slug=$(awk 'BEGIN{ws=ENVIRON["PWD"]; best=-1}
|
|
34
34
|
{ i = match($0, / [^ ]*$/)
|
|
35
|
-
if (i > 0
|
|
35
|
+
if (i > 0) {
|
|
36
|
+
wpath = substr($0, 1, i - 1)
|
|
37
|
+
if (ws == wpath || index(ws, wpath "/") == 1) {
|
|
38
|
+
if (length(wpath) > best) { best = length(wpath); slug = substr($0, i + 1) }
|
|
39
|
+
}
|
|
40
|
+
} }
|
|
41
|
+
END { if (best >= 0) print slug }' "$reg" 2>/dev/null)
|
|
36
42
|
[ -z "$slug" ] && return 1
|
|
37
43
|
[ -d "$HOME/fde-engagements/$slug/.fde" ] && printf '%s\n' "$HOME/fde-engagements/$slug/.fde" && return 0
|
|
38
44
|
return 1
|
package/hooks/session-stop
CHANGED
|
@@ -26,9 +26,15 @@ resolve_engagement_dir() {
|
|
|
26
26
|
registry_engagement_dir() {
|
|
27
27
|
local reg="$HOME/fde-engagements/.registry" slug
|
|
28
28
|
[ -f "$reg" ] || return 1
|
|
29
|
-
slug=$(awk 'BEGIN{ws=ENVIRON["PWD"]}
|
|
29
|
+
slug=$(awk 'BEGIN{ws=ENVIRON["PWD"]; best=-1}
|
|
30
30
|
{ i = match($0, / [^ ]*$/)
|
|
31
|
-
if (i > 0
|
|
31
|
+
if (i > 0) {
|
|
32
|
+
wpath = substr($0, 1, i - 1)
|
|
33
|
+
if (ws == wpath || index(ws, wpath "/") == 1) {
|
|
34
|
+
if (length(wpath) > best) { best = length(wpath); slug = substr($0, i + 1) }
|
|
35
|
+
}
|
|
36
|
+
} }
|
|
37
|
+
END { if (best >= 0) print slug }' "$reg" 2>/dev/null)
|
|
32
38
|
[ -z "$slug" ] && return 1
|
|
33
39
|
[ -d "$HOME/fde-engagements/$slug/.fde" ] && printf '%s\n' "$HOME/fde-engagements/$slug/.fde" && return 0
|
|
34
40
|
return 1
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fdeops",
|
|
3
|
-
"version": "3.7.
|
|
3
|
+
"version": "3.7.4",
|
|
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",
|