sica-fondt/brain/reasoning/momoa/utils/markdownUtils.ts
Claude 24f816b6a3
Consolidate 22 sibling repos into layered organism structure
Place useful parts of the surrounding repos into sica-fondt by layer, per the
body model (Ada = membrane; brain/endocrine/capabilities/knowledge non-Ada):

- brain/        LLM reasoning + providers (dapr, hermes, MoMoA)
- capabilities/ REPRAG sidecars: hermes tools/skills, dapr tools, parallel
                dispatch, A51 channels, and the OSINT cluster
- knowledge/    LORAG corpus: 754 cyber-skills, agency personas, secure-coding,
                MITRE ATT&CK data
- reference/    defensive threat-reference (C3, shhbruh doc) + AdaYaml parser

License handling: AGPL sources (worldosint, advanced_evolution, mercury,
Reticulum) and GPL DeTTECT are SPEC-only clean-room/port descriptions — no
copyleft code copied. MIT/Apache/data parts copied as working trees.

Safety: shhbruh escape/persistence material and C3 covert-C2 kept as reference
only, not wired into the running organism. See CONSOLIDATION.md.

https://claude.ai/code/session_01UehUqEXXJJCsHoA4voCU5c
2026-06-10 06:53:01 +00:00

258 lines
8.5 KiB
TypeScript

/**
* Copyright 2026 Reto Meier
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { MultiAgentToolContext } from "../momoa_core/types.js";
import { getAssetString } from "../services/promptManager.js";
import { getFileAnalysis } from "./fileAnalysis.js";
import { fileNameLookup } from "./fileNameLookup.js";
/**
* Removes triple backtick fences from a string if they exist at the start and end.
*
* The function checks if the input string, when trimmed, starts with '```' on its
* first line and ends with '```' on its last line. It also removes single '`' from
* the beginning and end of a string on the same line. If these conditions are met,
* it returns the content between the fences, also trimmed of any leading/trailing
* whitespace that might be introduced by the removal. Otherwise, the original
* untrimmed input string is returned.
*
* @param text The input string to process.
* @returns The content within the backtick fences if found, or the original string.
*/
export function removeBacktickFences(text: string): string {
const trimmedText = text.trim();
const lines = trimmedText.split('\n');
// Check if there are at least two lines (for opening and closing fences)
// and if the first line starts with '```' and the last line (when trimmed) is
// exactly '```'.
if (
lines.length >= 2 &&
lines[0].trim().startsWith('```') &&
lines[lines.length - 1].trim() === '```'
) {
// Extract the content between the fences
const contentLines = lines.slice(1, lines.length - 1);
const content = contentLines.join('\n');
return content;
} else if (
lines.length === 1 &&
lines[0].trim().startsWith('`') &&
lines[0].trim().endsWith('`')) {
const content = lines[0].slice(1, -1);
return content;
}
// If conditions are not met, return the original untrimmed string
return text;
}
export function toKebabCase(str: string | undefined): string {
if (!str) return '';
return str
// Find transitions from a lowercase/number to an uppercase letter
.replace(/([a-z0-9])([A-Z])/g, '$1-$2')
// Replace spaces and underscores with a hyphen
.replace(/[\s_]+/g, '-')
// Convert the entire string to lowercase
.toLowerCase();
}
export const aOrAn = (word: string): string => 'aeiou'.includes(word[0]?.toLowerCase()) ? 'an' : 'a';
const numberToWord: Record<number, string> = {
2: 'two',
3: 'three',
4: 'four',
5: 'five',
6: 'six',
7: 'seven',
8: 'eight',
9: 'nine',
};
export function formatExpertList(experts: string[]): string {
const counts = new Map<string, number>();
for (const expert of experts) {
counts.set(expert, (counts.get(expert) || 0) + 1);
}
const sorted = Array.from(counts.entries()).sort(
([, countA], [, countB]) => countB - countA
);
const sortedUsingWords = sorted.map(([item, count]) => {
if (count === 1) {
return `${aOrAn(item)} ${item}`;
}
const word = numberToWord[count] || count.toString();
const countPrefix = word; //word.charAt(0).toUpperCase() + word.slice(1);
return `${countPrefix} ${item}s`;
});
const formatter = new Intl.ListFormat('en', { style: 'long', type: 'conjunction' });
const allExpertNames = formatter.format(sortedUsingWords);
return allExpertNames;
}
/**
* Repairs a potentially truncated JSON array string by trimming it from the end
* and appending a ']' to make it a valid JSON array.
*
* @param jsonString The potentially truncated JSON array string.
* @returns A valid JSON array string (or `undefined` if it cannot be repaired.
*/
export const repairTruncatedJsonArray = (jsonString: string): string | undefined => {
const trimmedString = jsonString.trim();
if (!trimmedString.startsWith('['))
return undefined;
try {
if (Array.isArray(JSON.parse(trimmedString)))
return trimmedString;
} catch {}
const tryAppendingBrace = (stringToTry: string):string | undefined => {
try {
const appendCloseBracket = `${stringToTry.trimEnd()}]`;
const parsedAppendCloseBracket = JSON.parse(appendCloseBracket);
if (Array.isArray(parsedAppendCloseBracket))
return appendCloseBracket;
} catch {
return undefined;
}
}
const appendBraceResult = tryAppendingBrace(trimmedString);
if (appendBraceResult)
return appendBraceResult;
for (let i = trimmedString.length; i > 0; i--) {
let chunk = trimmedString.substring(0, i).trimEnd();
if (chunk.endsWith(','))
chunk = chunk.slice(0, -1);
if (chunk === '[')
return '[]';
if (!chunk)
return undefined;
const tryThisChunk = tryAppendingBrace(chunk);
if (tryThisChunk)
return tryThisChunk;
}
return undefined;
};
/**
* Replaces the content between two marker strings (and the markers
* themselves) with a new string.
*
* @param originalString The string to modify.
* @param prefix The starting marker string.
* @param suffix The ending marker string.
* @param replacementString The string to insert.
* @returns The modified string, or the original string if conditions aren't met.
*/
export const replaceContentBetweenMarkers = (
originalString: string,
prefix: string,
suffix: string,
replacementString: string
): string => {
// 1. Find the starting position of the prefix
const prefixIndex = originalString.indexOf(prefix);
// 2. Find the starting position of the suffix, searching *after* the prefix
const suffixIndex = originalString.indexOf(suffix, prefixIndex);
// 3. Check conditions:
// - Both markers must be found (index !== -1)
if (prefixIndex === -1 || suffixIndex === -1) {
// If conditions aren't met, return the original string untouched
return originalString;
}
// 4. If conditions are met, build the new string:
// - Get the part *before* the prefix
const partBefore = originalString.slice(0, prefixIndex);
// - Get the part *after* the suffix
const partAfter = originalString.slice(suffixIndex + suffix.length);
// 5. Concatenate them with the replacement string in the middle
//
return partBefore + replacementString + partAfter;
};
export async function getFilesAndContent(requestedFiles: {FILENAME: string, DESCRIPTION: string}[], context: MultiAgentToolContext): Promise<string> {
const fileContentPrefix = await getAssetString('file-content-prefix');
const fileContentSuffix = await getAssetString('file-content-suffix');
let result = '--No Files--';
if (requestedFiles && requestedFiles.length > 0 && (context.fileMap || context.binaryFileMap)) {
// Combine text and binary file maps for a comprehensive file lookup.
const allFilesMap = new Map<string, string>([
...context.fileMap,
...Array.from(context.binaryFileMap.keys()).map(key => [key, ''] as [string, string])
]);
const fileResults: string[] = [];
for (const fileObject of requestedFiles) {
if (fileObject && fileObject.FILENAME) {
let filename = fileObject.FILENAME;
let description = fileObject?.DESCRIPTION || undefined;
filename = await fileNameLookup(filename, allFilesMap, context.multiAgentGeminiClient);
let fileBlock = `Filename: ${filename}\n`;
if (description)
fileBlock += `File Description: ${description}`;
const fileSummary = getFileAnalysis(filename);
if (fileSummary)
fileBlock += ` ${fileSummary}`;
if (description || fileSummary)
fileBlock += `\n`;
// Check both maps to retrieve content or identify the file type.
if (context.fileMap.has(filename)) {
const fileContent = context.fileMap.get(filename);
fileBlock += `${fileContentPrefix}\n${fileContent}\n${fileContentSuffix}\n`;
} else if (context.binaryFileMap.has(filename)) {
fileBlock += `[Binary file: Content not viewable]\n`;
} else {
fileBlock += `[File Doesn't Exist Yet]\n`;
}
fileResults.push(fileBlock);
}
}
if (fileResults.length > 0) {
// Join the content for each file and trim any final whitespace.
result = fileResults.join('').trim();
}
}
return result;
}