fdeops 5.1.18 → 5.1.19
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 +26 -9
- package/bin/install.js +4 -1
- package/mcp/fdeops-ingest/package.json +1 -1
- package/mcp/fdeops-ingest/server.js +5 -2
- package/package.json +1 -1
- package/plugin.json +1 -1
package/bin/fde.js
CHANGED
|
@@ -527,10 +527,11 @@ function formatFsError(err, action, target) {
|
|
|
527
527
|
}
|
|
528
528
|
|
|
529
529
|
let debriefTransactionActive = false
|
|
530
|
+
let activeFileLocks = 0
|
|
530
531
|
const ownedDebriefLocks = new Set()
|
|
531
532
|
|
|
532
533
|
function failFs(err, action, target) {
|
|
533
|
-
if (debriefTransactionActive || ownedDebriefLocks.size) throw new Error(formatFsError(err, action, target))
|
|
534
|
+
if (activeFileLocks || debriefTransactionActive || ownedDebriefLocks.size) throw new Error(formatFsError(err, action, target))
|
|
534
535
|
console.error(formatFsError(err, action, target))
|
|
535
536
|
process.exit(1)
|
|
536
537
|
}
|
|
@@ -546,7 +547,7 @@ function refuseSymlinkWrite(p, opts = {}) {
|
|
|
546
547
|
const msg = st.isSymbolicLink()
|
|
547
548
|
? `refused: ${path.basename(p)} is a symlink - write would leave the engagement tree. Replace it with a real file.`
|
|
548
549
|
: `refused: ${path.basename(p)} is not a regular file - remove it and re-run; every write is refused while it is there.`
|
|
549
|
-
if ((debriefTransactionActive || ownedDebriefLocks.size) && !opts.soft) throw new Error(msg)
|
|
550
|
+
if ((activeFileLocks || debriefTransactionActive || ownedDebriefLocks.size) && !opts.soft) throw new Error(msg)
|
|
550
551
|
if (opts.soft) return msg
|
|
551
552
|
console.error(msg)
|
|
552
553
|
process.exit(1)
|
|
@@ -563,7 +564,7 @@ function refuseSymlinkWrite(p, opts = {}) {
|
|
|
563
564
|
// appending the same .fde file otherwise interleave/corrupt under load.
|
|
564
565
|
function withFileLock(targetPath, fn, opts = {}) {
|
|
565
566
|
if (ownedDebriefLocks.has(targetPath)) return fn()
|
|
566
|
-
if (debriefTransactionActive || ownedDebriefLocks.size) opts = { ...opts, soft: true }
|
|
567
|
+
if (activeFileLocks || debriefTransactionActive || ownedDebriefLocks.size) opts = { ...opts, soft: true }
|
|
567
568
|
const lockPath = targetPath + '.lock'
|
|
568
569
|
const deadline = Date.now() + 5000
|
|
569
570
|
while (true) {
|
|
@@ -585,9 +586,12 @@ function withFileLock(targetPath, fn, opts = {}) {
|
|
|
585
586
|
if (opts.soft) throw e
|
|
586
587
|
failFs(e, 'lock', targetPath)
|
|
587
588
|
}
|
|
589
|
+
// Filesystem failures must unwind this lock instead of exiting in place.
|
|
590
|
+
activeFileLocks++
|
|
588
591
|
try {
|
|
589
592
|
return fn()
|
|
590
593
|
} finally {
|
|
594
|
+
activeFileLocks--
|
|
591
595
|
try { fs.closeSync(fd) } catch (_) {}
|
|
592
596
|
try { fs.unlinkSync(lockPath) } catch (_) {}
|
|
593
597
|
}
|
|
@@ -794,6 +798,13 @@ function appendLogEntry(eng, type, entry, opts = {}) {
|
|
|
794
798
|
})
|
|
795
799
|
// Durable CLI ledger - not rewritten by agent artifact passes.
|
|
796
800
|
lockedAppendFile(path.join(eng, SIGNAL_LEDGER), `${entry}\n`)
|
|
801
|
+
} else if (type === 'risk') {
|
|
802
|
+
withFileLock(p, () => {
|
|
803
|
+
const md = readEng(eng, LOG_FILES[type])
|
|
804
|
+
const retired = md.search(/^#{1,6}\s+Retired\b/im)
|
|
805
|
+
const end = retired < 0 ? md.length : retired
|
|
806
|
+
atomicWriteFile(p, `${md.slice(0, end).trimEnd()}\n\n${entry}\n${retired < 0 ? '' : '\n' + md.slice(end)}`, { soft: true })
|
|
807
|
+
})
|
|
797
808
|
} else {
|
|
798
809
|
lockedAppendFile(p, `\n${entry}\n`)
|
|
799
810
|
}
|
|
@@ -1406,14 +1417,18 @@ function cmdResume(args) {
|
|
|
1406
1417
|
// line - resolution is first-match-wins, so appending a second line would
|
|
1407
1418
|
// leave the stale binding winning and silently write to the wrong client.
|
|
1408
1419
|
const cwd = process.cwd()
|
|
1409
|
-
|
|
1410
|
-
const kept = readRegistry().filter(r => r.workspace !== cwd).map(r => `${r.workspace} ${r.slug}`)
|
|
1411
|
-
kept.push(`${cwd} ${slug}`)
|
|
1420
|
+
let prev
|
|
1412
1421
|
let bindErr = null
|
|
1413
1422
|
// soft: an unwritable registry must not process.exit() from inside the lock -
|
|
1414
1423
|
// that skipped the finally and left a stale .registry.lock behind.
|
|
1415
1424
|
try {
|
|
1416
|
-
withFileLock(REGISTRY, () => {
|
|
1425
|
+
withFileLock(REGISTRY, () => {
|
|
1426
|
+
const registry = readRegistry()
|
|
1427
|
+
prev = registry.find(r => r.workspace === cwd)
|
|
1428
|
+
const kept = registry.filter(r => r.workspace !== cwd).map(r => `${r.workspace} ${r.slug}`)
|
|
1429
|
+
kept.push(`${cwd} ${slug}`)
|
|
1430
|
+
atomicWriteFile(REGISTRY, kept.join('\n') + '\n', { soft: true })
|
|
1431
|
+
}, { soft: true })
|
|
1417
1432
|
} catch (e) { bindErr = e }
|
|
1418
1433
|
if (bindErr || !readRegistry().some(r => r.workspace === cwd && r.slug === slug)) {
|
|
1419
1434
|
// Silently unbound is the worst outcome: the memory exists, every later
|
|
@@ -1423,7 +1438,6 @@ function cmdResume(args) {
|
|
|
1423
1438
|
`could not bind this workspace - ${REGISTRY} is not writable${bindErr ? ` (${bindErr.code || bindErr.message})` : ''}.\n` +
|
|
1424
1439
|
` fix the file (it must be a regular file), or work with: export FDEOPS_ENGAGEMENT=${fdeDir}\n`
|
|
1425
1440
|
)
|
|
1426
|
-
try { fs.unlinkSync(REGISTRY + '.lock') } catch (_) {}
|
|
1427
1441
|
process.exit(1)
|
|
1428
1442
|
}
|
|
1429
1443
|
console.log(`ENGAGEMENT READY: ${fdeDir}\nbound to workspace: ${cwd}`)
|
|
@@ -2540,7 +2554,10 @@ function cmdIngest(args) {
|
|
|
2540
2554
|
if (args[i] === '--force') { force = true; continue }
|
|
2541
2555
|
rest.push(args[i])
|
|
2542
2556
|
}
|
|
2543
|
-
|
|
2557
|
+
// Redact before slugification: filenames no longer retain privacy markers.
|
|
2558
|
+
const cleanMetadata = value => masking.mask(splitPrivate(value, { sealDangling: true }).clean).replace(/[\r\n]+/g, ' ')
|
|
2559
|
+
source = sanitizeIngestToken(cleanMetadata(source), 'manual')
|
|
2560
|
+
title = cleanMetadata(title)
|
|
2544
2561
|
const titleSlug = sanitizeIngestToken(title || 'notes', 'notes')
|
|
2545
2562
|
if (!title) title = titleSlug
|
|
2546
2563
|
|
package/bin/install.js
CHANGED
|
@@ -294,7 +294,10 @@ function placePointer(destPath, content, label, appendable) {
|
|
|
294
294
|
mkdir(path.dirname(destPath))
|
|
295
295
|
if (fs.existsSync(destPath)) {
|
|
296
296
|
const existing = fs.readFileSync(destPath, 'utf8')
|
|
297
|
-
|
|
297
|
+
// Templates can change wording; a branded pointer remains installed.
|
|
298
|
+
const hasIdentity = existing.includes(FDE_MARKER) || /^#\s+fde(?:ops|os)\b/im.test(existing)
|
|
299
|
+
const hasSkillPointer = /\bskills\/fde\/SKILL\.md\b/.test(existing)
|
|
300
|
+
if (hasIdentity && hasSkillPointer) {
|
|
298
301
|
console.log(` skip ${label} (already wired)`)
|
|
299
302
|
return
|
|
300
303
|
}
|
|
@@ -164,8 +164,11 @@ function runFde(args, stdin, extraEnv) {
|
|
|
164
164
|
}
|
|
165
165
|
|
|
166
166
|
function engagementEnv(args) {
|
|
167
|
-
|
|
168
|
-
|
|
167
|
+
if (!Object.prototype.hasOwnProperty.call(args, 'engagement')) return {}
|
|
168
|
+
if (typeof args.engagement !== 'string' || !args.engagement.trim()) {
|
|
169
|
+
throw new Error('engagement must be a non-empty string when supplied; no customer was selected')
|
|
170
|
+
}
|
|
171
|
+
return { FDEOPS_ENGAGEMENT: args.engagement.trim() }
|
|
169
172
|
}
|
|
170
173
|
|
|
171
174
|
function cliPayload(out) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fdeops",
|
|
3
|
-
"version": "5.1.
|
|
3
|
+
"version": "5.1.19",
|
|
4
4
|
"description": "Skills for forward deployed engineers across strategy, architecture and engineering. Use individual tasks or @fde coordination; local customer memory supports continuity.",
|
|
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.
|
|
4
|
+
"version": "5.1.19",
|
|
5
5
|
"description": "Skills for forward deployed engineers across strategy, architecture and engineering. Use individual tasks or @fde coordination; local customer memory supports continuity.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Subash Natarajan",
|