From 901bf0ae124e0d8364da209864d3d1163fa40575 Mon Sep 17 00:00:00 2001 From: Kjell Tore Guttormsen Date: Thu, 9 Apr 2026 21:47:05 +0200 Subject: [PATCH] feat(ms-ai-architect): add local cron wrapper for weekly KB maintenance Wrapper script that polls Microsoft Learn sitemaps and spawns a local Claude session to update stale reference files. Designed for crontab, zero cloud dependencies. Co-Authored-By: Claude Opus 4.6 --- .../scripts/kb-update/weekly-kb-cron.mjs | 147 ++++++++++++++++++ 1 file changed, 147 insertions(+) create mode 100755 plugins/ms-ai-architect/scripts/kb-update/weekly-kb-cron.mjs diff --git a/plugins/ms-ai-architect/scripts/kb-update/weekly-kb-cron.mjs b/plugins/ms-ai-architect/scripts/kb-update/weekly-kb-cron.mjs new file mode 100755 index 0000000..5a91893 --- /dev/null +++ b/plugins/ms-ai-architect/scripts/kb-update/weekly-kb-cron.mjs @@ -0,0 +1,147 @@ +#!/usr/bin/env node +// weekly-kb-cron.mjs — Local cron wrapper for weekly KB maintenance. +// Runs sitemap polling + change report. If critical/high findings exist, +// spawns a local Claude Code session to update stale reference files. +// +// Crontab: 23 4 * * 3 node /path/to/weekly-kb-cron.mjs >> /tmp/kb-cron.log 2>&1 +// +// Zero npm dependencies. Uses only node builtins. + +import { execFileSync, execSync } from 'node:child_process'; +import { readFileSync, existsSync, appendFileSync } from 'node:fs'; +import { join, dirname } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const PLUGIN_ROOT = join(__dirname, '..', '..'); +const DATA_DIR = join(__dirname, 'data'); +const CLAUDE_BIN = '/Users/ktg/.local/bin/claude'; +const LOG_FILE = '/tmp/kb-cron.log'; + +const MAX_UPDATE_FILES = 10; + +function log(msg) { + const ts = new Date().toISOString(); + const line = `[${ts}] ${msg}`; + console.log(line); +} + +function run(script, args = []) { + const fullPath = join(__dirname, script); + log(`Running ${script} ${args.join(' ')}`); + try { + execFileSync('node', [fullPath, ...args], { + stdio: 'inherit', + timeout: 10 * 60 * 1000, + cwd: PLUGIN_ROOT, + }); + } catch (err) { + log(`ERROR: ${script} failed: ${err.message}`); + process.exit(1); + } +} + +// --- Step 1: Sitemap polling + discovery --- +log('=== Weekly KB Cron Start ==='); +run('run-weekly-update.mjs', ['--force', '--discover']); + +// --- Step 2: Read change report --- +const reportPath = join(DATA_DIR, 'change-report.json'); +if (!existsSync(reportPath)) { + log('No change report found. Exiting.'); + process.exit(0); +} + +const report = JSON.parse(readFileSync(reportPath, 'utf8')); +const { critical = 0, high = 0, medium = 0, low = 0 } = report.by_priority || {}; +log(`Change report: ${critical} critical, ${high} high, ${medium} medium, ${low} low`); + +// --- Step 3: If critical/high exist, spawn Claude for updates --- +if (critical + high === 0) { + log('No critical/high findings. Committing data updates only.'); + + try { + execSync('git add scripts/kb-update/data/', { cwd: PLUGIN_ROOT, stdio: 'pipe' }); + const status = execSync('git status --porcelain scripts/kb-update/data/', { cwd: PLUGIN_ROOT, encoding: 'utf8' }); + if (status.trim()) { + execSync( + 'git commit -m "docs(architect): weekly KB poll — no stale files"', + { cwd: PLUGIN_ROOT, stdio: 'pipe' } + ); + execSync('git push origin main', { cwd: PLUGIN_ROOT, stdio: 'pipe' }); + log('Data committed and pushed.'); + } else { + log('No data changes to commit.'); + } + } catch (err) { + log(`Git error: ${err.message}`); + } + + log('=== Weekly KB Cron Done ==='); + process.exit(0); +} + +// Build list of stale files (critical + high only, max MAX_UPDATE_FILES) +const staleFiles = (report.files || []) + .filter(f => f.priority === 'critical' || f.priority === 'high') + .slice(0, MAX_UPDATE_FILES); + +log(`Spawning Claude to update ${staleFiles.length} stale files...`); + +const fileList = staleFiles.map(f => { + const urls = (f.changed_urls || []).slice(0, 5).join('\n '); + return `- ${f.path} [${f.priority}]\n Changed URLs:\n ${urls}`; +}).join('\n'); + +const prompt = `Du er Cosmo Skyberg. Oppdater ${staleFiles.length} stale kunnskapsreferanser i ms-ai-architect pluginen. + +Arbeidsmappe: ${PLUGIN_ROOT} + +## Filer å oppdatere + +${fileList} + +## For HVER fil + +1. Les filen med Read +2. Bruk microsoft_docs_fetch på de endrede kilde-URLene listet over +3. Bruk microsoft_docs_search for supplerende info +4. Oppdater filen med Edit: + - Oppdater "Last updated" til ${new Date().toISOString().slice(0, 7)} + - Oppdater utdaterte fakta, priser, datoer + - Bevar eksisterende struktur og seksjoner + - Marker oppdatert innhold med "Verified (MCP ${new Date().toISOString().slice(0, 7)})" + +## Etter alle oppdateringer + +1. Kjør: node scripts/kb-update/build-registry.mjs --merge +2. Kjør: node scripts/kb-update/report-changes.mjs +3. git add skills/ scripts/kb-update/data/ +4. git commit -m "docs(architect): weekly KB update — ${staleFiles.length} files refreshed + +Co-Authored-By: Claude Sonnet 4.6 " +5. git push origin main + +## Regler +- Aldri slett filer, kun oppdater +- Bruk Edit, ikke Write +- Bevar all eksisterende struktur`; + +try { + execFileSync(CLAUDE_BIN, [ + '-p', prompt, + '--model', 'sonnet', + '--allowedTools', 'Read,Edit,Bash,Glob,Grep,mcp__microsoft-learn__microsoft_docs_search,mcp__microsoft-learn__microsoft_docs_fetch', + '--max-turns', '30', + ], { + stdio: 'inherit', + timeout: 15 * 60 * 1000, + cwd: PLUGIN_ROOT, + }); + log('Claude session completed.'); +} catch (err) { + log(`Claude session error: ${err.message}`); + process.exit(1); +} + +log('=== Weekly KB Cron Done ===');