From 1885855558098915977b706b8007e706a5d20adc Mon Sep 17 00:00:00 2001 From: "david.delagneau" Date: Wed, 15 Apr 2026 08:48:00 -0600 Subject: [PATCH] feat: Enhance Mattermost integration by adding sync commands and context checks for recent messages --- .opencode/agents/fidelity.md | 1 + .opencode/commands/latest-message.md | 40 ++++++++++++ .opencode/commands/mattermost-sync.md | 2 + .opencode/plugins/mattermost-inbox.js | 88 ++++++++++++++++++++++++++- AGENTS.md | 2 + README.md | 1 + ai/AGENTS.md | 2 + 7 files changed, 133 insertions(+), 3 deletions(-) create mode 100644 .opencode/commands/latest-message.md diff --git a/.opencode/agents/fidelity.md b/.opencode/agents/fidelity.md index a174f3e..98b6b8c 100644 --- a/.opencode/agents/fidelity.md +++ b/.opencode/agents/fidelity.md @@ -12,6 +12,7 @@ Behavior rules: - Treat `README.md`, `ai/context/`, `ai/state/`, `knowledge/`, and `ai/logs/` as the persistent memory of the project. - Before answering a prompt that depends on current state, verify the latest relevant files instead of relying only on conversation history. +- If the prompt asks for the latest Mattermost message, the last message from Jeff/current manager, or what someone just said, force a Mattermost refresh before answering and do not rely on stale inbox context. - For any meaningful prompt, decide whether the interaction adds, corrects, or sharpens project memory. - When the user provides new durable information, update the right workspace files before or while answering. - If existing context is stale, correct it directly instead of leaving conflicting versions. diff --git a/.opencode/commands/latest-message.md b/.opencode/commands/latest-message.md new file mode 100644 index 0000000..167a747 --- /dev/null +++ b/.opencode/commands/latest-message.md @@ -0,0 +1,40 @@ +--- +description: Force-sync Mattermost and answer from the latest matching message +--- + +Force-refresh Mattermost first, then answer the user's question from the refreshed inbox. + +Use this when the user asks for: + +- the latest or last message +- what Jeff or another person just said +- the latest Mattermost update +- the latest message in `fidelity-preguntas` + +Run sync: + +!`if [ -n "$FIDELITY_MATTERMOST_SYNC_CMD" ]; then bash -lc "$FIDELITY_MATTERMOST_SYNC_CMD"; elif [ -f scripts/mattermost/sync.sh ]; then bash scripts/mattermost/sync.sh; else echo "No Mattermost sync command is configured."; fi` + +Read refreshed Mattermost context: + +!`if [ -s ai/inbox/mattermost-latest.md ]; then cat ai/inbox/mattermost-latest.md; elif [ -s scripts/mattermost/generated/mattermost_context.jsonl ]; then cat scripts/mattermost/generated/mattermost_context.jsonl; else echo "No Mattermost context available after sync."; fi` + +User request: + +$ARGUMENTS + +Instructions: + +- Do not answer from old conversation memory. +- Use only the refreshed Mattermost output above. +- If the user asks for Jeff/current manager, filter messages by `jeff`, `jeff.dewitte`, or the current manager profile when visible. +- If multiple messages match, return the newest matching message first. +- Include timestamp, channel, sender, and concise summary. +- If the message changes project context, update the appropriate workspace memory after answering. +- If sync fails or no refreshed context is available, say that directly and do not infer from stale context. + +Return: + +1. Latest matching message +2. Why it matters +3. Any memory update made diff --git a/.opencode/commands/mattermost-sync.md b/.opencode/commands/mattermost-sync.md index d7bac56..1095d10 100644 --- a/.opencode/commands/mattermost-sync.md +++ b/.opencode/commands/mattermost-sync.md @@ -13,6 +13,8 @@ Run the command and use its output as fresh communication context: !`if [ -n "$FIDELITY_MATTERMOST_SYNC_CMD" ]; then bash -lc "$FIDELITY_MATTERMOST_SYNC_CMD"; elif [ -f scripts/mattermost/sync.sh ]; then bash scripts/mattermost/sync.sh; else echo "No Mattermost sync command is configured."; fi` +Use this command implicitly when the user asks for the latest or last Mattermost message, especially messages from Jeff or the current manager. + Then: - if the command fails, stop there and do not edit any workspace files diff --git a/.opencode/plugins/mattermost-inbox.js b/.opencode/plugins/mattermost-inbox.js index 1288e2c..5d60b66 100644 --- a/.opencode/plugins/mattermost-inbox.js +++ b/.opencode/plugins/mattermost-inbox.js @@ -35,6 +35,78 @@ async function resolveSyncContent(directory, stdoutText) { return (generated || "").trim() } +function extractPromptText(event) { + const candidates = [ + event?.properties?.text, + event?.properties?.message, + event?.properties?.prompt, + event?.text, + event?.message, + event?.prompt, + ] + + for (const candidate of candidates) { + if (typeof candidate === "string" && candidate.trim()) { + return candidate + } + } + + try { + return JSON.stringify(event) + } catch { + return "" + } +} + +function requiresFreshMattermost(promptText) { + const text = promptText + .normalize("NFD") + .replace(/[\u0300-\u036f]/g, "") + .toLowerCase() + + const freshnessTerms = [ + "ultimo", + "ultimos", + "ultima", + "ultimas", + "latest", + "last", + "reciente", + "recent", + "nuevo", + "new", + "actualiza", + "sync", + "sincroniza", + "revisa", + "dijo", + "menciono", + "respondio", + "contesto", + "check", + "look at", + "said", + "mentioned", + "replied", + ] + + const mattermostTerms = [ + "mattermost", + "mensaje", + "mensajes", + "message", + "messages", + "jeff", + "manager", + "fidelity-preguntas", + ] + + return ( + freshnessTerms.some((term) => text.includes(term)) && + mattermostTerms.some((term) => text.includes(term)) + ) +} + export const MattermostInbox = async ({ $, directory, client }) => { let lastSyncAt = 0 @@ -43,7 +115,7 @@ export const MattermostInbox = async ({ $, directory, client }) => { await writeFile(statusPath, `${JSON.stringify(data, null, 2)}\n`, "utf8") } - async function sync(reason) { + async function sync(reason, options = {}) { const command = await resolveSyncCommand(directory) if (!command) return @@ -54,7 +126,7 @@ export const MattermostInbox = async ({ $, directory, client }) => { const minIntervalMs = Math.max(1, Number.isNaN(intervalMinutes) ? 15 : intervalMinutes) * 60 * 1000 const now = Date.now() - if (now - lastSyncAt < minIntervalMs) return + if (!options.force && now - lastSyncAt < minIntervalMs) return lastSyncAt = now const inboxDir = path.join(directory, "ai/inbox") @@ -78,6 +150,7 @@ export const MattermostInbox = async ({ $, directory, client }) => { await writeStatus(statusPath, { syncedAt: nowIso(), reason, + forced: Boolean(options.force), changed: previous !== `${output}\n` && previous !== output, commandConfigured: true, commandSource: process.env.FIDELITY_MATTERMOST_SYNC_CMD?.trim() ? "env" : "workspace-default", @@ -86,6 +159,7 @@ export const MattermostInbox = async ({ $, directory, client }) => { await writeStatus(statusPath, { syncedAt: nowIso(), reason, + forced: Boolean(options.force), changed: false, commandConfigured: true, commandSource: process.env.FIDELITY_MATTERMOST_SYNC_CMD?.trim() ? "env" : "workspace-default", @@ -96,6 +170,7 @@ export const MattermostInbox = async ({ $, directory, client }) => { await writeStatus(statusPath, { syncedAt: nowIso(), reason, + forced: Boolean(options.force), changed: false, commandConfigured: true, commandSource: process.env.FIDELITY_MATTERMOST_SYNC_CMD?.trim() ? "env" : "workspace-default", @@ -123,7 +198,14 @@ export const MattermostInbox = async ({ $, directory, client }) => { } if (event.type === "tui.prompt.append") { - await sync("tui.prompt.append") + const promptText = extractPromptText(event) + const forceFreshMattermost = requiresFreshMattermost(promptText) + await sync( + forceFreshMattermost + ? "tui.prompt.append:fresh-mattermost-request" + : "tui.prompt.append", + { force: forceFreshMattermost }, + ) } }, } diff --git a/AGENTS.md b/AGENTS.md index 6229267..b55bdbe 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -38,6 +38,8 @@ These are also loaded through `opencode.json`. - Assume the workspace may contain stale context until checked. - Before answering questions that depend on current work state, inspect `ai/state/current.md` and the latest relevant log under `ai/logs/`. - If `ai/inbox/mattermost-latest.md` exists, inspect it for fresher communication context before answering standup, status, or manager-message prompts. +- If the user asks for the latest/last/recent Mattermost message, the latest message from Jeff/current manager, or what someone just said, synchronize Mattermost first instead of relying on existing inbox context. +- If automatic refresh is uncertain, use the explicit latest-message flow: run the Mattermost sync command, then answer from the refreshed inbox only. - For any meaningful prompt, decide whether the interaction introduces or corrects project memory. - If a sync command, extraction script, or inbox refresh fails, do not update logs, state, or context files from that failed attempt. - Treat sync failures as operational errors, not project context. diff --git a/README.md b/README.md index ca3b66e..9802029 100644 --- a/README.md +++ b/README.md @@ -168,6 +168,7 @@ Project commands live under `.opencode/commands/` and are intended to: - draft standups - draft manager updates - draft Copilot prompts for coding work on the Fidelity machine +- force-refresh and inspect latest Mattermost messages - convert rough notes into daily log updates This keeps AI output tied to the latest workspace state instead of relying on chat memory alone. diff --git a/ai/AGENTS.md b/ai/AGENTS.md index c5faf5f..2dd2ed9 100644 --- a/ai/AGENTS.md +++ b/ai/AGENTS.md @@ -66,6 +66,8 @@ When drafting messages for a manager or stakeholder: - Before answering Swift, SwiftUI, iOS architecture, testing, or debugging questions, check `ai/context/ios/` and use the project-local iOS skills when available - Before generating a prompt for another AI or GitHub Copilot, check `ai/context/process/ai-to-ai-prompting.md` and make the prompt self-contained - If `ai/inbox/mattermost-latest.md` exists, check it before answering prompts about current status, standups, or supervisor communication +- If the user asks for the latest/last/recent Mattermost message, the latest message from Jeff/current manager, or what someone just said, synchronize Mattermost first and then answer from the refreshed inbox +- If automatic refresh is uncertain, use the explicit latest-message flow: run the Mattermost sync command, then answer from refreshed output only - Treat all meaningful user prompts as potential memory updates, not only explicit sync commands - If a Mattermost sync or other context-ingestion step fails, do not update `ai/logs/`, `ai/state/`, or stable context files based on that failure - Do not record missing dependencies, failed sync attempts, or missing inbox files as project facts unless the user explicitly asks to track the operational issue