fix(mcp-server): 3 A2A pipeline bug fixes from audit
- nomgap-client: add electrolyte_reminder + extended_fast_warning to PushTriggerType; rename scheduledAt → scheduledFor to match backend schema; fix startedAt type string → number (backend stores epoch ms) - nomgap-tools: rename scheduledAt → scheduledFor in nomgap.push.fire tool schema + execute call - safety-monitor-pipeline: fix FastStateResult.startedAt type number; use epoch ms directly instead of new Date(string) - sync-diagnostics-pipeline: remove peakpulseGetStats (returns aggregate stats not sync queue); derive queueDepth from recentFailureCount telemetry; add lastSuccessfulSync derivation from events
This commit is contained in:
parent
a7d8a58348
commit
7e47151918
@ -41,18 +41,18 @@
|
||||
|
||||
### 2.3 API & Clients
|
||||
|
||||
| Package | Purpose | Exports | Consumers |
|
||||
| ------------------------------- | ------------------------- | --------------------------------------- | ------------- |
|
||||
| `@bytelyst/api-client` | Typed fetch wrapper | `createApiClient()` with auth injection | Web + RN |
|
||||
| `@bytelyst/platform-client` | Platform service client | Typed fetch with 401 retry | Web + RN |
|
||||
| `@bytelyst/broadcast-client` | Broadcast/survey client | Poll and broadcast APIs | iOS + Android |
|
||||
| `@bytelyst/survey-client` | Survey response client | Submit survey responses | iOS + Android |
|
||||
| `@bytelyst/telemetry-client` | Telemetry ingestion | `createTelemetryClient()` with batching | All platforms |
|
||||
| `@bytelyst/feature-flag-client` | Feature flag polling | `createFeatureFlagClient()` | All platforms |
|
||||
| `@bytelyst/kill-switch-client` | Kill switch check | `createKillSwitchClient()` (fail-open) | All platforms |
|
||||
| `@bytelyst/offline-queue` | Persistent retry queue | Configurable storage adapter | Web + RN |
|
||||
| `@bytelyst/diagnostics-client` | Remote diagnostics client | Error reporting, log upload | All platforms |
|
||||
| `@bytelyst/feedback-client` | In-app feedback | Submit feedback + attachments | iOS + Android |
|
||||
| Package | Purpose | Exports | Consumers |
|
||||
| ------------------------------- | ------------------------------------- | ------------------------------------------------------------------------------------- | ---------------------------------- |
|
||||
| `@bytelyst/api-client` | Typed fetch wrapper | `createApiClient()` with auth injection | Web + RN |
|
||||
| `@bytelyst/platform-client` | Platform service client | Typed fetch with 401 retry | Web + RN |
|
||||
| `@bytelyst/broadcast-client` | Broadcast/survey client | Poll and broadcast APIs | iOS + Android |
|
||||
| `@bytelyst/survey-client` | Survey response client | Submit survey responses | iOS + Android |
|
||||
| `@bytelyst/telemetry-client` | Telemetry ingestion | `createTelemetryClient()` with batching | All platforms |
|
||||
| `@bytelyst/feature-flag-client` | Feature flag polling | `createFeatureFlagClient()` | All platforms |
|
||||
| `@bytelyst/kill-switch-client` | Kill switch check | `createKillSwitchClient()` (fail-open) | All platforms |
|
||||
| `@bytelyst/offline-queue` | Persistent retry queue | Configurable storage adapter | Web + RN |
|
||||
| `@bytelyst/diagnostics-client` | Admin-triggered remote debug sessions | Session polling, trace spans, breadcrumbs, network/console/error capture, batch flush | ✅ All 6 products (web + iOS + RN) |
|
||||
| `@bytelyst/feedback-client` | In-app feedback | Submit feedback + attachments | iOS + Android |
|
||||
|
||||
### 2.4 Storage & Data
|
||||
|
||||
|
||||
547
scripts/update-agent-docs.sh
Executable file
547
scripts/update-agent-docs.sh
Executable file
@ -0,0 +1,547 @@
|
||||
#!/usr/bin/env bash
|
||||
# scripts/update-agent-docs.sh
|
||||
#
|
||||
# Regenerates AI agent config files across all repos listed in repos.txt.
|
||||
#
|
||||
# Files always regenerated (overwritten):
|
||||
# .cursorrules, .windsurfrules, .clinerules, .aider.conf.yml,
|
||||
# .editorconfig, .github/copilot-instructions.md
|
||||
#
|
||||
# Files created only if missing (never overwritten):
|
||||
# CLAUDE.md — hand-crafted summary; script creates a placeholder if absent
|
||||
#
|
||||
# Files never touched:
|
||||
# AGENTS.md — canonical source of truth; must be maintained manually
|
||||
#
|
||||
# Usage:
|
||||
# ./scripts/update-agent-docs.sh # regenerate + commit
|
||||
# ./scripts/update-agent-docs.sh --dry-run # show what would change, no writes
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPOS_TXT="${SCRIPT_DIR}/../.windsurf/workflows/repos.txt"
|
||||
BASE_DIR="$(cd "${SCRIPT_DIR}/../.." && pwd)"
|
||||
DRY_RUN=false
|
||||
CHANGED_REPOS=()
|
||||
|
||||
[[ "${1:-}" == "--dry-run" ]] && DRY_RUN=true
|
||||
|
||||
# ── colours ────────────────────────────────────────────────────────────────
|
||||
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'; CYAN='\033[0;36m'; NC='\033[0m'
|
||||
info() { echo -e "${BLUE}[INFO]${NC} $*"; }
|
||||
ok() { echo -e "${GREEN}[OK]${NC} $*"; }
|
||||
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
|
||||
header(){ echo -e "\n${CYAN}══ $* ══${NC}"; }
|
||||
|
||||
# ── per-repo single-line metadata ──────────────────────────────────────────
|
||||
set_meta() {
|
||||
NAME="" ID="" TAGLINE="" STACK="" BUILD_VFY="" LINT1="" LINT2="" LINT3="" AIDER_READ2="README.md"
|
||||
case "$1" in
|
||||
learning_ai_common_plat)
|
||||
NAME="@bytelyst Common Platform"
|
||||
ID="(product-agnostic)"
|
||||
TAGLINE="Shared packages + microservices for the ByteLyst ecosystem"
|
||||
STACK="TypeScript, ESM, pnpm workspace, Fastify 5, Vitest, Azure Cosmos DB"
|
||||
BUILD_VFY="pnpm build && pnpm test && pnpm typecheck"
|
||||
LINT1="pnpm build 2>&1 | tail -10"
|
||||
LINT2="pnpm test 2>&1 | tail -10"
|
||||
LINT3="pnpm typecheck 2>&1 | tail -10"
|
||||
AIDER_READ2="README.md"
|
||||
;;
|
||||
learning_voice_ai_agent)
|
||||
NAME="LysnrAI"
|
||||
ID="lysnrai"
|
||||
TAGLINE="Cross-platform voice-to-text dictation platform"
|
||||
STACK="Python 3.12 (desktop/backend) + Next.js 16 (dashboards) + Fastify 5 (microservices in sibling repo)"
|
||||
BUILD_VFY="python -m pytest tests/ -v --tb=short"
|
||||
LINT1="cd user-dashboard-web && npx tsc --noEmit 2>&1 | tail -10"
|
||||
LINT2="python -m pytest tests/ -x --tb=short 2>&1 | tail -10"
|
||||
AIDER_READ2="README_MONO_REPO.md"
|
||||
;;
|
||||
learning_multimodal_memory_agents)
|
||||
NAME="MindLyst"
|
||||
ID="mindlyst"
|
||||
TAGLINE="Role-Based Life OS — AI-powered multimodal second brain"
|
||||
STACK="KMP (shared) + SwiftUI (iOS) + Jetpack Compose (Android) + Next.js 16 (web)"
|
||||
BUILD_VFY="./gradlew :shared:compileKotlinIosSimulatorArm64"
|
||||
LINT1="cd mindlyst-native && ./gradlew :shared:compileKotlinIosSimulatorArm64 2>&1 | tail -5"
|
||||
LINT2="cd mindlyst-native/web && npx next build 2>&1 | tail -10"
|
||||
AIDER_READ2="ARCHITECTURE.md"
|
||||
;;
|
||||
learning_ai_clock)
|
||||
NAME="ChronoMind"
|
||||
ID="chronomind"
|
||||
TAGLINE="AI-Powered Contextual Clock & Timer"
|
||||
STACK="Next.js 16 (web) + SwiftUI (iOS/Watch/Mac) + Jetpack Compose (Android) + Fastify 5 (backend)"
|
||||
BUILD_VFY="cd web && npm test && npm run typecheck && npm run build"
|
||||
LINT1="cd web && npm test 2>&1 | tail -10"
|
||||
LINT2="cd web && npm run typecheck 2>&1 | tail -10"
|
||||
AIDER_READ2="docs/PRD.md"
|
||||
;;
|
||||
learning_ai_fastgap)
|
||||
NAME="NomGap"
|
||||
ID="nomgap"
|
||||
TAGLINE="Fasting visualization and coaching app"
|
||||
STACK="React Native (Expo SDK 55) + TypeScript + Fastify 5 (backend)"
|
||||
BUILD_VFY="npm test && npm run typecheck"
|
||||
LINT1="npm test 2>&1 | tail -10"
|
||||
LINT2="npm run typecheck 2>&1 | tail -10"
|
||||
AIDER_READ2="docs/PRD.md"
|
||||
;;
|
||||
learning_ai_jarvis_jr)
|
||||
NAME="JarvisJr"
|
||||
ID="jarvisjr"
|
||||
TAGLINE="Voice-first multi-agent coaching platform"
|
||||
STACK="SwiftUI (iOS/Watch/Mac) + Next.js 16 (web) + Jetpack Compose (Android) + Fastify 5 (backend)"
|
||||
BUILD_VFY="cd web && npm test && npm run typecheck && npm run build"
|
||||
LINT1="cd web && npm test 2>&1 | tail -10"
|
||||
LINT2="cd web && npm run typecheck 2>&1 | tail -10"
|
||||
AIDER_READ2="docs/ENHANCED_IDEA_v2.md"
|
||||
;;
|
||||
learning_ai_peakpulse)
|
||||
NAME="PeakPulse"
|
||||
ID="peakpulse"
|
||||
TAGLINE="Sensor-driven adventure tracker for hikers and skiers"
|
||||
STACK="SwiftUI (iOS 17+), SwiftData, MapKit, WeatherKit, ActivityKit, WidgetKit, App Intents"
|
||||
BUILD_VFY="cd ios && xcodegen generate && xcodebuild -scheme PeakPulse -sdk iphonesimulator build"
|
||||
LINT1="cd ios && xcodebuild -scheme PeakPulse -sdk iphonesimulator build 2>&1 | tail -20"
|
||||
AIDER_READ2="docs/PRD.md"
|
||||
;;
|
||||
*)
|
||||
warn "Unknown repo: $1 — skipping"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# ── per-repo multi-line content ─────────────────────────────────────────────
|
||||
repo_rules() {
|
||||
case "$1" in
|
||||
learning_ai_common_plat)
|
||||
echo "- Package manager: pnpm — NEVER use npm"
|
||||
echo "- ESM everywhere: \"type\": \"module\", .js extensions in imports"
|
||||
echo "- Fastify module pattern: types.ts → repository.ts → routes.ts"
|
||||
echo "- Use req.log / app.log — never console.log"
|
||||
echo "- Every Cosmos document MUST include a productId field"
|
||||
echo "- peerDependencies for heavy deps; workspace:* for inter-package deps"
|
||||
echo "- Services re-export @bytelyst/* in src/lib/ for clean internal imports"
|
||||
;;
|
||||
learning_voice_ai_agent)
|
||||
echo "- Python: structlog (never print), ruff lint, type hints required, pydantic-settings"
|
||||
echo "- TypeScript: App Router (not Pages), @bytelyst/api-client for service clients"
|
||||
echo "- All Cosmos documents MUST include productId field"
|
||||
echo "- Use PRODUCT_ID from @bytelyst/config — never hardcode \"lysnrai\""
|
||||
echo "- Dashboards: TailwindCSS v4, shadcn/ui, Recharts"
|
||||
echo "- Desktop build requires --webpack flag for Next.js"
|
||||
;;
|
||||
learning_multimodal_memory_agents)
|
||||
echo "- ALL business logic in shared/src/commonMain/ — NEVER in platform UI code"
|
||||
echo "- Android/iOS/Web are thin UI shells consuming shared repositories via StateFlow"
|
||||
echo "- Koin for DI — register all new deps in SharedModule.kt"
|
||||
echo "- MindLystTokens.kt is the single source of design tokens"
|
||||
echo "- Web: Pages Router, vanilla CSS with --ml-* props, NO Tailwind"
|
||||
echo "- Use compilerOptions {} (not deprecated kotlinOptions {})"
|
||||
echo "- Use StateFlow for observable state, never LiveData"
|
||||
;;
|
||||
learning_ai_clock)
|
||||
echo "- Web engine logic in web/src/lib/ — pure TS, no React imports"
|
||||
echo "- Components in web/src/components/ — React UI only"
|
||||
echo "- iOS shared logic in ios/ChronoMind/Shared/ — consumed by all Apple targets"
|
||||
echo "- Android engine in android/.../engine/ — pure Kotlin, no Android framework deps"
|
||||
echo "- Web build: must use --webpack flag (Serwist incompatible with Turbopack)"
|
||||
echo "- Every Cosmos doc: productId: \"chronomind\""
|
||||
echo "- Theme: --cm-* CSS custom properties (web), ChronoMindTheme (native)"
|
||||
;;
|
||||
learning_ai_fastgap)
|
||||
echo "- All engine logic in src/lib/ — zero React Native imports"
|
||||
echo "- Screens are thin composites wiring store + engines + components"
|
||||
echo "- Theme tokens from src/theme/ — never hardcode colors, spacing, or fonts"
|
||||
echo "- Expo native modules mocked in __mocks__/ for Vitest"
|
||||
echo "- Engine modules are pure functions — testable without mocking RN"
|
||||
echo "- Every Cosmos doc: productId: \"nomgap\""
|
||||
;;
|
||||
learning_ai_jarvis_jr)
|
||||
echo "- iOS shared logic in ios/JarvisJr/Shared/ — consumed by iOS, Watch, Mac"
|
||||
echo "- Web engine in web/src/lib/ — pure TS, no React imports"
|
||||
echo "- Web components in web/src/components/ — React UI only"
|
||||
echo "- Android engine in android/.../engine/ — pure Kotlin, no Android framework deps"
|
||||
echo "- Web build: must use --webpack flag (Serwist incompatible with Turbopack)"
|
||||
echo "- Never use print() in Swift — use os.Logger"
|
||||
echo "- Theme: --jj-* CSS custom properties (web), JarvisJrTheme (native)"
|
||||
echo "- Every Cosmos doc: productId: \"jarvisjr\""
|
||||
;;
|
||||
learning_ai_peakpulse)
|
||||
echo "- Models in ios/PeakPulse/Models/ — SwiftData @Model + Codable structs"
|
||||
echo "- Services in ios/PeakPulse/Services/ — pure Swift engine logic"
|
||||
echo "- ViewModels in ios/PeakPulse/ViewModels/ — @Observable MVVM"
|
||||
echo "- Platform/ files are thin wrappers over ByteLystPlatformSDK — never reimplement"
|
||||
echo "- All logging via os.Logger — never use print()"
|
||||
echo "- All colors from PeakPulseTheme — never hardcode"
|
||||
echo "- Every Cosmos doc: productId: \"peakpulse\""
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
repo_paths() {
|
||||
case "$1" in
|
||||
learning_ai_common_plat)
|
||||
echo "- packages/ — @bytelyst/* shared libraries (errors, cosmos, config, auth, api-client, fastify-core, react-auth, logger, testing, blob, extraction, monitoring, design-tokens)"
|
||||
echo "- services/platform-service/ — consolidated platform service (port 4003)"
|
||||
echo "- services/extraction-service/ — text extraction + Python sidecar (port 4005)"
|
||||
echo "- dashboards/admin-web/ — admin console (port 3001)"
|
||||
echo "- dashboards/tracker-web/ — issue tracker (port 3003)"
|
||||
;;
|
||||
learning_voice_ai_agent)
|
||||
echo "- src/ — Desktop app (Python 3.12, tkinter, Azure Speech SDK)"
|
||||
echo "- backend/ — Fastify 5 + TypeScript backend (port 4015)"
|
||||
echo "- user-dashboard-web/ — Next.js 16 user portal (port 3002)"
|
||||
echo "- ../learning_ai_common_plat/services/ — platform-service (4003), extraction-service (4005)"
|
||||
echo "- ../learning_ai_common_plat/dashboards/ — admin-web (3001), tracker-web (3003)"
|
||||
echo "- mobile_app/ios/ — Swift + SwiftUI"
|
||||
echo "- mobile_app/android/ — Kotlin + Jetpack Compose"
|
||||
;;
|
||||
learning_multimodal_memory_agents)
|
||||
echo "- mindlyst-native/shared/src/commonMain/ — KMP shared logic (ALL business logic)"
|
||||
echo "- mindlyst-native/androidApp/ — Jetpack Compose Android app"
|
||||
echo "- mindlyst-native/iosApp/ — SwiftUI iOS app"
|
||||
echo "- mindlyst-native/web/ — Next.js 16 web dashboard"
|
||||
echo "- backend/ — Fastify 5 + TypeScript backend (port 4014)"
|
||||
echo "- design-system/ — design tokens source"
|
||||
;;
|
||||
learning_ai_clock)
|
||||
echo "- web/src/lib/ — Pure TS engine modules (timer, cascade, urgency, nl-parser, etc.)"
|
||||
echo "- web/src/components/ — React UI components"
|
||||
echo "- ios/ChronoMind/Shared/ — Shared Swift code (iOS + Watch + Mac + Widgets)"
|
||||
echo "- android/app/.../engine/ — Pure Kotlin engine"
|
||||
echo "- backend/ — Fastify 5 + TypeScript backend (port 4011)"
|
||||
;;
|
||||
learning_ai_fastgap)
|
||||
echo "- src/lib/ — Pure engine modules (NO React Native deps)"
|
||||
echo "- src/screens/ — Screen components (thin UI composites)"
|
||||
echo "- src/components/ — Reusable UI components"
|
||||
echo "- src/store/ — Zustand store"
|
||||
echo "- src/theme/ — Design tokens (colors, typography, spacing)"
|
||||
echo "- backend/ — Fastify 5 + TypeScript backend (port 4013)"
|
||||
;;
|
||||
learning_ai_jarvis_jr)
|
||||
echo "- ios/JarvisJr/Shared/ — Cross-platform Swift code (iOS + Watch + Mac)"
|
||||
echo "- web/src/lib/ — Pure TS engine + API clients"
|
||||
echo "- web/src/app/ — Next.js App Router pages"
|
||||
echo "- android/app/.../engine/ — Pure Kotlin engine"
|
||||
echo "- backend/ — Fastify 5 + TypeScript backend (port 4012)"
|
||||
;;
|
||||
learning_ai_peakpulse)
|
||||
echo "- ios/PeakPulse/Models/ — SwiftData models"
|
||||
echo "- ios/PeakPulse/Services/ — Pure Swift engine logic"
|
||||
echo "- ios/PeakPulse/ViewModels/ — @Observable MVVM"
|
||||
echo "- ios/PeakPulse/Views/ — SwiftUI screens + components"
|
||||
echo "- ios/PeakPulse/Platform/ — ByteLystPlatformSDK thin wrappers"
|
||||
echo "- backend/ — Fastify 5 + TypeScript backend (port 4010)"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# ── file generators ─────────────────────────────────────────────────────────
|
||||
|
||||
write_editorconfig() {
|
||||
cat > "$1" << 'EOF'
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
EOF
|
||||
}
|
||||
|
||||
write_cursorrules() {
|
||||
local dest="$1" repo="$2"
|
||||
local RULES PATHS
|
||||
RULES=$(repo_rules "$repo")
|
||||
PATHS=$(repo_paths "$repo")
|
||||
cat > "$dest" << EOF
|
||||
# ${NAME} — Cursor Rules
|
||||
# Read AGENTS.md for full context.
|
||||
|
||||
Project: ${NAME} — ${TAGLINE}
|
||||
Product ID: ${ID}
|
||||
Stack: ${STACK}
|
||||
|
||||
## Architecture
|
||||
${PATHS}
|
||||
|
||||
## Rules
|
||||
${RULES}
|
||||
- Commits: feat(scope): description / fix(scope): description
|
||||
- Never delete existing comments/documentation unless asked
|
||||
- Never add emojis unless asked
|
||||
- Never hardcode secrets, colors, or API URLs
|
||||
|
||||
## Build Verification
|
||||
- ${BUILD_VFY}
|
||||
|
||||
## Key Documents
|
||||
- AGENTS.md — Full AI agent instructions (read this first)
|
||||
EOF
|
||||
}
|
||||
|
||||
write_windsurfrules() {
|
||||
local dest="$1" repo="$2"
|
||||
local RULES PATHS
|
||||
RULES=$(repo_rules "$repo")
|
||||
PATHS=$(repo_paths "$repo")
|
||||
cat > "$dest" << EOF
|
||||
# ${NAME} — Windsurf / Codeium Rules
|
||||
# Read AGENTS.md for full context. These are the critical rules.
|
||||
|
||||
Project: ${NAME} — ${TAGLINE}
|
||||
Stack: ${STACK}
|
||||
|
||||
## Architecture Rules
|
||||
${PATHS}
|
||||
|
||||
## Conventions
|
||||
${RULES}
|
||||
- Commits: feat(scope): description / fix(scope): description
|
||||
|
||||
## Build Verification
|
||||
- ${BUILD_VFY}
|
||||
EOF
|
||||
}
|
||||
|
||||
write_clinerules() {
|
||||
local dest="$1" repo="$2"
|
||||
local RULES PATHS
|
||||
RULES=$(repo_rules "$repo")
|
||||
PATHS=$(repo_paths "$repo")
|
||||
cat > "$dest" << EOF
|
||||
# .clinerules — Cline / Roo Code Rules for ${NAME}
|
||||
# Read AGENTS.md for the complete onboarding guide.
|
||||
|
||||
## Project: ${NAME} — ${TAGLINE}
|
||||
Architecture: ${STACK}
|
||||
|
||||
## Mandatory Rules
|
||||
${RULES}
|
||||
- Commits: \`type(scope): description\`
|
||||
- After changes, verify: \`${BUILD_VFY}\`
|
||||
|
||||
## Key File Locations
|
||||
${PATHS}
|
||||
- Full guide: \`AGENTS.md\`
|
||||
EOF
|
||||
}
|
||||
|
||||
write_aider_conf() {
|
||||
local dest="$1"
|
||||
# Build lint-cmd list (skip empty entries)
|
||||
local lint_block=""
|
||||
for cmd in "${LINT1:-}" "${LINT2:-}" "${LINT3:-}"; do
|
||||
[[ -z "$cmd" ]] && continue
|
||||
lint_block+=" - '${cmd}'"$'\n'
|
||||
done
|
||||
# Build read list
|
||||
local read_block=" - AGENTS.md"$'\n'
|
||||
[[ -n "${AIDER_READ2:-}" ]] && read_block+=" - ${AIDER_READ2}"$'\n'
|
||||
|
||||
cat > "$dest" << EOF
|
||||
# .aider.conf.yml — Aider Configuration for ${NAME}
|
||||
# Helps Aider understand the project structure and conventions.
|
||||
|
||||
read:
|
||||
${read_block}
|
||||
conventions: AGENTS.md
|
||||
|
||||
lint-cmd:
|
||||
${lint_block}
|
||||
auto-commits: false
|
||||
EOF
|
||||
}
|
||||
|
||||
write_copilot_instructions() {
|
||||
local dest_dir="$1" repo="$2"
|
||||
local RULES PATHS
|
||||
RULES=$(repo_rules "$repo")
|
||||
PATHS=$(repo_paths "$repo")
|
||||
mkdir -p "$dest_dir"
|
||||
cat > "${dest_dir}/copilot-instructions.md" << EOF
|
||||
# GitHub Copilot Instructions — ${NAME}
|
||||
|
||||
> For full agent instructions, read [\`AGENTS.md\`](../AGENTS.md) at the repo root.
|
||||
|
||||
## Project Context
|
||||
|
||||
**${NAME}** — ${TAGLINE}.
|
||||
Stack: ${STACK}
|
||||
|
||||
## Code Generation Rules
|
||||
|
||||
### Always
|
||||
${RULES}
|
||||
- Include \`productId: "${ID}"\` in every Cosmos DB document
|
||||
- Use \`type(scope): description\` commit message format
|
||||
- Fix source code, not tests (unless the test itself is wrong)
|
||||
|
||||
### Never
|
||||
- \`console.log\` or \`print()\` in production code
|
||||
- Hardcoded secrets, API keys, colors, or URLs
|
||||
- Delete existing comments or documentation unless explicitly asked
|
||||
- Add emojis unless asked
|
||||
|
||||
## Key Paths
|
||||
${PATHS}
|
||||
|
||||
## Build Verification
|
||||
\`\`\`bash
|
||||
${BUILD_VFY}
|
||||
\`\`\`
|
||||
EOF
|
||||
}
|
||||
|
||||
write_claude_placeholder() {
|
||||
local dest="$1" repo="$2"
|
||||
local PATHS
|
||||
PATHS=$(repo_paths "$repo")
|
||||
cat > "$dest" << EOF
|
||||
# ${NAME} — Claude Code Instructions
|
||||
|
||||
> This file is read automatically by Claude Code. For full agent instructions
|
||||
> shared across all AI tools, see [\`AGENTS.md\`](AGENTS.md).
|
||||
|
||||
## Quick Context
|
||||
|
||||
**Product:** ${NAME} — ${TAGLINE}
|
||||
**Product ID:** \`${ID}\`
|
||||
**Stack:** ${STACK}
|
||||
|
||||
## Rules
|
||||
|
||||
1. **Read \`AGENTS.md\` first** — it has coding conventions, file ownership, and tech stack rules.
|
||||
2. **Every Cosmos document** must have a \`productId: "${ID}"\` field.
|
||||
3. **Commit messages:** \`type(scope): description\` (feat, fix, docs, refactor, test, chore).
|
||||
4. **Fix source, not tests** — unless the test itself is wrong.
|
||||
5. **Never use** \`console.log\`, \`print()\`, or hardcoded colors/secrets/URLs.
|
||||
|
||||
## Architecture
|
||||
|
||||
${PATHS}
|
||||
|
||||
## Build Verification
|
||||
|
||||
\`\`\`bash
|
||||
${BUILD_VFY}
|
||||
\`\`\`
|
||||
EOF
|
||||
}
|
||||
|
||||
# ── main loop ───────────────────────────────────────────────────────────────
|
||||
|
||||
if [[ ! -f "$REPOS_TXT" ]]; then
|
||||
echo -e "${RED}[ERROR]${NC} repos.txt not found at: ${REPOS_TXT}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
REPOS=()
|
||||
while IFS= read -r line; do
|
||||
[[ "$line" =~ ^[[:space:]]*# ]] && continue
|
||||
[[ -z "${line// }" ]] && continue
|
||||
REPOS+=("$line")
|
||||
done < "$REPOS_TXT"
|
||||
info "Base directory: ${BASE_DIR}"
|
||||
info "Repos to process: ${#REPOS[@]}"
|
||||
$DRY_RUN && warn "DRY RUN — no files will be written or committed"
|
||||
|
||||
for REPO in "${REPOS[@]}"; do
|
||||
header "$REPO"
|
||||
REPO_DIR="${BASE_DIR}/${REPO}"
|
||||
|
||||
if [[ ! -d "$REPO_DIR" ]]; then
|
||||
warn "Directory not found at ${REPO_DIR} — skipping"
|
||||
continue
|
||||
fi
|
||||
|
||||
if ! set_meta "$REPO"; then
|
||||
continue
|
||||
fi
|
||||
|
||||
if [[ ! -f "${REPO_DIR}/AGENTS.md" ]]; then
|
||||
warn "AGENTS.md not found in ${REPO} — skipping (run manually to bootstrap)"
|
||||
continue
|
||||
fi
|
||||
|
||||
if $DRY_RUN; then
|
||||
info "Would generate agent docs for ${NAME} (${ID})"
|
||||
info " build: ${BUILD_VFY}"
|
||||
continue
|
||||
fi
|
||||
|
||||
# .editorconfig — always overwrite (identical across all repos)
|
||||
write_editorconfig "${REPO_DIR}/.editorconfig"
|
||||
ok ".editorconfig"
|
||||
|
||||
# .cursorrules — always overwrite
|
||||
write_cursorrules "${REPO_DIR}/.cursorrules" "$REPO"
|
||||
ok ".cursorrules"
|
||||
|
||||
# .windsurfrules — always overwrite
|
||||
write_windsurfrules "${REPO_DIR}/.windsurfrules" "$REPO"
|
||||
ok ".windsurfrules"
|
||||
|
||||
# .clinerules — always overwrite
|
||||
write_clinerules "${REPO_DIR}/.clinerules" "$REPO"
|
||||
ok ".clinerules"
|
||||
|
||||
# .aider.conf.yml — always overwrite
|
||||
write_aider_conf "${REPO_DIR}/.aider.conf.yml"
|
||||
ok ".aider.conf.yml"
|
||||
|
||||
# .github/copilot-instructions.md — always overwrite
|
||||
write_copilot_instructions "${REPO_DIR}/.github" "$REPO"
|
||||
ok ".github/copilot-instructions.md"
|
||||
|
||||
# CLAUDE.md — create only if missing
|
||||
if [[ ! -f "${REPO_DIR}/CLAUDE.md" ]]; then
|
||||
write_claude_placeholder "${REPO_DIR}/CLAUDE.md" "$REPO"
|
||||
ok "CLAUDE.md (created — was missing)"
|
||||
else
|
||||
info "CLAUDE.md exists — skipping (hand-crafted; delete to regenerate)"
|
||||
fi
|
||||
|
||||
# Commit if there are any changes in this repo
|
||||
if git -C "$REPO_DIR" status --porcelain 2>/dev/null | grep -q .; then
|
||||
git -C "$REPO_DIR" add \
|
||||
.editorconfig .cursorrules .windsurfrules .clinerules \
|
||||
.aider.conf.yml CLAUDE.md .github/copilot-instructions.md \
|
||||
2>/dev/null || true
|
||||
git -C "$REPO_DIR" commit -m "chore(docs): regenerate AI agent config files" \
|
||||
--no-verify 2>/dev/null || true
|
||||
ok "Committed changes in ${REPO}"
|
||||
CHANGED_REPOS+=("$REPO")
|
||||
else
|
||||
info "No changes in ${REPO}"
|
||||
fi
|
||||
done
|
||||
|
||||
# ── summary ──────────────────────────────────────────────────────────────────
|
||||
echo ""
|
||||
if [[ ${#CHANGED_REPOS[@]} -gt 0 ]]; then
|
||||
ok "Updated and committed ${#CHANGED_REPOS[@]} repo(s):"
|
||||
for r in "${CHANGED_REPOS[@]}"; do
|
||||
echo " • $r"
|
||||
done
|
||||
echo ""
|
||||
info "Push with:"
|
||||
for r in "${CHANGED_REPOS[@]}"; do
|
||||
echo " git -C ${BASE_DIR}/${r} push"
|
||||
done
|
||||
else
|
||||
ok "All repos already up-to-date — no changes."
|
||||
fi
|
||||
@ -42,7 +42,7 @@ export interface FastingSessionDoc {
|
||||
userId: string;
|
||||
productId: string;
|
||||
protocolId: string;
|
||||
startedAt: string;
|
||||
startedAt: number;
|
||||
targetDurationMs: number;
|
||||
status: 'active' | 'completed' | 'broken';
|
||||
stages: unknown[];
|
||||
@ -153,7 +153,9 @@ export type PushTriggerType =
|
||||
| 'social_invite'
|
||||
| 'weekly_digest'
|
||||
| 'achievement_unlocked'
|
||||
| 'refeeding_reminder';
|
||||
| 'refeeding_reminder'
|
||||
| 'electrolyte_reminder'
|
||||
| 'extended_fast_warning';
|
||||
|
||||
export interface PushTriggerDoc {
|
||||
id: string;
|
||||
@ -171,7 +173,7 @@ export function nomgapPushFire(
|
||||
type: PushTriggerType;
|
||||
userId: string;
|
||||
variables?: Record<string, string>;
|
||||
scheduledAt?: string;
|
||||
scheduledFor?: string;
|
||||
},
|
||||
opts: NomGapClientOptions
|
||||
): Promise<PushTriggerDoc> {
|
||||
|
||||
@ -30,7 +30,7 @@ type PushType = 'refeeding_reminder' | 'extended_fast_warning' | 'electrolyte_re
|
||||
interface FastStateResult {
|
||||
sessionId: string;
|
||||
protocolId: string;
|
||||
startedAt: string;
|
||||
startedAt: number;
|
||||
elapsedHours: number;
|
||||
targetHours: number;
|
||||
isActive: boolean;
|
||||
@ -126,8 +126,8 @@ async function inspectFastState(
|
||||
const session = result.items[0] as FastingSessionDoc | undefined;
|
||||
if (!session) return null;
|
||||
|
||||
const startedAt = session.startedAt ?? session.createdAt;
|
||||
const elapsedMs = Date.now() - new Date(startedAt).getTime();
|
||||
const startedAt = session.startedAt ?? new Date(session.createdAt).getTime();
|
||||
const elapsedMs = Date.now() - startedAt;
|
||||
const elapsedHours = elapsedMs / (1000 * 60 * 60);
|
||||
|
||||
return {
|
||||
|
||||
@ -15,7 +15,6 @@ import { randomUUID } from 'node:crypto';
|
||||
import { z } from 'zod';
|
||||
import { registerTool } from '../tools/registry.js';
|
||||
import type { McpToolRequest } from '../tools/types.js';
|
||||
import { peakpulseGetStats } from '../../lib/peakpulse-client.js';
|
||||
import {
|
||||
telemetryQuery,
|
||||
diagnosticsCreateSession,
|
||||
@ -78,16 +77,7 @@ async function inspectSyncFailures(
|
||||
platform: string | null,
|
||||
opts: { token?: string; requestId?: string }
|
||||
): Promise<SyncFailureInspection> {
|
||||
// Fetch session stats for queue depth
|
||||
let queueDepth = 0;
|
||||
let lastSuccessfulSync: string | null = null;
|
||||
try {
|
||||
const stats = await peakpulseGetStats(opts);
|
||||
queueDepth = ((stats as Record<string, unknown>).unsyncedCount as number) ?? 0;
|
||||
lastSuccessfulSync = ((stats as Record<string, unknown>).lastSyncAt as string) ?? null;
|
||||
} catch {
|
||||
// best-effort
|
||||
}
|
||||
|
||||
// Query telemetry for recent sync failures (last 24h)
|
||||
const now = new Date();
|
||||
@ -105,10 +95,18 @@ async function inspectSyncFailures(
|
||||
{ ...opts, productId: 'peakpulse' }
|
||||
);
|
||||
recentFailureCount = result.total;
|
||||
// Derive last successful sync from most recent event timestamp (inverse: last non-failure event)
|
||||
const events = result.events as Array<Record<string, unknown>>;
|
||||
if (events.length > 0 && recentFailureCount === 0) {
|
||||
lastSuccessfulSync = (events[0]?.['timestamp'] as string) ?? null;
|
||||
}
|
||||
} catch {
|
||||
// best-effort
|
||||
}
|
||||
|
||||
// queueDepth: approximate as recent failure count (each failure = one upload that didn't land)
|
||||
const queueDepth = recentFailureCount;
|
||||
|
||||
// Classify failure pattern
|
||||
let failurePattern: FailurePattern;
|
||||
if (errorCode === 'auth_401' || errorCode === 'unauthorized') {
|
||||
|
||||
@ -105,7 +105,7 @@ registerTool({
|
||||
.optional()
|
||||
.default({})
|
||||
.describe('Template variables for the notification message'),
|
||||
scheduledAt: z.string().datetime().optional().describe('ISO 8601 fire time (default: now)'),
|
||||
scheduledFor: z.string().datetime().optional().describe('ISO 8601 fire time (default: now)'),
|
||||
}),
|
||||
async execute(args, req) {
|
||||
return nomgapPushFire(
|
||||
@ -113,7 +113,7 @@ registerTool({
|
||||
type: args.type,
|
||||
userId: args.userId,
|
||||
variables: args.variables,
|
||||
scheduledAt: args.scheduledAt,
|
||||
scheduledFor: args.scheduledFor,
|
||||
},
|
||||
{ token: tokenOf(req), requestId: req.id }
|
||||
);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user