Files
daily-notification-plugin/scripts/todo-scan.js
Matthew Raymer cdbe51f46a feat(docs): add production readiness runbook and enhanced TODO scan
Add comprehensive production readiness checklist and improve TODO scanning.

Changes:
- Production Readiness Runbook (docs/progress/PRODUCTION-READINESS-RUNBOOK.md)
  - Complete mechanical execution checklist for TypeScript, Android, iOS
  - File anchors and search commands for verification
  - Cross-platform behavior consistency checks
  - Logging and observability requirements
  - Release packaging sanity checks
  - Troubleshooting guide
  - Quick reference for expected file anchors
- Enhanced TODO Scan Script (scripts/todo-scan.js)
  - Split reporting: core code vs docs/test-apps
  - Core code count (should be 0)
  - Docs/test-apps count (expected to be large)
  - Enhanced JSON output with summary statistics
  - Improved console output with visual indicators
  - Clear separation of production code vs planning artifacts

Implementation Details:
- Core code detection: ios/Plugin/, android/src/main/, src/, packages/, lib/
- Docs/test-apps detection: docs/, test-apps/, tests/, *Tests/
- JSON output includes summary with coreCount, docsTestCount, otherCount
- Markdown output includes summary section with split counts
- Console output shows visual indicators (/⚠️) for quick assessment

Benefits:
- Clear visibility into production code TODOs (should be 0)
- Acceptable TODOs in docs/test-apps are clearly separated
- Production readiness checklist provides deterministic verification
- File anchors enable quick verification of implementation completeness

Verification:
- TODO scan runs successfully
- JSON output includes summary statistics
- Markdown output includes split summary
- Console output shows visual indicators
2025-12-24 08:19:48 +00:00

210 lines
5.9 KiB
JavaScript
Executable File

#!/usr/bin/env node
/**
* Scans repo for TODO/FIXME markers and emits:
* - machine-readable JSON
* - human-readable markdown summary
*
* Output:
* - docs/TODO-CLASSIFICATION.md (overwritten)
* - docs/todo-scan.json
*
* Note: This script itself may contain "TODO" or "FIXME" in comments or strings.
* These are intentional and should be excluded from scan results.
*
* @author Matthew Raymer
* @version 1.0.0
*/
const fs = require("fs");
const path = require("path");
const ROOT = process.cwd();
const TARGET_DIRS = [
"src",
"ios/Plugin",
"ios/Tests",
"android/src/main",
"android/src/test",
"scripts",
"docs",
];
const EXCLUDE_DIR_NAMES = new Set([
".git",
"node_modules",
"dist",
"build",
".venv",
"venv",
"__pycache__",
]);
const FILE_EXTS = new Set([
".ts", ".tsx", ".js", ".jsx",
".swift",
".java", ".kt",
".md",
".json", ".yml", ".yaml",
".py",
]);
const MARKERS = ["TODO", "FIXME"];
function walk(dir, out = []) {
if (!fs.existsSync(dir)) return out;
const st = fs.statSync(dir);
if (!st.isDirectory()) return out;
for (const name of fs.readdirSync(dir)) {
if (EXCLUDE_DIR_NAMES.has(name)) continue;
const p = path.join(dir, name);
const s = fs.statSync(p);
if (s.isDirectory()) walk(p, out);
else out.push(p);
}
return out;
}
function scanFile(fp) {
const ext = path.extname(fp);
if (!FILE_EXTS.has(ext)) return [];
const text = fs.readFileSync(fp, "utf8");
const lines = text.split(/\r?\n/);
const hits = [];
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
for (const m of MARKERS) {
// require marker as a token-ish substring
if (line.includes(m + ":") || line.includes(m + " ")) {
hits.push({ line: i + 1, marker: m, text: line.trim() });
break;
}
}
}
return hits;
}
function bucketForPath(rel) {
if (rel.startsWith("ios/")) return "iOS";
if (rel.startsWith("android/")) return "Android";
if (rel.startsWith("src/")) return "TypeScript";
if (rel.startsWith("docs/")) return "Docs";
if (rel.startsWith("scripts/")) return "Scripts";
return "Other";
}
function isCoreCode(rel) {
// Core code directories (production code)
return (
rel.startsWith("ios/Plugin/") ||
rel.startsWith("android/src/main/") ||
rel.startsWith("src/") ||
rel.startsWith("packages/") ||
rel.startsWith("lib/") ||
(rel.startsWith("scripts/") && !rel.includes("test"))
);
}
function isDocsOrTestApp(rel) {
// Documentation and test harness directories
return (
rel.startsWith("docs/") ||
rel.startsWith("test-apps/") ||
rel.startsWith("ios/Tests/") ||
rel.startsWith("android/src/test/") ||
rel.startsWith("tests/")
);
}
function main() {
const results = [];
for (const d of TARGET_DIRS) {
const dirAbs = path.join(ROOT, d);
const files = walk(dirAbs);
for (const fpAbs of files) {
const rel = path.relative(ROOT, fpAbs).replace(/\\/g, "/");
const hits = scanFile(fpAbs);
for (const h of hits) results.push({ file: rel, ...h, bucket: bucketForPath(rel) });
}
}
// sort stable: bucket, file, line
results.sort((a, b) =>
a.bucket.localeCompare(b.bucket) ||
a.file.localeCompare(b.file) ||
a.line - b.line
);
// Split core vs docs/test-apps
const coreResults = results.filter(r => isCoreCode(r.file));
const docsTestResults = results.filter(r => isDocsOrTestApp(r.file));
const otherResults = results.filter(r => !isCoreCode(r.file) && !isDocsOrTestApp(r.file));
// Enhanced JSON output with split counts
const jsonOutput = {
summary: {
total: results.length,
coreCount: coreResults.length,
docsTestCount: docsTestResults.length,
otherCount: otherResults.length,
generatedAt: new Date().toISOString()
},
core: coreResults,
docsTest: docsTestResults,
other: otherResults,
all: results
};
fs.writeFileSync(path.join(ROOT, "docs/todo-scan.json"), JSON.stringify(jsonOutput, null, 2), "utf8");
// markdown
const byBucket = new Map();
for (const r of results) {
if (!byBucket.has(r.bucket)) byBucket.set(r.bucket, []);
byBucket.get(r.bucket).push(r);
}
let md = "";
md += `# TODO Classification (auto-generated)\n\n`;
md += `Generated by \`scripts/todo-scan.js\`\n\n`;
md += `## Summary\n\n`;
md += `- **Total markers:** ${results.length}\n`;
md += `- **Core code (production):** ${coreResults.length} ⚠️\n`;
md += `- **Docs/test-apps:** ${docsTestResults.length} ✅ (expected)\n`;
md += `- **Other:** ${otherResults.length}\n\n`;
md += `> **Note:** Core code TODOs should be near zero. Docs/test-app TODOs are expected and acceptable.\n\n`;
md += `---\n\n`;
for (const [bucket, items] of byBucket.entries()) {
md += `## ${bucket} (${items.length})\n\n`;
// group by file
const byFile = new Map();
for (const it of items) {
if (!byFile.has(it.file)) byFile.set(it.file, []);
byFile.get(it.file).push(it);
}
for (const [file, hits] of byFile.entries()) {
md += `### ${file}\n\n`;
for (const h of hits) {
md += `- L${h.line}: **${h.marker}** — ${h.text}\n`;
}
md += `\n`;
}
}
fs.writeFileSync(path.join(ROOT, "docs/TODO-CLASSIFICATION.md"), md, "utf8");
// Console output with split summary
console.log(`\n📊 TODO Scan Complete`);
console.log(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`);
console.log(`Total markers: ${results.length}`);
console.log(`Core code: ${coreResults.length} ${coreResults.length === 0 ? '✅' : '⚠️ (should be 0)'}`);
console.log(`Docs/test-apps: ${docsTestResults.length} ✅ (expected)`);
console.log(`Other: ${otherResults.length}`);
console.log(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`);
}
main();