diff --git a/scripts/tracker-seed/README.md b/scripts/tracker-seed/README.md new file mode 100644 index 0000000..9964c92 --- /dev/null +++ b/scripts/tracker-seed/README.md @@ -0,0 +1,49 @@ +# Tracker seed — Engineering Review work items + +Files the `ENGINEERING_REVIEW_SCORECARD.md` P0–P3 action plan as **tracker items** +(one per affected product) via the platform-service tracker API +(`POST /api/items` — the same endpoint `tracker-web` proxies to). + +## Files +- `engineering-review-items.json` — the item payloads (16 items), scoped per `productId`. +- `seed-tracker-items.mjs` — dependency-free Node script that mints an HS256 + token from `$JWT_SECRET` and POSTs each item (with dedupe-by-title). + +## Prerequisites +The platform stack must be running and reachable (it serves the tracker items API): + +```bash +# in learning_ai_common_plat (Cosmos emulator path) +docker compose up -d # platform-service on :4003 +# …or run platform-service directly (ephemeral, no persistence): +# DB_PROVIDER=memory COSMOS_ENDPOINT=x COSMOS_KEY=x JWT_SECRET=dev \ +# pnpm --dir services/platform-service dev +``` + +## Usage +```bash +cd scripts/tracker-seed + +# 1) Preview — no token, no network calls: +node seed-tracker-items.mjs --dry-run + +# 2) Create for real (uses the same JWT_SECRET platform-service verifies): +JWT_SECRET="" \ +PLATFORM_API_URL="http://localhost:4003" \ +node seed-tracker-items.mjs +``` + +Re-running is safe: existing items are skipped by title per product (use +`--force` to bypass the check). View results in `tracker-web` (`:3003`) under +each product, or `GET /api/items?productId=`. + +## Env +| Var | Default | Notes | +|-----|---------|-------| +| `PLATFORM_API_URL` | `http://localhost:4003` | platform-service base URL | +| `JWT_SECRET` | — | required (unless `--dry-run`); must match platform-service | +| `SEED_SUB` | `eng-review-bot` | token subject → `reportedBy` | +| `SEED_ROLE` | `admin` | token role claim | +| `ITEMS_FILE` | `engineering-review-items.json` | payload file | + +Source of the items: `../../ENGINEERING_REVIEW_SCORECARD.md`. diff --git a/scripts/tracker-seed/engineering-review-items.json b/scripts/tracker-seed/engineering-review-items.json new file mode 100644 index 0000000..49dbd05 --- /dev/null +++ b/scripts/tracker-seed/engineering-review-items.json @@ -0,0 +1,189 @@ +{ + "_meta": { + "source": "ENGINEERING_REVIEW_SCORECARD.md (workspace review 2026-05-30)", + "note": "One tracker item per affected product/area, derived from the P0-P3 action plan. productId values use the ByteLyst ecosystem slugs; standalone repos use a descriptive slug. Seed with seed-tracker-items.mjs.", + "repoToProductId": { + "learning_ai_common_plat / ecosystem infra": "platform", + "learning_ai_devops_tools": "platform", + "learning_voice_ai_agent": "lysnrai", + "learning_multimodal_memory_agents": "mindlyst", + "learning_ai_clock": "chronomind", + "learning_ai_jarvis_jr": "jarvisjr", + "learning_ai_fastgap": "nomgap", + "learning_ai_peakpulse": "peakpulse", + "learning_ai_flowmonk": "flowmonk", + "learning_ai_notes": "notelett", + "learning_ai_trails": "actiontrail", + "learning_ai_local_memory_gpt": "localmemgpt", + "learning_ai_efforise": "efforise", + "learning_ai_2nd_brain": "secondbrain", + "learning_ai_mac_tooling": "mactooling", + "learning_ai_productivity_web": "productivityweb", + "learning_ai_webui_copilot": "webuicopilot", + "learning_agent_monitoring_fx": "agentmonitoring", + "learning_ai_magic_clipboard_mgr": "magicclipboard" + } + }, + "items": [ + { + "productId": "platform", + "type": "task", + "priority": "critical", + "title": "[P0] Restore a working CI gate on learning_ai_common_plat", + "description": "GitHub Actions is disabled (billing) on the platform monorepo that every product depends on; gitea CI exists but isn't an enforced gate. Re-enable GH Actions or make gitea CI the mandatory pre-merge gate. Ref: ENGINEERING_REVIEW_SCORECARD.md P0#1.", + "labels": ["eng-review-2026-05", "p0", "ci", "platform"], + "source": "internal", + "visibility": "internal" + }, + { + "productId": "platform", + "type": "task", + "priority": "critical", + "title": "[P0] Resolve ~14 dirty repos and add git upstreams", + "description": "About 14 repos had uncommitted work / were behind origin at review time. Review+commit or discard intentionally, and add upstreams for learning_pytorch_todo_predictor and learning_sidecar_setup (currently no remote). Ref: scorecard P0#2.", + "labels": ["eng-review-2026-05", "p0", "hygiene", "git"], + "source": "internal", + "visibility": "internal" + }, + { + "productId": "platform", + "type": "task", + "priority": "high", + "title": "[P0] Define agent-queue daemon run policy (stop writing to live trees)", + "description": "The agent-queue daemon + devin agents were running in --permission-mode dangerous and writing directly to live working trees, causing constant dirtiness and duplicate work landing upstream. Define a least-privilege, branch-per-task policy. Ref: scorecard P0#3 + SOP §8.", + "labels": ["eng-review-2026-05", "p0", "agents", "devops"], + "source": "internal", + "visibility": "internal" + }, + { + "productId": "platform", + "type": "task", + "priority": "high", + "title": "[P1] Add healthchecks + ordered depends_on to docker-compose.ecosystem.yml", + "description": "The ecosystem compose orchestrates ~20 services with 30 restart policies and 24 build contexts but 0 healthcheck blocks. Add healthcheck to each backend/web service and depends_on: condition: service_healthy. Ref: scorecard D + P1#4.", + "labels": ["eng-review-2026-05", "p1", "devops", "docker"], + "source": "internal", + "visibility": "internal" + }, + { + "productId": "platform", + "type": "bug", + "priority": "high", + "title": "[P2] Remove NODE_TLS_REJECT_UNAUTHORIZED=0 and add rate-limiting to prototype APIs", + "description": "TLS verification is disabled in some Docker setups; thin/zero input validation and no rate limiting in the Python prototype apps (webui_copilot, mac_tooling). Remove the TLS bypass where a real CA/host override exists and add rate limits. Ref: scorecard F + P2#10.", + "labels": ["eng-review-2026-05", "p2", "security"], + "source": "internal", + "visibility": "internal" + }, + { + "productId": "platform", + "type": "task", + "priority": "low", + "title": "[P3] Portfolio-wide coverage + dependency-audit in CI", + "description": "Add coverage reporting and npm audit / pip-audit / cargo audit steps across the portfolio CI. Ref: scorecard P3#11.", + "labels": ["eng-review-2026-05", "p3", "ci"], + "source": "internal", + "visibility": "internal" + }, + { + "productId": "platform", + "type": "task", + "priority": "low", + "title": "[P3] Lightweight release/issue cadence for the 3 flagships (notelett, actiontrail, chronomind)", + "description": "Pick the production-grade flagships and drive a real launch checklist + release cadence. Ref: scorecard G + P3#12.", + "labels": ["eng-review-2026-05", "p3", "process"], + "source": "internal", + "visibility": "internal" + }, + { + "productId": "nomgap", + "type": "task", + "priority": "high", + "title": "[P1] Stabilize E2E and remove continue-on-error (fastgap/nomgap)", + "description": "Playwright E2E job is marked continue-on-error: true, so it isn't actually gating. Stabilize smoke E2E, then make it blocking. Ref: scorecard E + P1#5.", + "labels": ["eng-review-2026-05", "p1", "testing", "e2e"], + "source": "internal", + "visibility": "internal" + }, + { + "productId": "flowmonk", + "type": "task", + "priority": "high", + "title": "[P1] Stabilize E2E and remove continue-on-error (flowmonk)", + "description": "Playwright E2E is non-gating (continue-on-error: true) and visual regression is excluded on Linux CI. Stabilize and make smoke E2E blocking. Ref: scorecard E + P1#5.", + "labels": ["eng-review-2026-05", "p1", "testing", "e2e"], + "source": "internal", + "visibility": "internal" + }, + { + "productId": "jarvisjr", + "type": "task", + "priority": "high", + "title": "[P1] Stabilize E2E and finish native surfaces (jarvisjr)", + "description": "Playwright E2E is non-gating; watchOS/macOS in progress and Android (Phase 4) not started. Stabilize E2E and close out the 5 visible TODOs / native wiring. Ref: scorecard E/G.", + "labels": ["eng-review-2026-05", "p1", "testing", "native"], + "source": "internal", + "visibility": "internal" + }, + { + "productId": "secondbrain", + "type": "task", + "priority": "medium", + "title": "[P1] Replace 60+ print() calls with logging (learning_ai_2nd_brain)", + "description": "CLI/intelligence modules use 60+ print() statements (violates the no-print rule). Switch to typer.echo/logging; keep behavior identical; run pytest. Ref: scorecard B + P1#6.", + "labels": ["eng-review-2026-05", "p1", "code-quality"], + "source": "internal", + "visibility": "internal" + }, + { + "productId": "mactooling", + "type": "task", + "priority": "medium", + "title": "[P2] Add tests, split 3k-line files, replace 200+ print() (learning_ai_mac_tooling)", + "description": "Forensics toolkit has 0 tests, 200+ print() calls, and 3k+-line files (network_transfer_audit.py 3521, api_server.py 3116). Add a smoke test suite, split the large files, and move to logging. Ref: scorecard B/E + P2#9.", + "labels": ["eng-review-2026-05", "p2", "code-quality", "testing"], + "source": "internal", + "visibility": "internal" + }, + { + "productId": "productivityweb", + "type": "task", + "priority": "medium", + "title": "[P2] Add a test suite (learning_ai_productivity_web has 0 tests)", + "description": "Clean client-only Next.js tool app with 0 tests and no CI. Add unit tests for the tools registry + a smoke test, and a CI job. Ref: scorecard E + P2#7.", + "labels": ["eng-review-2026-05", "p2", "testing"], + "source": "internal", + "visibility": "internal" + }, + { + "productId": "webuicopilot", + "type": "task", + "priority": "medium", + "title": "[P2] Add tests + CI + Docker (learning_ai_webui_copilot)", + "description": "FastAPI + LangChain app with 0 tests, no CI, no Docker. Add pytest smoke tests for the rules/policy engines and a copilot happy-path, plus a CI job and Dockerfile. Ref: scorecard E/D + P2#7.", + "labels": ["eng-review-2026-05", "p2", "testing", "devops"], + "source": "internal", + "visibility": "internal" + }, + { + "productId": "agentmonitoring", + "type": "task", + "priority": "medium", + "title": "[P2] Add CI, cut console.log, finish native TODOs (learning_agent_monitoring_fx)", + "description": "No CI; 54 console.log matches; 5 TODOs for native (KMP/iOS/Android) wiring; no Docker. Add CI, reduce logging noise, and close the native TODOs. Ref: scorecard B/D/G.", + "labels": ["eng-review-2026-05", "p2", "code-quality", "devops"], + "source": "internal", + "visibility": "internal" + }, + { + "productId": "magicclipboard", + "type": "task", + "priority": "medium", + "title": "[P2] Audit 50+ services for dead/stubbed code (learning_ai_magic_clipboard_mgr)", + "description": "50+ service files with phase-named test buckets (Phase5-8, RemainingQATests) suggest AI-scaffold smell. Produce a report of wired vs stubbed services and consolidate. Ref: scorecard B + P2#8.", + "labels": ["eng-review-2026-05", "p2", "code-quality", "audit"], + "source": "internal", + "visibility": "internal" + } + ] +} diff --git a/scripts/tracker-seed/seed-tracker-items.mjs b/scripts/tracker-seed/seed-tracker-items.mjs new file mode 100644 index 0000000..2b7da26 --- /dev/null +++ b/scripts/tracker-seed/seed-tracker-items.mjs @@ -0,0 +1,138 @@ +#!/usr/bin/env node +/** + * Seed ByteLyst tracker items from a JSON payload file. + * + * Creates feature/bug/task items via the platform-service tracker API + * (POST /api/items — the same endpoint tracker-web proxies to). Items are + * scoped per `productId`. Use this to file the ENGINEERING_REVIEW_SCORECARD.md + * P0-P3 work once the platform stack is running. + * + * Auth: mints a short-lived HS256 access token signed with $JWT_SECRET + * (same secret platform-service verifies with). No external deps — uses + * node:crypto only. + * + * Env: + * PLATFORM_API_URL Base URL of platform-service (default http://localhost:4003) + * JWT_SECRET Shared JWT secret (required unless --dry-run) + * SEED_SUB Token subject / reportedBy (default "eng-review-bot") + * SEED_EMAIL Token email claim (default "eng-review-bot@bytelyst.local") + * SEED_ROLE Token role claim (default "admin") + * ITEMS_FILE Payload file (default ./engineering-review-items.json) + * + * Flags: + * --dry-run Print what would be created; no token, no network calls. + * --force Skip the dedupe-by-title check. + * + * Examples: + * node seed-tracker-items.mjs --dry-run + * JWT_SECRET=... PLATFORM_API_URL=http://localhost:4003 node seed-tracker-items.mjs + */ + +import { readFileSync } from 'node:fs'; +import { createHmac } from 'node:crypto'; +import { fileURLToPath } from 'node:url'; +import { dirname, resolve } from 'node:path'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); + +const args = new Set(process.argv.slice(2)); +const DRY_RUN = args.has('--dry-run'); +const FORCE = args.has('--force'); + +const API = (process.env.PLATFORM_API_URL || 'http://localhost:4003').replace(/\/$/, ''); +const SECRET = process.env.JWT_SECRET || ''; +const SUB = process.env.SEED_SUB || 'eng-review-bot'; +const EMAIL = process.env.SEED_EMAIL || 'eng-review-bot@bytelyst.local'; +const ROLE = process.env.SEED_ROLE || 'admin'; +const ITEMS_FILE = resolve(__dirname, process.env.ITEMS_FILE || 'engineering-review-items.json'); + +function b64url(buf) { + return Buffer.from(buf).toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''); +} + +/** Mint an HS256 access token compatible with @bytelyst/auth extractAuth(). */ +function mintToken() { + if (!SECRET) throw new Error('JWT_SECRET must be set (or use --dry-run)'); + const now = Math.floor(Date.now() / 1000); + const header = { alg: 'HS256', typ: 'JWT' }; + const payload = { sub: SUB, email: EMAIL, role: ROLE, type: 'access', iat: now, exp: now + 3600 }; + const head = b64url(JSON.stringify(header)); + const body = b64url(JSON.stringify(payload)); + const sig = b64url(createHmac('sha256', SECRET).update(`${head}.${body}`).digest()); + return `${head}.${body}.${sig}`; +} + +async function listTitles(token, productId) { + const url = `${API}/api/items?productId=${encodeURIComponent(productId)}&limit=500`; + const res = await fetch(url, { + headers: { authorization: `Bearer ${token}`, 'x-product-id': productId }, + }); + if (!res.ok) throw new Error(`list failed (${res.status})`); + const data = await res.json(); + return new Set((data.items || []).map((i) => i.title)); +} + +async function createItem(token, item) { + const res = await fetch(`${API}/api/items`, { + method: 'POST', + headers: { + authorization: `Bearer ${token}`, + 'content-type': 'application/json', + 'x-product-id': item.productId, + }, + body: JSON.stringify(item), + }); + const text = await res.text(); + if (!res.ok) throw new Error(`${res.status} ${text.slice(0, 300)}`); + return JSON.parse(text); +} + +async function main() { + const raw = JSON.parse(readFileSync(ITEMS_FILE, 'utf8')); + const items = raw.items || raw; + console.log(`Loaded ${items.length} item(s) from ${ITEMS_FILE}`); + console.log(`Target: ${API}/api/items (dry-run=${DRY_RUN}, force=${FORCE})\n`); + + if (DRY_RUN) { + for (const it of items) { + console.log(` [DRY] ${it.productId.padEnd(16)} ${it.type}/${it.priority} ${it.title}`); + } + console.log(`\nDry run only — nothing sent. ${items.length} item(s) would be created.`); + return; + } + + const token = mintToken(); + const titleCache = new Map(); + let created = 0; + let skipped = 0; + let failed = 0; + + for (const it of items) { + try { + if (!FORCE) { + if (!titleCache.has(it.productId)) { + titleCache.set(it.productId, await listTitles(token, it.productId).catch(() => new Set())); + } + if (titleCache.get(it.productId).has(it.title)) { + console.log(` SKIP ${it.productId.padEnd(16)} ${it.title} (already exists)`); + skipped++; + continue; + } + } + const res = await createItem(token, it); + console.log(` OK ${it.productId.padEnd(16)} ${res.id} ${it.title}`); + created++; + } catch (err) { + console.error(` FAIL ${it.productId.padEnd(16)} ${it.title}\n ${err.message}`); + failed++; + } + } + + console.log(`\nDone. created=${created} skipped=${skipped} failed=${failed}`); + if (failed > 0) process.exit(1); +} + +main().catch((err) => { + console.error(`Fatal: ${err.message}`); + process.exit(1); +});