@goodandready/dsh-moa 0.2.74 → 0.2.76

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.
@@ -0,0 +1,401 @@
1
+ /**
2
+ * DeepSeek Harness Mixture of Agents (MoA) — Chain-of-Verification (CoVe) Fact-Checking Gate (#314)
3
+ *
4
+ * Pre-Judge verification gate that analyzes candidate proposals, formulates 3-5 factual API & syntax
5
+ * verification questions, audits them against standard libraries & project context, and flags
6
+ * hallucinated methods or nonexistent modules before arbitration.
7
+ */
8
+
9
+ import { MOA_EVENTS, emitEvent } from '../events.js'
10
+
11
+ /**
12
+ * Standard Node.js built-in modules.
13
+ */
14
+ export const NODE_STDLIB_MODULES = new Set([
15
+ 'assert', 'async_hooks', 'buffer', 'child_process', 'cluster', 'console',
16
+ 'constants', 'crypto', 'dgram', 'diagnostics_channel', 'dns', 'domain',
17
+ 'events', 'fs', 'fs/promises', 'http', 'http2', 'https', 'inspector',
18
+ 'module', 'net', 'os', 'path', 'path/posix', 'path/win32', 'perf_hooks',
19
+ 'process', 'punycode', 'querystring', 'readline', 'repl', 'stream',
20
+ 'stream/promises', 'stream/web', 'string_decoder', 'test', 'timers',
21
+ 'timers/promises', 'tls', 'trace_events', 'tty', 'url', 'util',
22
+ 'v8', 'vm', 'wasi', 'worker_threads', 'zlib',
23
+ ])
24
+
25
+ /**
26
+ * Common hallucinated APIs and their real replacements.
27
+ */
28
+ export const KNOWN_HALLUCINATIONS = [
29
+ {
30
+ regex: /fs\.(?:readAllText|readTextFile|readText|readAsString)\s*\(/i,
31
+ hallucination: 'fs.readAllText / fs.readTextFile',
32
+ correction: 'fs.readFileSync(path, "utf8") or fs.promises.readFile(path, "utf8")',
33
+ reason: 'Method does not exist in Node.js fs module (often hallucinated from C# .NET or Deno).',
34
+ },
35
+ {
36
+ regex: /fs\.(?:writeAllText|writeTextFile|writeText|writeAsString)\s*\(/i,
37
+ hallucination: 'fs.writeAllText / fs.writeTextFile',
38
+ correction: 'fs.writeFileSync(path, data, "utf8") or fs.promises.writeFile(path, data, "utf8")',
39
+ reason: 'Method does not exist in Node.js fs module.',
40
+ },
41
+ {
42
+ regex: /fs\.readFileAsync\s*\(/i,
43
+ hallucination: 'fs.readFileAsync',
44
+ correction: 'fs.promises.readFile(...) or util.promisify(fs.readFile)',
45
+ reason: 'Legacy Bluebird promise naming is not a native Node.js fs method.',
46
+ },
47
+ {
48
+ regex: /path\.(?:normalizeUrl|combine|joinPath)\s*\(/i,
49
+ hallucination: 'path.combine / path.normalizeUrl',
50
+ correction: 'path.join(...) or new URL(...)',
51
+ reason: 'Method does not exist in Node.js path module.',
52
+ },
53
+ {
54
+ regex: /crypto\.(?:generateRandomUuidString|randomString|generateUuid)\s*\(/i,
55
+ hallucination: 'crypto.generateRandomUuidString',
56
+ correction: 'crypto.randomUUID() or crypto.randomBytes(...)',
57
+ reason: 'Nonexistent crypto helper hallucinated by language models.',
58
+ },
59
+ {
60
+ regex: /JSON\.(?:toObject|fromObject|encode|decode)\s*\(/i,
61
+ hallucination: 'JSON.toObject / JSON.fromObject / JSON.encode',
62
+ correction: 'JSON.parse(...) or JSON.stringify(...)',
63
+ reason: 'Invalid JSON global methods hallucinated from other ecosystems.',
64
+ },
65
+ {
66
+ regex: /Promise\.(?:wait|sleep|delay)\s*\(/i,
67
+ hallucination: 'Promise.sleep / Promise.wait / Promise.delay',
68
+ correction: 'new Promise(resolve => setTimeout(resolve, ms)) or timers/promises setTimeout',
69
+ reason: 'Native JavaScript Promise does not feature sleep/delay directly on Promise constructor.',
70
+ },
71
+ ]
72
+
73
+ /**
74
+ * Extracts candidate imports, requires, and method calls to formulate verification questions.
75
+ *
76
+ * @param {string} code
77
+ * @param {object} [options]
78
+ * @returns {Array<object>} Up to maxQuestions questions
79
+ */
80
+ export function generateCoveQuestions(code, options = {}) {
81
+ const maxQuestions = Math.min(10, Math.max(1, options.maxQuestions || 5))
82
+ const questions = []
83
+ if (!code || typeof code !== 'string') return questions
84
+
85
+ // 1. Check imports/requires
86
+ const importRegex = /(?:import\s+(?:\{[^}]+\}|\*\s+as\s+\w+|\w+)\s+from\s+['"]([^'"]+)['"]|require\s*\(\s*['"]([^'"]+)['"]\s*\))/g
87
+ let match
88
+ const seenModules = new Set()
89
+ while ((match = importRegex.exec(code)) !== null) {
90
+ const mod = match[1] || match[2]
91
+ if (mod && !seenModules.has(mod) && !mod.startsWith('.') && !mod.startsWith('/')) {
92
+ seenModules.add(mod)
93
+ const cleanMod = mod.replace(/^node:/, '')
94
+ questions.push({
95
+ id: questions.length + 1,
96
+ type: 'module_resolution',
97
+ subject: mod,
98
+ cleanSubject: cleanMod,
99
+ question: `Is module '${mod}' available in standard library or project dependencies?`,
100
+ })
101
+ if (questions.length >= maxQuestions) return questions
102
+ }
103
+ }
104
+
105
+ // 2. Check for known hallucination method calls
106
+ for (const h of KNOWN_HALLUCINATIONS) {
107
+ if (h.regex.test(code)) {
108
+ questions.push({
109
+ id: questions.length + 1,
110
+ type: 'api_existence',
111
+ subject: h.hallucination,
112
+ hallucinationDef: h,
113
+ question: `Does API '${h.hallucination}' exist and is it valid in the target runtime?`,
114
+ })
115
+ if (questions.length >= maxQuestions) return questions
116
+ }
117
+ }
118
+
119
+ // 3. Check general API calls (fs, path, crypto, child_process)
120
+ const apiCallRegex = /\b(fs|path|crypto|os|child_process|JSON|Promise)\.([a-zA-Z0-9_]+)\s*\(/g
121
+ const seenApis = new Set()
122
+ while ((match = apiCallRegex.exec(code)) !== null) {
123
+ const obj = match[1]
124
+ const method = match[2]
125
+ const full = `${obj}.${method}`
126
+ if (!seenApis.has(full)) {
127
+ seenApis.add(full)
128
+ questions.push({
129
+ id: questions.length + 1,
130
+ type: 'method_signature',
131
+ subject: full,
132
+ object: obj,
133
+ method,
134
+ question: `Does method '${method}' natively exist on standard global '${obj}'?`,
135
+ })
136
+ if (questions.length >= maxQuestions) return questions
137
+ }
138
+ }
139
+
140
+ // Fallback question if code has no special calls
141
+ if (questions.length === 0) {
142
+ questions.push({
143
+ id: 1,
144
+ type: 'syntax_coherence',
145
+ subject: 'code_structure',
146
+ question: 'Are all functions and return types syntactically aligned and verifiable?',
147
+ })
148
+ }
149
+
150
+ return questions.slice(0, maxQuestions)
151
+ }
152
+
153
+ /**
154
+ * Verifies a single CoVe factual question against stdlib and project dependencies.
155
+ *
156
+ * @param {object} q - Question object from generateCoveQuestions
157
+ * @param {object} [projectContext]
158
+ * @returns {object} Verification result
159
+ */
160
+ export function verifyCoveQuestion(q, projectContext = {}) {
161
+ const deps = {
162
+ ...(projectContext.dependencies || {}),
163
+ ...(projectContext.devDependencies || {}),
164
+ ...(projectContext.stackInfo?.manifestDependencies || {}),
165
+ }
166
+
167
+ if (q.type === 'module_resolution') {
168
+ const isNode = NODE_STDLIB_MODULES.has(q.cleanSubject)
169
+ const isDep = Boolean(deps[q.cleanSubject] || deps[q.subject])
170
+ if (isNode || isDep) {
171
+ return {
172
+ verified: true,
173
+ confidence: 1.0,
174
+ rationale: isNode
175
+ ? `Module '${q.subject}' is a verified Node.js standard library module.`
176
+ : `Module '${q.subject}' is defined in project manifest dependencies.`,
177
+ hallucination: null,
178
+ }
179
+ }
180
+ return {
181
+ verified: false,
182
+ confidence: 0.85,
183
+ rationale: `Module '${q.subject}' is not found in Node.js stdlib or declared project dependencies.`,
184
+ hallucination: {
185
+ api: q.subject,
186
+ reason: `Module '${q.subject}' was referenced without declaration in package.json or stdlib.`,
187
+ },
188
+ }
189
+ }
190
+
191
+ if (q.type === 'api_existence') {
192
+ // Matched a known hallucination pattern
193
+ const h = q.hallucinationDef
194
+ return {
195
+ verified: false,
196
+ confidence: 0.95,
197
+ rationale: `Invalid API detected: ${h.reason} Suggested fix: ${h.correction}`,
198
+ hallucination: {
199
+ api: h.hallucination,
200
+ reason: h.reason,
201
+ correction: h.correction,
202
+ },
203
+ }
204
+ }
205
+
206
+ if (q.type === 'method_signature') {
207
+ // Verify common valid methods on known objects
208
+ const validFs = new Set(['readFile', 'readFileSync', 'writeFile', 'writeFileSync', 'existsSync', 'statSync', 'stat', 'mkdirSync', 'mkdir', 'unlinkSync', 'unlink', 'readdirSync', 'readdir', 'createReadStream', 'createWriteStream', 'promises'])
209
+ const validPath = new Set(['join', 'resolve', 'basename', 'dirname', 'extname', 'relative', 'normalize', 'isAbsolute', 'parse', 'format', 'sep', 'posix', 'win32'])
210
+ const validCrypto = new Set(['randomUUID', 'randomBytes', 'createHash', 'createHmac', 'createCipheriv', 'createDecipheriv', 'timingSafeEqual', 'scryptSync', 'pbkdf2Sync'])
211
+ const validJson = new Set(['parse', 'stringify'])
212
+ const validPromise = new Set(['all', 'allSettled', 'race', 'any', 'resolve', 'reject'])
213
+
214
+ let isValid = true
215
+ if (q.object === 'fs') isValid = validFs.has(q.method)
216
+ else if (q.object === 'path') isValid = validPath.has(q.method)
217
+ else if (q.object === 'crypto') isValid = validCrypto.has(q.method)
218
+ else if (q.object === 'JSON') isValid = validJson.has(q.method)
219
+ else if (q.object === 'Promise') isValid = validPromise.has(q.method)
220
+
221
+ if (isValid) {
222
+ return {
223
+ verified: true,
224
+ confidence: 0.95,
225
+ rationale: `'${q.subject}' is a verified standard method.`,
226
+ hallucination: null,
227
+ }
228
+ }
229
+ return {
230
+ verified: false,
231
+ confidence: 0.9,
232
+ rationale: `'${q.method}' does not exist on global '${q.object}'.`,
233
+ hallucination: {
234
+ api: q.subject,
235
+ reason: `Method '${q.method}' does not exist on '${q.object}'.`,
236
+ },
237
+ }
238
+ }
239
+
240
+ return {
241
+ verified: true,
242
+ confidence: 0.9,
243
+ rationale: 'Verified general syntax structure.',
244
+ hallucination: null,
245
+ }
246
+ }
247
+
248
+ /**
249
+ * Audits a single candidate solution using Chain-of-Verification (CoVe).
250
+ *
251
+ * @param {string} code
252
+ * @param {object} [projectContext]
253
+ * @param {object} [options]
254
+ * @returns {object}
255
+ */
256
+ export function auditCandidateCove(code, projectContext = {}, options = {}) {
257
+ const questions = generateCoveQuestions(code, options)
258
+ const verifications = []
259
+ const hallucinations = []
260
+
261
+ for (const q of questions) {
262
+ const v = verifyCoveQuestion(q, projectContext)
263
+ verifications.push({
264
+ ...q,
265
+ verified: v.verified,
266
+ confidence: v.confidence,
267
+ rationale: v.rationale,
268
+ })
269
+ if (!v.verified && v.hallucination) {
270
+ hallucinations.push(v.hallucination)
271
+ }
272
+ }
273
+
274
+ const penalty = hallucinations.length * 35
275
+ const coveScore = Math.max(0, 100 - penalty)
276
+ const passed = hallucinations.length === 0
277
+
278
+ let summary = ''
279
+ if (passed) {
280
+ summary = `🔍 CoVe Fact-Checking: PASSED (Score 100/100). Verified ${questions.length} factual API/module question(s).`
281
+ } else {
282
+ summary = `⚠️ CoVe Hallucination Detected (Score ${coveScore}/100): ${hallucinations.length} non-existent or unverified API(s) found.`
283
+ }
284
+
285
+ return {
286
+ passed,
287
+ coveScore,
288
+ questions: verifications,
289
+ hallucinations,
290
+ summary,
291
+ }
292
+ }
293
+
294
+ /**
295
+ * Runs the Chain-of-Verification Gate across all candidates.
296
+ *
297
+ * @param {Array<object>} candidates
298
+ * @param {object} [projectContext]
299
+ * @param {object} [options]
300
+ * @returns {object}
301
+ */
302
+ export function runCoveVerificationGate(candidates, projectContext = {}, options = {}) {
303
+ const { ctx, runId, onProgress } = options
304
+ const results = []
305
+ let totalHallucinations = 0
306
+
307
+ if (!Array.isArray(candidates) || candidates.length === 0) {
308
+ return {
309
+ results: [],
310
+ passedCount: 0,
311
+ hallucinationCount: 0,
312
+ coveDirective: null,
313
+ }
314
+ }
315
+
316
+ for (let i = 0; i < candidates.length; i++) {
317
+ const cand = candidates[i]
318
+ const text = typeof cand === 'string' ? cand : cand.output || cand.text || cand.code || ''
319
+ const report = auditCandidateCove(text, projectContext, options)
320
+
321
+ if (typeof cand === 'object' && cand !== null) {
322
+ cand.coveReport = report
323
+ cand.coveScore = report.coveScore
324
+ cand.coveHallucinations = report.hallucinations
325
+ }
326
+
327
+ totalHallucinations += report.hallucinations.length
328
+ results.push({
329
+ candidateIndex: i,
330
+ model: cand?.model || `Candidate ${i + 1}`,
331
+ report,
332
+ })
333
+
334
+ if (ctx) {
335
+ emitEvent(ctx, MOA_EVENTS.COVE_VERIFIED, {
336
+ runId,
337
+ candidateIndex: i,
338
+ coveScore: report.coveScore,
339
+ passed: report.passed,
340
+ questionsCount: report.questions.length,
341
+ hallucinationsCount: report.hallucinations.length,
342
+ })
343
+ }
344
+ }
345
+
346
+ const passedCount = results.filter((r) => r.report.passed).length
347
+
348
+ if (typeof onProgress === 'function') {
349
+ if (totalHallucinations === 0) {
350
+ onProgress(`🔍 *CoVe Fact-Check Passed*: All APIs and modules verified against project context.\n`)
351
+ } else {
352
+ onProgress(`⚠️ *CoVe Fact-Check Alert*: Found ${totalHallucinations} unverified or hallucinated API(s) across candidate solutions.\n`)
353
+ }
354
+ }
355
+
356
+ const coveDirective = formatCoveDirective(results)
357
+
358
+ return {
359
+ results,
360
+ passedCount,
361
+ hallucinationCount: totalHallucinations,
362
+ coveDirective,
363
+ }
364
+ }
365
+
366
+ /**
367
+ * Formats CoVe fact-checking verification findings into a prompt directive for Judge/Synthesis.
368
+ *
369
+ * @param {Array<object>} results
370
+ * @returns {string|null}
371
+ */
372
+ export function formatCoveDirective(results) {
373
+ if (!Array.isArray(results) || results.length === 0) return null
374
+
375
+ const hasAnyHallucinations = results.some((r) => r.report && r.report.hallucinations.length > 0)
376
+ if (!hasAnyHallucinations) {
377
+ return '### 🔍 CHAIN-OF-VERIFICATION (CoVe): ALL CANDIDATE APIS VERIFIED FACTUAL\nNo hallucinated APIs, phantom modules, or broken native signatures detected.'
378
+ }
379
+
380
+ const lines = [
381
+ '### 🔍 CHAIN-OF-VERIFICATION (CoVe) FACT-CHECK DIRECTIVE',
382
+ 'Independent factual audit of API calls and module imports has been completed:',
383
+ ]
384
+
385
+ for (const r of results) {
386
+ const label = `Candidate ${r.candidateIndex + 1}`
387
+ const rep = r.report || {}
388
+ if (!rep.passed && rep.hallucinations.length > 0) {
389
+ lines.push(`- **${label}**: ❌ **FACT-CHECK FAILURE** (Score ${rep.coveScore}/100) — Hallucinated APIs detected:`)
390
+ for (const h of rep.hallucinations) {
391
+ lines.push(` * \`${h.api}\`: ${h.reason}${h.correction ? ` (Correction: ${h.correction})` : ''}`)
392
+ }
393
+ } else {
394
+ lines.push(`- **${label}**: ✅ VERIFIED (Score 100/100) — All ${rep.questions?.length || 0} API checks confirmed valid.`)
395
+ }
396
+ }
397
+
398
+ lines.push('\n**Judge Fact-Checking Rule**: Do not award winning status to solutions that rely on hallucinated methods or nonexistent packages. Prefer solutions with 100/100 verified API accuracy.')
399
+
400
+ return lines.join('\n')
401
+ }
@@ -0,0 +1,196 @@
1
+ /**
2
+ * Self-Learning Developer Preferences Metaprompt (#309)
3
+ *
4
+ * Tracks developer coding principles, architectural standards, and preferred idioms
5
+ * in a persistent local store (~/.dsh/moa/preferences.json).
6
+ * Injects a concise "Developer Coding Principles" directive into the Judge
7
+ * synthesis phase (and candidate prompts) to bias consensus toward the engineer's tastes.
8
+ */
9
+
10
+ import fs from 'node:fs'
11
+ import path from 'node:path'
12
+ import os from 'node:os'
13
+
14
+ export const DEFAULT_PREFERENCES = Object.freeze([
15
+ {
16
+ id: 'strict-typing',
17
+ title: 'Strict Types & Explicit Contracts',
18
+ rule: "Never use loose 'any' types or unchecked type casts. Explicitly define interfaces, return types, and input validation schemas.",
19
+ category: 'typing',
20
+ enabled: true,
21
+ },
22
+ {
23
+ id: 'functional-purity',
24
+ title: 'Functional Immutability',
25
+ rule: 'Prefer pure functions, immutable data transforms, and explicit return values over hidden global state mutations.',
26
+ category: 'architecture',
27
+ enabled: true,
28
+ },
29
+ {
30
+ id: 'defensive-errors',
31
+ title: 'Defensive Error Handling',
32
+ rule: 'Never swallow exceptions silently. Use structured error types, descriptive messages, and clean fallback strategies.',
33
+ category: 'reliability',
34
+ enabled: true,
35
+ },
36
+ ])
37
+
38
+ /**
39
+ * Resolves the persistent preferences file path.
40
+ *
41
+ * @param {string} [customPath]
42
+ * @returns {string}
43
+ */
44
+ export function getPreferencesFilePath(customPath = null) {
45
+ if (customPath && typeof customPath === 'string' && customPath.trim()) {
46
+ return path.resolve(customPath.trim())
47
+ }
48
+ return path.join(os.homedir(), '.dsh', 'moa', 'preferences.json')
49
+ }
50
+
51
+ /**
52
+ * Loads developer preferences from disk with default fallback.
53
+ *
54
+ * @param {string} [customPath]
55
+ * @returns {Array<object>}
56
+ */
57
+ export function loadDeveloperPreferences(customPath = null) {
58
+ const filePath = getPreferencesFilePath(customPath)
59
+ if (!fs.existsSync(filePath)) {
60
+ // Seed default preferences file
61
+ try {
62
+ const dir = path.dirname(filePath)
63
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true })
64
+ fs.writeFileSync(filePath, JSON.stringify(DEFAULT_PREFERENCES, null, 2), 'utf8')
65
+ } catch {
66
+ // safe fallback if filesystem is read-only
67
+ }
68
+ return [...DEFAULT_PREFERENCES]
69
+ }
70
+
71
+ try {
72
+ const raw = fs.readFileSync(filePath, 'utf8')
73
+ const parsed = JSON.parse(raw)
74
+ if (Array.isArray(parsed)) {
75
+ return parsed.filter((p) => p && typeof p === 'object' && p.id && p.rule)
76
+ }
77
+ } catch {
78
+ // safe fallback
79
+ }
80
+
81
+ return [...DEFAULT_PREFERENCES]
82
+ }
83
+
84
+ /**
85
+ * Atomically saves developer preferences to disk.
86
+ *
87
+ * @param {Array<object>} preferences
88
+ * @param {string} [customPath]
89
+ * @returns {boolean}
90
+ */
91
+ export function saveDeveloperPreferences(preferences, customPath = null) {
92
+ if (!Array.isArray(preferences)) return false
93
+ const filePath = getPreferencesFilePath(customPath)
94
+
95
+ try {
96
+ const dir = path.dirname(filePath)
97
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true })
98
+ const tmp = `${filePath}.tmp.${Date.now()}`
99
+ fs.writeFileSync(tmp, JSON.stringify(preferences, null, 2), 'utf8')
100
+ fs.renameSync(tmp, filePath)
101
+ return true
102
+ } catch {
103
+ return false
104
+ }
105
+ }
106
+
107
+ /**
108
+ * Adds a new developer preference rule.
109
+ *
110
+ * @param {object} item - { title, rule, category, enabled }
111
+ * @param {string} [customPath]
112
+ * @returns {object|null}
113
+ */
114
+ export function addDeveloperPreference(item = {}, customPath = null) {
115
+ if (!item || !item.rule || typeof item.rule !== 'string') return null
116
+ const current = loadDeveloperPreferences(customPath)
117
+
118
+ const id = item.id || `pref-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`
119
+ const newPref = {
120
+ id,
121
+ title: (item.title && String(item.title).trim()) || 'Custom Coding Standard',
122
+ rule: String(item.rule).trim(),
123
+ category: (item.category && String(item.category).trim()) || 'general',
124
+ enabled: item.enabled !== false,
125
+ createdAt: new Date().toISOString(),
126
+ }
127
+
128
+ current.push(newPref)
129
+ saveDeveloperPreferences(current, customPath)
130
+ return newPref
131
+ }
132
+
133
+ /**
134
+ * Updates an existing developer preference.
135
+ *
136
+ * @param {string} id
137
+ * @param {object} updates
138
+ * @param {string} [customPath]
139
+ * @returns {object|null}
140
+ */
141
+ export function updateDeveloperPreference(id, updates = {}, customPath = null) {
142
+ if (!id) return null
143
+ const current = loadDeveloperPreferences(customPath)
144
+ const idx = current.findIndex((p) => p.id === id)
145
+ if (idx === -1) return null
146
+
147
+ const target = current[idx]
148
+ if (typeof updates.title === 'string') target.title = updates.title.trim()
149
+ if (typeof updates.rule === 'string') target.rule = updates.rule.trim()
150
+ if (typeof updates.category === 'string') target.category = updates.category.trim()
151
+ if (typeof updates.enabled === 'boolean') target.enabled = updates.enabled
152
+
153
+ saveDeveloperPreferences(current, customPath)
154
+ return target
155
+ }
156
+
157
+ /**
158
+ * Deletes a developer preference by ID.
159
+ *
160
+ * @param {string} id
161
+ * @param {string} [customPath]
162
+ * @returns {boolean}
163
+ */
164
+ export function deleteDeveloperPreference(id, customPath = null) {
165
+ if (!id) return false
166
+ const current = loadDeveloperPreferences(customPath)
167
+ const filtered = current.filter((p) => p.id !== id)
168
+ if (filtered.length === current.length) return false
169
+
170
+ return saveDeveloperPreferences(filtered, customPath)
171
+ }
172
+
173
+ /**
174
+ * Formats active developer preferences into an injected prompt directive.
175
+ *
176
+ * @param {Array<object>} [preferences] - Array of preferences (loads from disk if omitted)
177
+ * @param {object} [options]
178
+ * @param {boolean} [options.enabled=true]
179
+ * @returns {string|null}
180
+ */
181
+ export function formatPreferencesDirective(preferences = null, options = {}) {
182
+ const { enabled = true, customPath = null } = options
183
+ if (!enabled) return null
184
+
185
+ const list = Array.isArray(preferences) ? preferences : loadDeveloperPreferences(customPath)
186
+ const active = list.filter((p) => p && p.enabled !== false && p.rule)
187
+
188
+ if (active.length === 0) return null
189
+
190
+ const lines = active.map((p) => `- [${p.title}]: ${p.rule}`)
191
+
192
+ return `### Developer Coding Principles & Preferred Conventions
193
+ The developer has established the following coding standards for this project:
194
+ ${lines.join('\n')}
195
+ Evaluate all proposals against these principles and favor implementations adhering to them.`
196
+ }