Compare commits

...

3 Commits

26 changed files with 338 additions and 60 deletions

View File

@@ -26,11 +26,12 @@ Read:
Fresh communication evidence:
!`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 communication evidence available."; fi`
!`python3 scripts/mattermost-proxy/read-context.py --mode latest`
Instructions:
- if the sync command failed, stop and do not edit workspace memory
- prefer local proxy mirror evidence when present; legacy sync output is fallback evidence
- treat connector output as evidence, not automatically as project truth
- promote only explicit, project-relevant, high-confidence facts
- default destination is `project-knowledge/06-daily/$(date +%F).md`

View File

@@ -59,7 +59,7 @@ PY`
Latest Mattermost context, if available:
!`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."; fi`
!`python3 scripts/mattermost-proxy/read-context.py --mode latest`
Respond with:

View File

@@ -2,7 +2,7 @@
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.
Refresh/read Mattermost first, then answer the user's question from the freshest evidence. Prefer the local proxy mirror when it is present; legacy sync output is fallback evidence.
Use this when the user asks for:
@@ -11,46 +11,13 @@ Use this when the user asks for:
- the latest Mattermost update
- the latest message in `fidelity-preguntas`
Run sync:
Run sync/fallback refresh:
!`start=$(date +%s); if [ -n "$AIW_MATTERMOST_SYNC_CMD" ]; then bash -lc "$AIW_MATTERMOST_SYNC_CMD"; elif [ -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; status=$?; end=$(date +%s); echo "__MATTERMOST_SYNC_SECONDS__=$((end - start))"; exit "$status"`
Read a focused slice of refreshed Mattermost context:
Read a focused slice of refreshed Mattermost context, preferring the proxy mirror:
!`python3 - <<'PY'
import json
from pathlib import Path
paths = [
Path("ai/inbox/mattermost-latest.md"),
Path("scripts/mattermost/generated/mattermost_context.jsonl"),
]
source = next((path for path in paths if path.is_file() and path.stat().st_size > 0), None)
if not source:
print("No Mattermost context available after sync.")
raise SystemExit(0)
records = []
for line in source.read_text().splitlines():
line = line.strip()
if not line:
continue
try:
records.append(json.loads(line))
except json.JSONDecodeError:
continue
manager_names = {"jeff", "jeff.dewitte"}
manager_records = [
record for record in records
if str(record.get("username", "")).lower() in manager_names
]
focused = manager_records[-10:] if manager_records else records[-15:]
for record in focused:
print(json.dumps(record, ensure_ascii=False))
PY`
!`python3 scripts/mattermost-proxy/read-context.py --mode focused`
User request:

View File

@@ -22,7 +22,7 @@ Today's log, if present:
Latest Mattermost context, if available:
!`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."; fi`
!`python3 scripts/mattermost-proxy/read-context.py --mode latest`
Detailed active work item files, if available:

View File

@@ -2,7 +2,7 @@
description: Sync Mattermost context and automatically promote high-confidence project memory
---
// turbo-all
Use the configured Mattermost sync command to fetch fresh communication context and maintain workspace memory automatically.
Use the configured Mattermost sync command and/or local proxy mirror evidence to fetch fresh communication context and maintain workspace memory automatically.
Preferred command sources:
@@ -14,12 +14,17 @@ Run the command and use its output as fresh communication context:
!`if [ -n "$AIW_MATTERMOST_SYNC_CMD" ]; then bash -lc "$AIW_MATTERMOST_SYNC_CMD"; elif [ -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`
Fresh Mattermost evidence, preferring the proxy mirror:
!`python3 scripts/mattermost-proxy/read-context.py --mode latest`
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
- use `ai/inbox/mattermost-latest.md` if it exists and is non-empty
- prefer the local proxy mirror via `scripts/mattermost-proxy/read-context.py --mode latest` when it has evidence
- otherwise use `ai/inbox/mattermost-latest.md` if it exists and is non-empty
- otherwise use `scripts/mattermost/generated/mattermost_context.jsonl` if it exists and is non-empty
- apply the memory promotion rules from `agent-memory/memory/promotion-rules.md`
- treat Mattermost output as live communication evidence; the agent decides what becomes promoted memory

View File

@@ -22,11 +22,11 @@ Today's log, if present:
Current Mattermost inbox, if present:
!`if [ -f ai/inbox/mattermost-latest.md ]; then cat ai/inbox/mattermost-latest.md; else echo "No Mattermost inbox file is available."; fi`
!`python3 scripts/mattermost-proxy/read-context.py --mode latest`
Generated Mattermost context, if present:
!`if [ -s scripts/mattermost/generated/mattermost_context.jsonl ]; then cat scripts/mattermost/generated/mattermost_context.jsonl; else echo "No generated Mattermost context is available."; fi`
!`if [ -s scripts/mattermost/generated/mattermost_context.jsonl ]; then cat scripts/mattermost/generated/mattermost_context.jsonl; else echo "No legacy generated Mattermost context is available."; fi`
User direction or facts to promote:

View File

@@ -79,7 +79,7 @@ Today's log, if present:
Latest refreshed Mattermost context, if present:
!`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 refreshed Mattermost context available."; fi`
!`python3 scripts/mattermost-proxy/read-context.py --mode standup --today $(date +%F)`
Detailed active work item files, if available:

View File

@@ -36,7 +36,7 @@ Today's log, if present:
Latest Mattermost context, if available:
!`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."; fi`
!`python3 scripts/mattermost-proxy/read-context.py --mode latest`
Detailed active work item files, if available:

View File

@@ -66,7 +66,7 @@ PY`
Latest communication inbox, if available:
!`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 live communication context available."; fi`
!`python3 scripts/mattermost-proxy/read-context.py --mode latest`
Respond with:

View File

@@ -17,6 +17,7 @@ Behavior rules:
- Treat `scripts/memory/` as the project-agnostic access layer for note creation, project-knowledge search, Base queries, and health checks.
- Treat `scripts/obsidian/` as the current Obsidian adapter. Do not couple durable memory rules to Obsidian-specific behavior.
- Treat `ai/inbox/` and generated connector files as raw evidence only, not promoted memory.
- For Mattermost context, prefer the local proxy mirror in `ai/inbox/mattermost-mirror/` when present. Use `scripts/mattermost-proxy/read-context.py` or the mirror views (`latest.*`, `by-date/`, `channels/`, `threads/`) before falling back to legacy `ai/inbox/mattermost-latest.md` or `scripts/mattermost/generated/` artifacts.
- Keep Obsidian Bases clean: do not let templates in `project-knowledge/09-templates/` appear as real daily notes, work items, people, decisions, systems, or workstreams.
- Role mapping notes such as `project-knowledge/04-people/manager.md` are `type: role-map`; actual people profiles are `type: person`.
- When editing canonical project notes, update useful metadata at the same time: `updated`, `systems`, `workstreams`, `people`, `related`, `focus`, `work-items`, and `blockers` when applicable.
@@ -25,7 +26,7 @@ Behavior rules:
- Work item notes should preserve Jira ID/title and explicit relationships so standups, Bases, and graph navigation stay useful.
- Daily notes should include `focus`, `work-items`, and `blockers` when those values are clear.
- 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.
- If the prompt asks for the latest Mattermost message, the last message from Jeff/current manager, or what someone just said, refresh or read the freshest Mattermost evidence before answering; the proxy mirror is the primary source when it is present, and legacy sync artifacts are fallback evidence.
- Treat latest-message prompts as read-first: answer from refreshed evidence and report memory update candidates instead of editing canonical memory by default.
- For learning-style questions, answer from known context and verified facts only; explicitly label unknowns, assumptions, and inferences.
- For learning sessions, prioritize durable architecture, process, ownership, debugging strategy, release mechanics, domain concepts, and decision rules over transient ticket status.

View File

@@ -17,6 +17,7 @@ Behavior rules:
- Treat `scripts/memory/` as the stable memory access layer.
- Treat tool-specific integrations such as Obsidian as replaceable adapters.
- Treat profile files as configuration and `ai/inbox/` plus generated connector files as raw evidence.
- For live communication context, prefer project-local mirror evidence under `ai/inbox/*-mirror/` through its reader script when available, then fall back to legacy inbox/generated connector artifacts.
- Keep Obsidian Bases clean by excluding templates and typing role maps separately from people.
- When updating canonical project notes, maintain relationship metadata and `updated` fields so project knowledge remains useful to both humans and agents.
- Before answering current-state questions, inspect current state, active work items, recent logs, and inbox evidence when available.

View File

@@ -43,9 +43,9 @@ Do not preemptively load broad context sets, all work-item files, or all process
- Maintain useful project-note properties when editing canonical notes, especially work-item relationships (`systems`, `workstreams`, `people`, `related`) and daily note fields (`focus`, `work-items`, `blockers`).
- Before answering questions that depend on current work state, inspect `project-knowledge/01-current/current-work.md` and the latest relevant daily note under `project-knowledge/06-daily/`.
- Prefer lazy loading over eager loading. Pull in only the smallest relevant files for the active task.
- 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 Mattermost evidence, prefer the local proxy mirror under `ai/inbox/mattermost-mirror/` when present. Use `scripts/mattermost-proxy/read-context.py` or mirror views (`latest.*`, `by-date/`, `channels/`, `threads/`) before falling back to legacy `ai/inbox/mattermost-latest.md` or `scripts/mattermost/generated/` artifacts.
- If the user asks for the latest/last/recent Mattermost message, the latest message from Jeff/current manager, or what someone just said, use the explicit latest-message flow and answer from the freshest refreshed evidence; the proxy mirror is primary when available.
- If automatic refresh is uncertain and the proxy mirror is not available, run the Mattermost sync command, then answer from the refreshed inbox only.
- Treat latest-message flows as read-first. Report memory update candidates, but do not edit canonical memory from that command unless the user explicitly asks to promote the fact.
- For learning-style questions, answer from known context and verified facts only; label unknowns, assumptions, and inferences instead of inventing missing details.
- For learning sessions, prioritize durable architecture, process, ownership, debugging strategy, release mechanics, domain concepts, and decision rules over transient ticket status.

View File

@@ -1,7 +1,7 @@
---
type: agent-integration
status: active
updated: 2026-05-08
updated: 2026-05-19
tags:
- communication
- evidence
@@ -17,12 +17,17 @@ Communication connectors extract evidence. The agent decides what to promote.
Mattermost is the current live communication connector.
- Fresh output goes to `ai/inbox/mattermost-latest.md`.
- Generated extraction artifacts stay under `scripts/mattermost/generated/`.
- Primary local evidence is the Mattermost proxy mirror under `ai/inbox/mattermost-mirror/` when present.
- Prefer `ai/inbox/mattermost-mirror/latest.md` / `latest.jsonl` for latest-message context, `by-date/YYYY/MM/YYYY-MM-DD.jsonl` for daily/standup context, `channels/<channel>/YYYY/MM/YYYY-MM-DD.jsonl` for channel-specific context, and `threads/<root-or-post-id>.jsonl` for thread-specific context.
- Use `scripts/mattermost-proxy/read-context.py` from commands/workflows instead of reading ad hoc files; it prefers the proxy mirror and falls back to legacy sync artifacts.
- Legacy fresh output may still go to `ai/inbox/mattermost-latest.md`.
- Legacy generated extraction artifacts stay under `scripts/mattermost/generated/`.
- Failed syncs must not update project knowledge.
- Latest-message requests must refresh Mattermost before answering.
- Latest-message requests are read-first. The agent may identify a memory update candidate, but should not edit `project-knowledge/` from the latest-message command unless the user explicitly asks to promote the fact.
- Standup generation is a separate required-refresh flow: it must fetch Mattermost before drafting, even though general prompts should not sync automatically.
- Standup reads should use the focused reader mode, `scripts/mattermost-proxy/read-context.py --mode standup --today YYYY-MM-DD`, which reads date-bucketed previous-workday/today records and should use the active profile's configured `AIW_MATTERMOST_CONTEXT_CHANNELS` when available. Avoid loading broad mirror `latest.md` into standup prompts because it may include stale or unrelated channels and waste tokens. Keep project-specific channel names out of reusable connector code.
- If the proxy mirror is running, treat it as fresher than legacy `mattermost-latest.md` / generated JSONL. Do not ignore mirror evidence merely because a legacy sync command also ran.
- Do not refresh Mattermost just because a prompt mentions a manager or stakeholder.
- Treat document review, message polishing, translation, and "does this align with Jeff's expectations?" prompts as normal drafting tasks unless the user explicitly asks for the latest message or fresh Mattermost evidence.
- The OpenCode plugin syncs automatically only for explicit latest-message requests by default.

View File

@@ -4,8 +4,9 @@ This directory stores raw or semi-processed communication captured from Mattermo
Recommended usage:
- `mattermost-latest.md` contains the most recent capture
- `mattermost-mirror/` is the preferred local Mattermost evidence source when present. It stores `latest.*`, `by-date/`, `channels/`, `threads/`, and `refs/` views from the local proxy mirror.
- `mattermost-latest.md` contains the legacy most recent sync capture
- timestamped snapshots can be stored here if needed
- durable facts should be promoted into `vault/06-daily/`, `vault/01-current/`, `vault/02-work-items/`, `vault/03-context/`, `vault/04-people/`, or `vault/05-decisions/`
- durable facts should be promoted into `project-knowledge/06-daily/`, `project-knowledge/01-current/`, `project-knowledge/02-work-items/`, `project-knowledge/03-context/`, `project-knowledge/04-people/`, or `project-knowledge/05-decisions/`
This directory is intentionally treated as an inbox, not as the final source of truth.

View File

@@ -20,9 +20,11 @@ It keeps Fidelity-specific context, integrations, commands, and skills separate
## Communication Sources
- Live communication: Mattermost
- Preferred local Mattermost evidence source: proxy mirror under `ai/inbox/mattermost-mirror/` when present; legacy `ai/inbox/mattermost-latest.md` and `scripts/mattermost/generated/` are fallback evidence.
- Historical archive: Slack export
- Preferred channel naming: readable channel names instead of raw IDs
- Current high-signal channel: `fidelity-preguntas`
- Focused Mattermost context for standups/latest project reads should come from configured profile/environment channels, not hardcoded connector defaults. For this profile, the useful context-channel set is currently `fidelity-preguntas`, `fidelity-standup`, `fidelity-code-review`, `fidelity-interface-meetings-on-calendar-outlook-team-etc`, and `dm-david--jeff`; keep that list in local `.env` as `AIW_MATTERMOST_CONTEXT_CHANNELS` or an equivalent profile setup when using the reusable Mattermost reader.
Compatibility environment variables:
@@ -38,6 +40,7 @@ Generic variables should be preferred for new setup:
- `AIW_SLACK_EXPORT_PATH`
- `AIW_CHANNEL_PREFIX=fidelity`
- `AIW_PROJECT_PROFILE=fidelity`
- `AIW_MATTERMOST_CONTEXT_CHANNELS=fidelity-preguntas,fidelity-standup,fidelity-code-review,fidelity-interface-meetings-on-calendar-outlook-team-etc,dm-david--jeff`
---

View File

@@ -32,6 +32,8 @@ tags:
- SampleApp should support explicit validation of both UIKit-host and SwiftUI-host scenarios. XFlowSDK should remain self-aware of which dismissal mechanism to use based on the active integration path, so SampleApp can exercise both paths without relying on stale singleton/default host-mode state.
- May 14 SampleApp iteration: SampleApp was updated to choose between UIKit-host (`initialViewController(...)`) and SwiftUI-host (`makeInitialFlowView(...)`) paths from the existing `Use SwiftUI` setting / feature-toggle path. XFlowSDK entrypoints now prepare the matching dismissal mechanism, and the SampleApp SwiftUI-host rendering was refined to use a `ViewBuilder`/`flowContent` approach rather than storing an `AnyView` in state. Next validation is SampleApp back-to-back host-mode switching plus Fid4 AccountLink revalidation in both host modes.
- Jeff recommended expanding validation beyond AccountLink before PR review: test as many current Fid4 XFlow entry and dismissal points as possible in both default SwiftUI-host and forced UIKit-host modes, with particular attention to AO flows. Use the existing Confluence entry-point list as a starting point, but verify it against current code and temporary logs because some entries may be stale.
- May 20 validation: sessions 004 and 005 show SwiftUI-host load, dismissal, and delegate cleanup coverage for HybridBrokerageAccountOpening and accountlink, but HybridYouthAccountOpening has load/start coverage only and needs complete dismissal coverage.
- Remaining validation gaps on the combined PDIAP-12284 / PDIAP-15836 branch include AO deep-link coverage, Fid4 surface attribution for launch wrappers, common-launch flows, HybridBloomAccountOpening, and explicit UIKit-host mode validation. Plans are to open a draft PR today while validation continues.
- Keep the separate `HybridBrokerageAccountOpening` / `JointIdentityCheck` scenario out of `PDIAP-15765` scope unless later evidence proves it belongs there
- Include feature-flag planning for the broader UIKit-removal spike, including dismissal sequencing changes that affect consumers
- Thoroughly verify current `ApexBridgingAddressComponent` / rule-loading usage before describing it as inactive or dead code

View File

@@ -28,7 +28,7 @@ Update the per-ticket files first when scope, status, sequencing, or communicati
- `PDIAP-12284` - Remove UIKit wrapping from XFlow
Detail: `project-knowledge/02-work-items/pdiap-12284.md`
Current note: moved to In Progress on May 12 and should be handled with `PDIAP-15836` in the same branch. Quy confirmed both stories can remain In Progress together, so no immediate Jira restructuring is required. Current implementation direction is to avoid Fid4 per-flow host-mode mapping and instead evaluate XFlowViewMaker-owned global host-mode resolution, with SwiftUI as default and `UIHostingController` only as an explicit temporary fallback. May 12 implementation pass resolved earlier shape concerns with neutral enum names, `iOS-XflowUIKitHostEnabled`, SwiftUI default semantics, and `AnyView` removed from builder internals. Fid4 now compiles after manual dependency/build handling. May 13 AccountLink runtime validation looks good for both SwiftUI-default and forced UIKit-host paths. May 14 SampleApp work added explicit UIKit-host vs SwiftUI-host validation paths and refined SwiftUI-host rendering to avoid local `AnyView` state, using a `ViewBuilder`/`flowContent` path instead. Next validation is SampleApp back-to-back mode switching and broader Fid4 entry/dismissal validation in both host modes, especially AO flows, per Jeff's review guidance.
Current note: moved to In Progress on May 12 and should be handled with `PDIAP-15836` in the same branch. Quy confirmed both stories can remain In Progress together, so no immediate Jira restructuring is required. Current implementation direction is to avoid Fid4 per-flow host-mode mapping and instead evaluate XFlowViewMaker-owned global host-mode resolution, with SwiftUI as default and `UIHostingController` only as an explicit temporary fallback. May 12 implementation pass resolved earlier shape concerns with neutral enum names, `iOS-XflowUIKitHostEnabled`, SwiftUI default semantics, and `AnyView` removed from builder internals. Fid4 now compiles after manual dependency/build handling. May 13 AccountLink runtime validation looks good for both SwiftUI-default and forced UIKit-host paths. May 14 SampleApp work added explicit UIKit-host vs SwiftUI-host validation paths. May 20 status: sessions 004 and 005 validated SwiftUI-host load, dismissal, and delegate cleanup for HybridBrokerageAccountOpening and accountlink, but HybridYouthAccountOpening needs complete dismissal coverage. Remaining gaps include AO deep-link coverage, launch wrappers, common-launch, HybridBloomAccountOpening, and explicit UIKit-host mode. Plan to open a draft PR today while validation continues.
## Backlog / Future Reference

View File

@@ -36,6 +36,7 @@ tags:
- May 14 SampleApp iteration updated the sample to exercise both host scenarios explicitly: UIKit-host through `initialViewController(...)` and SwiftUI-host through `makeInitialFlowView(...)`, selected by the existing `Use SwiftUI` / feature-toggle path. XFlowSDK entrypoints should prepare their own matching dismissal mechanism so `endActivity` cannot route to the SwiftUI subject unless the SwiftUI host is actually active.
- The SampleApp SwiftUI-host rendering was refined to avoid storing `AnyView` in state; SwiftUI-host content now lives behind a `ViewBuilder` / `flowContent` rendering path. This keeps local SampleApp code aligned with the branch goal of avoiding unnecessary `AnyView`, while preserving type erasure only at true public boundaries such as the existing XFlowViewMaker boundary if still required.
- Jeff recommended expanding validation before review: test as many current Fid4 XFlow entry and dismissal points as possible in both default SwiftUI-host and forced UIKit-host modes, with particular attention to AO flows. Use the prior Confluence entry-point list as a starting point, but verify it against current code and temporary runtime logs before treating it as complete.
- May 20 status: validation sessions 004 and 005 confirmed SwiftUI-host load, dismissal, and delegate cleanup for HybridBrokerageAccountOpening and accountlink, but HybridYouthAccountOpening needs complete dismissal coverage. Gaps remain in AO deep-link coverage, launch wrappers, common-launch, HybridBloomAccountOpening, and explicit UIKit-host validation. Plan to open a draft PR today while continuing validation.
---

View File

@@ -27,6 +27,7 @@ tags:
- May 13 AccountLink runtime validation looks good in both SwiftUI-default and forced UIKit-host modes. Both paths preserved the intended dismissal sequencing, with delegate callbacks and session cleanup after confirmed dismissal.
- May 14 SampleApp work refined dismissal validation coverage: SampleApp can exercise both UIKit-host and SwiftUI-host paths, while XFlowSDK entrypoints prepare the matching dismissal mechanism for the active integration path. This supports validating that UIKit dismiss completion and SwiftUI `XFlowDismissalHost` lifecycle sequencing both converge on the canonical delegate/session teardown path.
- Jeff recommended broad Fid4 validation before PR review: test as many current XFlow entry and dismissal points as possible in both host modes, especially AO flows, and use temporary logs to confirm the active entry path, host mode, dismissal mechanism, delegate callbacks, and session cleanup.
- May 20 status: validation sessions 004 and 005 confirmed SwiftUI-host load, dismissal, and delegate cleanup for HybridBrokerageAccountOpening and accountlink, but HybridYouthAccountOpening needs complete dismissal coverage. Gaps remain in AO deep-link coverage, launch wrappers, common-launch, HybridBloomAccountOpening, and explicit UIKit-host validation. Plan to open a draft PR today while continuing validation.
- Quy confirmed on May 13 that this story and `PDIAP-12284` can both remain In Progress together, so no immediate Jira restructuring is required.
- Sequenced after `PDIAP-15838` source work, but merge/release is delayed until after REST-transition consumer validation
- Sized at `8` points

View File

@@ -0,0 +1,26 @@
---
type: daily
project: fidelity
date: 2026-05-20
status: active
focus: [pdiap-12284, pdiap-15836, duplicate-ao-report]
work-items: [PDIAP-12284, PDIAP-15836]
blockers: [duplicate-ao-report-waiting-on-adam-discourse-details]
tags:
- daily
- fidelity
updated: 2026-05-20
---
# 2026-05-20
## Work Planned / Current State
- Continue `PDIAP-12284` / `PDIAP-15836` validation across current Fid4 XFlow entry points and dismissal paths.
- Current validation evidence from sessions `004` and `005` shows SwiftUI-host load/dismiss/delegate-cleanup coverage for `HybridBrokerageAccountOpening` and `accountlink`; `HybridYouthAccountOpening` has load/start coverage only and still needs complete dismissal coverage.
- Remaining validation gaps include AO deep-link coverage, Fid4 surface attribution for launch wrappers, common-launch flows, `HybridBloomAccountOpening`, and explicit UIKit-host mode validation.
- Plan to open a draft PR today for the combined `PDIAP-12284` / `PDIAP-15836` branch while validation continues.
## Work Done
- Previous-day project evidence confirms the duplicate account-opening investigation is still waiting on Adam's Discourse details; no new `xflog` Discourse post had been seen yet.

View File

@@ -8,8 +8,8 @@ Generate a standup update for the active project profile.
## Required refresh
- At the start of the day, fetch Mattermost before drafting.
- Fetch both latest available messages and previous-workday activity when the connector supports both modes.
- At the start of the day, fetch or read refreshed Mattermost evidence before drafting. Prefer the local proxy mirror through `scripts/mattermost-proxy/read-context.py` when it exists; legacy sync output is fallback evidence.
- Fetch focused standup evidence with `python3 scripts/mattermost-proxy/read-context.py --mode standup --today YYYY-MM-DD`; this reads previous-workday and today records from date-bucketed mirror files and should filter through the active profile's configured `AIW_MATTERMOST_CONTEXT_CHANNELS` when available. Do not read broad `latest.md` for standups unless the focused date-bucketed view is unavailable and you explicitly label the fallback as broad/noisy.
- If Mattermost refresh fails, say so internally and use only saved workspace memory with clear caution; do not invent fresher context.
- Do not skip communication refresh for standup just to reduce latency, because stale standups cost more time to correct later.

View File

@@ -59,7 +59,7 @@ Expected behavior:
- avoid interactive prompts
- return a non-zero exit code on failure
OpenCode can then use that output to refresh `ai/inbox/mattermost-latest.md` proactively.
OpenCode can then use that output to refresh `ai/inbox/mattermost-latest.md` proactively. When the local Mattermost proxy mirror is running, commands should prefer `ai/inbox/mattermost-mirror/` through `scripts/mattermost-proxy/read-context.py` and use legacy sync output as fallback evidence.
Historical Slack exports can also be imported through:

View File

@@ -19,6 +19,13 @@ MATTERMOST_MIRROR_LATEST_LIMIT=200
# Optional channel allowlist. Comma-separated channel IDs. Empty means all captured channels.
MATTERMOST_MIRROR_CHANNEL_IDS=
# Optional AI context channel filter for reader commands such as:
# python3 scripts/mattermost-proxy/read-context.py --mode standup --today YYYY-MM-DD
# Use readable channel names or channel IDs. Keep project-specific values in your local .env
# or active profile setup, not in reusable scripts.
# Example: project-main,project-standup,dm-you--manager
AIW_MATTERMOST_CONTEXT_CHANNELS=
# Write compact raw REST/WebSocket evidence in addition to normalized messages.
# Keep disabled by default to avoid large files.
MATTERMOST_MIRROR_WRITE_RAW=0

View File

@@ -69,6 +69,30 @@ and `threads/...` when a single discussion thread is the relevant evidence.
This mirrors Slack's export pattern of one folder per conversation with one file
per date, while adding Mattermost-specific thread views.
For standup generation, prefer the focused reader instead of loading broad
`latest.md` directly:
```bash
python3 scripts/mattermost-proxy/read-context.py --mode standup --today YYYY-MM-DD
```
`standup` mode reads only date-bucketed records for the previous workday and
today. To avoid spending tokens on unrelated channels or stale global `latest.md`
content, configure project-specific context channels in the connector-local
`.env` or pass them explicitly. Keep those channel values out of reusable scripts.
```bash
AIW_MATTERMOST_CONTEXT_CHANNELS="project-main,project-standup,dm-you--manager" \
python3 scripts/mattermost-proxy/read-context.py --mode standup --today YYYY-MM-DD
python3 scripts/mattermost-proxy/read-context.py --mode standup --today YYYY-MM-DD \
--channels "project-main,project-standup,dm-you--manager"
```
If no context channel filter is configured, `standup` mode still avoids
`latest.md` and reads date-bucketed records only, but it will include all mirrored
channels for those dates.
Direct-message channels are labeled as `dm-<user-a>--<user-b>` when the mirror
has seen enough user metadata to resolve the Mattermost channel ID. Group DMs
use `group-...`. If a DM was first captured before the relevant user metadata
@@ -119,6 +143,7 @@ Each line in the normalized JSONL contains:
- `MATTERMOST_MIRROR_CHANNEL_IDS`: optional comma-separated channel ID allowlist.
- `MATTERMOST_MIRROR_WRITE_RAW`: set to `1` to save compact raw REST/WebSocket evidence.
- `MATTERMOST_APP_PATH`: Mattermost Desktop `.app` bundle path.
- `AIW_MATTERMOST_PROJECT_CHANNELS`: optional comma-separated channel names or IDs for focused standup reads.
## Troubleshooting

View File

@@ -0,0 +1,227 @@
#!/usr/bin/env python3
"""Read local Mattermost mirror evidence for agent prompts.
The proxy mirror under ai/inbox/mattermost-mirror is the preferred Mattermost
evidence source when present. This reader gives commands a stable way to load a
small focused view and fall back to the older sync artifacts when the mirror is
not available.
"""
from __future__ import annotations
import argparse
import json
import os
import shlex
from datetime import date, datetime, timedelta
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
MIRROR_DIR = ROOT / "ai" / "inbox" / "mattermost-mirror"
LEGACY_LATEST = ROOT / "ai" / "inbox" / "mattermost-latest.md"
LEGACY_GENERATED = ROOT / "scripts" / "mattermost" / "generated" / "mattermost_context.jsonl"
LOCAL_ENV = Path(__file__).resolve().parent / ".env"
def load_local_env(path: Path = LOCAL_ENV) -> None:
"""Load simple KEY=VALUE pairs from the connector-local .env.
Existing process environment values win. This keeps the reusable reader
project-agnostic while allowing each workspace/profile to provide its own
channel filters without hardcoding them in Python.
"""
if not path.is_file():
return
for raw_line in path.read_text(encoding="utf-8").splitlines():
line = raw_line.strip()
if not line or line.startswith("#") or "=" not in line:
continue
if line.startswith("export "):
line = line[len("export ") :].strip()
key, value = line.split("=", 1)
key = key.strip()
value = value.strip()
if not key or key in os.environ:
continue
try:
parsed = shlex.split(value, comments=False, posix=True)
value = parsed[0] if parsed else ""
except ValueError:
value = value.strip('"\'')
os.environ[key] = value
def previous_workday(today: date) -> date:
day = today - timedelta(days=1)
while day.weekday() >= 5:
day -= timedelta(days=1)
return day
def daily_by_date_path(day: date) -> Path:
return MIRROR_DIR / "by-date" / f"{day:%Y}" / f"{day:%m}" / f"{day:%Y-%m-%d}.jsonl"
def print_file(path: Path) -> bool:
if path.is_file() and path.stat().st_size > 0:
print(path.read_text(encoding="utf-8"))
return True
return False
def read_jsonl(path: Path) -> list[dict]:
records = []
if not path.is_file():
return records
for line in path.read_text(encoding="utf-8").splitlines():
line = line.strip()
if not line:
continue
try:
records.append(json.loads(line))
except json.JSONDecodeError:
continue
return records
def parse_channels(raw: str | None) -> set[str]:
if not raw:
env_raw = os.getenv("AIW_MATTERMOST_CONTEXT_CHANNELS", "") or os.getenv(
"AIW_MATTERMOST_PROJECT_CHANNELS", ""
)
raw = env_raw
if not raw:
return set()
return {item.strip() for item in raw.split(",") if item.strip()}
def filter_channels(records: list[dict], channels: set[str] | None) -> list[dict]:
if not channels:
return records
lowered = {channel.lower() for channel in channels}
return [
record
for record in records
if str(record.get("channel_name", "")).lower() in lowered
or str(record.get("channel_id", "")).lower() in lowered
]
def trim_records(records: list[dict], limit: int | None) -> list[dict]:
if limit is None or limit <= 0 or len(records) <= limit:
return records
return records[-limit:]
def print_jsonl(records: list[dict]) -> bool:
if not records:
return False
for record in records:
print(json.dumps(record, ensure_ascii=False, sort_keys=True))
return True
def fallback() -> None:
if print_file(LEGACY_LATEST):
return
if print_file(LEGACY_GENERATED):
return
print("No Mattermost context available.")
def mode_latest() -> None:
print("## Mattermost mirror latest context")
if print_file(MIRROR_DIR / "latest.md"):
return
records = read_jsonl(MIRROR_DIR / "latest.jsonl")
if print_jsonl(records):
return
print("No proxy mirror latest context available; falling back to legacy sync artifacts.")
fallback()
def print_records_section(title: str, records: list[dict], limit: int | None = None) -> bool:
print(title)
records = trim_records(records, limit)
if print_jsonl(records):
return True
print("No matching Mattermost mirror records.")
return False
def mode_previous_workday(today_raw: str | None, channels: set[str] | None = None, limit: int | None = None) -> None:
today = datetime.strptime(today_raw, "%Y-%m-%d").date() if today_raw else date.today()
day = previous_workday(today)
path = daily_by_date_path(day)
records = filter_channels(read_jsonl(path), channels)
if print_records_section(f"## Mattermost mirror previous-workday context ({day.isoformat()})", records, limit):
return
if channels:
print("Filtered to project channels: " + ", ".join(sorted(channels)))
print("No proxy mirror previous-workday project context available; not falling back to broad latest context.")
def mode_standup(today_raw: str | None, channels: set[str], limit: int | None) -> None:
"""Print a compact standup-focused view.
Standup mode intentionally avoids latest.md because latest.md is a bounded
global window and can contain stale or unrelated channels. Use date-bucketed
mirror files filtered to known project channels instead.
"""
today = datetime.strptime(today_raw, "%Y-%m-%d").date() if today_raw else date.today()
day = previous_workday(today)
previous_records = filter_channels(read_jsonl(daily_by_date_path(day)), channels)
today_records = filter_channels(read_jsonl(daily_by_date_path(today)), channels)
print("## Mattermost mirror standup context")
if channels:
print("Filtered to configured context channels: " + ", ".join(sorted(channels)))
else:
print("No context channel filter configured; using all mirrored date-bucket records.")
print_records_section(f"\n### Previous workday ({day.isoformat()})", previous_records, limit)
print_records_section(f"\n### Today so far ({today.isoformat()})", today_records, limit)
def mode_focused() -> None:
records = read_jsonl(MIRROR_DIR / "latest.jsonl")
if not records:
records = read_jsonl(LEGACY_GENERATED)
if not records:
print("No Mattermost context available.")
return
manager_names = {"jeff", "jeff.dewitte"}
manager_records = [record for record in records if str(record.get("username", "")).lower() in manager_names]
focused = manager_records[-10:] if manager_records else records[-15:]
print_jsonl(focused)
def main() -> None:
load_local_env()
parser = argparse.ArgumentParser()
parser.add_argument("--mode", choices=["latest", "previous-workday", "standup", "focused"], default="latest")
parser.add_argument("--today", default="")
parser.add_argument(
"--channels",
default="",
help="Comma-separated channel names or IDs. Defaults to AIW_MATTERMOST_CONTEXT_CHANNELS from environment/.env when set.",
)
parser.add_argument("--limit", type=int, default=80, help="Max records per section; use 0 for no limit.")
args = parser.parse_args()
channels = parse_channels(args.channels or None)
limit = args.limit if args.limit > 0 else None
if args.mode == "latest":
mode_latest()
elif args.mode == "previous-workday":
mode_previous_workday(args.today or None, channels=None, limit=limit)
elif args.mode == "standup":
mode_standup(args.today or None, channels, limit)
elif args.mode == "focused":
mode_focused()
if __name__ == "__main__":
main()

View File

@@ -2,6 +2,11 @@
This directory contains the workspace-local Mattermost extractor used by OpenCode to refresh communication context.
The preferred live Mattermost evidence source is now the proxy mirror under
`ai/inbox/mattermost-mirror/` when it is running. This legacy extractor remains
the fallback and explicit refresh path for commands that need a fresh pull from
the Mattermost API.
## Files
- `mattermost_context.py`