màj wiki avec retour MES lot-5 AD

This commit is contained in:
Arthur Ria
2026-05-20 09:41:27 +02:00
commit 23eb3f3c84
4106 changed files with 469381 additions and 0 deletions
+66
View File
@@ -0,0 +1,66 @@
import { SECTIONS } from '../config/constants.js';
export const browseToolDefinition = {
name: 'list_wiki_sections',
description:
'Return the complete EasyWMS wiki structure for navigation. Optionally filter by section.',
inputSchema: {
type: 'object',
properties: {
section: {
type: 'string',
enum: ['concepts', 'modules', 'architecture', 'operations', 'limagrain'],
description: 'Filter by section (omit to return all sections)',
},
},
},
};
export function handleListWikiSections(args, { pages }) {
const { section } = args || {};
const sectionsToShow = section ? [section] : SECTIONS;
const lines = [];
for (const sec of sectionsToShow) {
const sectionPages = pages.filter(p => p.path.startsWith(sec + '/'));
if (sectionPages.length === 0) continue;
lines.push(`## ${sec.charAt(0).toUpperCase() + sec.slice(1)} (${sectionPages.length} pages)\n`);
for (const page of sectionPages) {
lines.push(`### ${page.title}`);
lines.push(`- **Path:** ${page.path}`);
lines.push(`- **Type:** ${page.type}`);
if (page.summary) lines.push(`- ${page.summary}`);
lines.push('');
}
}
if (lines.length === 0) {
return {
content: [
{
type: 'text',
text: section
? `No pages found in section "${section}".`
: 'No wiki pages found. Check that the wiki folder is properly configured.',
},
],
};
}
const total = sectionsToShow.reduce(
(sum, sec) => sum + pages.filter(p => p.path.startsWith(sec + '/')).length,
0
);
return {
content: [
{
type: 'text',
text: `# EasyWMS Wiki — ${section ? section : 'All Sections'} (${total} pages)\n\n` + lines.join('\n'),
},
],
};
}
+144
View File
@@ -0,0 +1,144 @@
/**
* Simple Levenshtein distance for fuzzy matching of glossary terms.
*/
function levenshtein(a, b) {
const m = a.length;
const n = b.length;
const dp = Array.from({ length: m + 1 }, (_, i) => [i]);
for (let j = 1; j <= n; j++) dp[0][j] = j;
for (let i = 1; i <= m; i++) {
for (let j = 1; j <= n; j++) {
dp[i][j] =
a[i - 1] === b[j - 1]
? dp[i - 1][j - 1]
: 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
}
}
return dp[m][n];
}
export const glossaryToolDefinition = {
name: 'get_glossary_term',
description:
'Look up a WMS-specific term, abbreviation, or acronym in the EasyWMS glossary (e.g. "LPN", "PDL", "ASN").',
inputSchema: {
type: 'object',
properties: {
term: {
type: 'string',
description: 'Term or abbreviation to look up (case-insensitive)',
},
},
required: ['term'],
},
};
export function handleGetGlossaryTerm(args, { glossary }) {
const { term } = args;
if (!term || term.trim().length === 0) {
return { content: [{ type: 'text', text: 'Error: term parameter is required.' }] };
}
const query = term.trim().toLowerCase();
// 1. Exact match
if (glossary.has(query)) {
const entry = glossary.get(query);
return {
content: [
{
type: 'text',
text: `**${entry.term}**\n\n${entry.definition}\n\n*Source: [Glossary](glossary.md)*`,
},
],
};
}
// 2. Partial match — prefer entries whose term STARTS WITH the query
// (handles "pdl" → "pdl (picking dedicated location)")
const startsWithMatches = [];
const containsMatches = [];
for (const [key, entry] of glossary) {
if (key.startsWith(query + ' ') || key.startsWith(query + '(') || key === query) {
startsWithMatches.push(entry);
} else if (key.includes(query)) {
containsMatches.push(entry);
}
}
// Single strong match
if (startsWithMatches.length === 1) {
const entry = startsWithMatches[0];
return {
content: [
{
type: 'text',
text: `**${entry.term}**\n\n${entry.definition}\n\n*Source: [Glossary](glossary.md)*`,
},
],
};
}
const partialMatches = startsWithMatches.length > 0 ? startsWithMatches : containsMatches;
if (partialMatches.length === 1) {
const entry = partialMatches[0];
return {
content: [
{
type: 'text',
text: `**${entry.term}** *(partial match)*\n\n${entry.definition}\n\n*Source: [Glossary](glossary.md)*`,
},
],
};
}
if (partialMatches.length > 1) {
const list = partialMatches
.slice(0, 5)
.map(e => `- **${e.term}**: ${e.definition.slice(0, 100)}...`)
.join('\n');
return {
content: [
{
type: 'text',
text: `Multiple matches for "${term}":\n\n${list}\n\n*Refine your query for an exact match.*`,
},
],
};
}
// 3. Fuzzy match (Levenshtein)
const candidates = [];
for (const [key, entry] of glossary) {
const dist = levenshtein(query, key);
if (dist <= 3) candidates.push({ entry, dist });
}
candidates.sort((a, b) => a.dist - b.dist);
if (candidates.length > 0) {
const suggestions = candidates
.slice(0, 3)
.map(c => `- **${c.entry.term}**`)
.join('\n');
return {
content: [
{
type: 'text',
text: `Term "${term}" not found in glossary. Did you mean:\n\n${suggestions}\n\n*See [Glossary](glossary.md) for the full list.*`,
},
],
};
}
return {
content: [
{
type: 'text',
text: `Term "${term}" not found in the EasyWMS glossary. See [Glossary](glossary.md) for the full list.`,
},
],
};
}
+49
View File
@@ -0,0 +1,49 @@
import { getPage, findSimilarPages } from '../services/page.js';
export const pageToolDefinition = {
name: 'get_wiki_page',
description:
'Load the full content of a specific EasyWMS wiki page by its relative path (e.g. "concepts/container.md").',
inputSchema: {
type: 'object',
properties: {
path: {
type: 'string',
description: 'Page path relative to wiki/ folder (e.g. "concepts/container.md")',
},
},
required: ['path'],
},
};
export function handleGetWikiPage(args, { wikiPath, pages }) {
const { path: relPath } = args;
if (!relPath || relPath.trim().length === 0) {
return { content: [{ type: 'text', text: 'Error: path parameter is required.' }] };
}
// Security: prevent path traversal
const normalized = relPath.replace(/\\/g, '/').replace(/\.\.\//g, '');
const content = getPage(wikiPath, normalized, pages);
if (!content) {
const similar = findSimilarPages(normalized, pages);
const suggestions =
similar.length > 0
? '\n\nDid you mean one of these?\n' + similar.map(p => `- ${p.path}${p.title}`).join('\n')
: '\n\nUse list_wiki_sections to browse available pages.';
return {
content: [
{
type: 'text',
text: `Page not found: "${relPath}"${suggestions}`,
},
],
};
}
return { content: [{ type: 'text', text: content }] };
}
+104
View File
@@ -0,0 +1,104 @@
export const relatedToolDefinition = {
name: 'get_related_pages',
description:
'Find all EasyWMS wiki pages that reference a given entity, command, or concept by name (e.g. "Container", "putaway", "ASN").',
inputSchema: {
type: 'object',
properties: {
name: {
type: 'string',
description: 'Entity name, command, or concept to find references for',
},
},
required: ['name'],
},
};
export function handleGetRelatedPages(args, { pages }) {
const { name } = args;
if (!name || name.trim().length === 0) {
return { content: [{ type: 'text', text: 'Error: name parameter is required.' }] };
}
const query = name.trim().toLowerCase();
const results = [];
for (const page of pages) {
const mentions = [];
// 1. Check `related` frontmatter
for (const rel of page.related) {
const relName = rel.replace(/^.*\//, '').replace('.md', '').toLowerCase();
if (relName.includes(query) || query.includes(relName)) {
mentions.push({ type: 'related', context: `related: ${rel}` });
}
}
// 2. Check page title
if (page.title.toLowerCase().includes(query)) {
mentions.push({ type: 'title', context: page.title });
}
// 3. Check body — extract sentences containing the query
const sentences = extractMentionContext(page.body, query, 3);
for (const sentence of sentences) {
mentions.push({ type: 'body', context: sentence });
}
if (mentions.length > 0) {
results.push({ page, mentions });
}
}
if (results.length === 0) {
return {
content: [
{
type: 'text',
text: `No pages found referencing "${name}". Try search_wiki for broader results.`,
},
],
};
}
const lines = [`**Pages referencing "${name}"** (${results.length} found)\n`];
for (const { page, mentions } of results) {
lines.push(`### ${page.title}`);
lines.push(`- **Path:** ${page.path}`);
const relatedMentions = mentions.filter(m => m.type === 'related');
const bodyMentions = mentions.filter(m => m.type === 'body');
if (relatedMentions.length > 0) {
lines.push(`- **In related:** ${relatedMentions.map(m => m.context).join(', ')}`);
}
for (const m of bodyMentions.slice(0, 2)) {
lines.push(`- *"${m.context}"*`);
}
lines.push('');
}
return { content: [{ type: 'text', text: lines.join('\n') }] };
}
/**
* Extract up to `limit` sentence snippets from text that contain the query.
*/
function extractMentionContext(body, query, limit) {
const sentences = body.split(/(?<=[.!?])\s+|\n\n/);
const results = [];
for (const sentence of sentences) {
if (results.length >= limit) break;
const clean = sentence.trim().replace(/\n/g, ' ');
if (clean.toLowerCase().includes(query) && clean.length > 10) {
results.push(clean.slice(0, 150));
}
}
return results;
}
+68
View File
@@ -0,0 +1,68 @@
import { searchPages } from '../services/search.js';
export const searchToolDefinition = {
name: 'search_wiki',
description:
'Search the EasyWMS wiki by keyword, tag, or entity name. Returns relevant pages sorted by relevance.',
inputSchema: {
type: 'object',
properties: {
query: {
type: 'string',
description: 'Search terms (keywords, entity name, concept)',
},
tags: {
type: 'array',
items: { type: 'string' },
description: 'Filter by frontmatter tags',
},
type: {
type: 'string',
enum: ['concept', 'module', 'architecture', 'operation', 'limagrain'],
description: 'Filter by page type',
},
max_results: {
type: 'number',
description: 'Maximum number of results to return (default: 5)',
},
},
required: ['query'],
},
};
export function handleSearchWiki(args, { pages, invertedIndex }) {
const { query, tags, type, max_results } = args;
if (!query || query.trim().length === 0) {
return { content: [{ type: 'text', text: 'Error: query parameter is required.' }] };
}
const results = searchPages(pages, invertedIndex, query, {
tags,
type,
maxResults: max_results || 5,
});
if (results.length === 0) {
return {
content: [
{
type: 'text',
text: `No results found for "${query}". Try different keywords or use list_wiki_sections to browse.`,
},
],
};
}
const lines = [`**Search results for "${query}"** (${results.length} found)\n`];
for (const r of results) {
lines.push(`### ${r.title}`);
lines.push(`- **Path:** ${r.path}`);
lines.push(`- **Type:** ${r.type}`);
lines.push(`- **Score:** ${r.score}`);
if (r.summary) lines.push(`- **Summary:** ${r.summary}`);
lines.push('');
}
return { content: [{ type: 'text', text: lines.join('\n') }] };
}