feat: Enhance Mattermost integration by adding sync commands and context checks for recent messages

This commit is contained in:
2026-04-15 08:48:00 -06:00
parent e389e0ae5e
commit 1885855558
7 changed files with 133 additions and 3 deletions

View File

@@ -12,6 +12,7 @@ Behavior rules:
- Treat `README.md`, `ai/context/`, `ai/state/`, `knowledge/`, and `ai/logs/` as the persistent memory of the project. - 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. - 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. - 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. - 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. - If existing context is stale, correct it directly instead of leaving conflicting versions.

View File

@@ -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

View File

@@ -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` !`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: Then:
- if the command fails, stop there and do not edit any workspace files - if the command fails, stop there and do not edit any workspace files

View File

@@ -35,6 +35,78 @@ async function resolveSyncContent(directory, stdoutText) {
return (generated || "").trim() 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 }) => { export const MattermostInbox = async ({ $, directory, client }) => {
let lastSyncAt = 0 let lastSyncAt = 0
@@ -43,7 +115,7 @@ export const MattermostInbox = async ({ $, directory, client }) => {
await writeFile(statusPath, `${JSON.stringify(data, null, 2)}\n`, "utf8") await writeFile(statusPath, `${JSON.stringify(data, null, 2)}\n`, "utf8")
} }
async function sync(reason) { async function sync(reason, options = {}) {
const command = await resolveSyncCommand(directory) const command = await resolveSyncCommand(directory)
if (!command) return 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 minIntervalMs = Math.max(1, Number.isNaN(intervalMinutes) ? 15 : intervalMinutes) * 60 * 1000
const now = Date.now() const now = Date.now()
if (now - lastSyncAt < minIntervalMs) return if (!options.force && now - lastSyncAt < minIntervalMs) return
lastSyncAt = now lastSyncAt = now
const inboxDir = path.join(directory, "ai/inbox") const inboxDir = path.join(directory, "ai/inbox")
@@ -78,6 +150,7 @@ export const MattermostInbox = async ({ $, directory, client }) => {
await writeStatus(statusPath, { await writeStatus(statusPath, {
syncedAt: nowIso(), syncedAt: nowIso(),
reason, reason,
forced: Boolean(options.force),
changed: previous !== `${output}\n` && previous !== output, changed: previous !== `${output}\n` && previous !== output,
commandConfigured: true, commandConfigured: true,
commandSource: process.env.FIDELITY_MATTERMOST_SYNC_CMD?.trim() ? "env" : "workspace-default", commandSource: process.env.FIDELITY_MATTERMOST_SYNC_CMD?.trim() ? "env" : "workspace-default",
@@ -86,6 +159,7 @@ export const MattermostInbox = async ({ $, directory, client }) => {
await writeStatus(statusPath, { await writeStatus(statusPath, {
syncedAt: nowIso(), syncedAt: nowIso(),
reason, reason,
forced: Boolean(options.force),
changed: false, changed: false,
commandConfigured: true, commandConfigured: true,
commandSource: process.env.FIDELITY_MATTERMOST_SYNC_CMD?.trim() ? "env" : "workspace-default", commandSource: process.env.FIDELITY_MATTERMOST_SYNC_CMD?.trim() ? "env" : "workspace-default",
@@ -96,6 +170,7 @@ export const MattermostInbox = async ({ $, directory, client }) => {
await writeStatus(statusPath, { await writeStatus(statusPath, {
syncedAt: nowIso(), syncedAt: nowIso(),
reason, reason,
forced: Boolean(options.force),
changed: false, changed: false,
commandConfigured: true, commandConfigured: true,
commandSource: process.env.FIDELITY_MATTERMOST_SYNC_CMD?.trim() ? "env" : "workspace-default", 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") { 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 },
)
} }
}, },
} }

View File

@@ -38,6 +38,8 @@ These are also loaded through `opencode.json`.
- Assume the workspace may contain stale context until checked. - 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/`. - 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 `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. - 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. - 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. - Treat sync failures as operational errors, not project context.

View File

@@ -168,6 +168,7 @@ Project commands live under `.opencode/commands/` and are intended to:
- draft standups - draft standups
- draft manager updates - draft manager updates
- draft Copilot prompts for coding work on the Fidelity machine - draft Copilot prompts for coding work on the Fidelity machine
- force-refresh and inspect latest Mattermost messages
- convert rough notes into daily log updates - convert rough notes into daily log updates
This keeps AI output tied to the latest workspace state instead of relying on chat memory alone. This keeps AI output tied to the latest workspace state instead of relying on chat memory alone.

View File

@@ -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 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 - 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 `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 - 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 - 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 - 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