Aryan Mishra
feat: initial commit - Multilingual ABSA project setup with 6 phases, 36 requirements
a6b96c2
Raw
History Blame Contribute Delete
31.3 kB
"use strict";
/**
* Open Artifact Audit β€” Cross-type unresolved state scanner
*
* Scans all .planning/ artifact categories for items with open/unresolved state.
* Returns structured JSON for workflow consumption.
* Called by: gsd-tools.cjs audit-open
* Used by: /gsd:complete-milestone pre-close gate
*
* ADR-457 build-at-publish: the hand-written bin/lib/audit.cjs collapsed
* to a TypeScript source of truth. Behaviour is preserved byte-for-behaviour
* from the prior hand-written .cjs; only strict types are added.
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
const node_fs_1 = __importDefault(require("node:fs"));
const node_path_1 = __importDefault(require("node:path"));
const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
// eslint-disable-next-line @typescript-eslint/no-require-imports
const planningWorkspace = require("./planning-workspace.cjs");
const { planningDir } = planningWorkspace;
// eslint-disable-next-line @typescript-eslint/no-require-imports
const frontmatter = require("./frontmatter.cjs");
const { extractFrontmatter } = frontmatter;
const security_cjs_1 = require("./security.cjs");
// Terminal UAT states: `complete` (legacy) and `resolved` (post-gap-closure
// per workflows/execute-phase.md). Hoisted outside scanUatGaps so the Set is
// not recreated on each loop iteration.
const TERMINAL_UAT_STATUSES = new Set(['complete', 'resolved']);
// ─── scanDebugSessions ────────────────────────────────────────────────────────
/**
* Scan .planning/debug/ for open sessions.
* Open = status NOT in ['resolved', 'complete'].
* Ignores the resolved/ subdirectory.
*/
function scanDebugSessions(planDir) {
const debugDir = node_path_1.default.join(planDir, 'debug');
if (!node_fs_1.default.existsSync(debugDir))
return [];
const results = [];
let files;
try {
files = node_fs_1.default.readdirSync(debugDir, { withFileTypes: true });
}
catch {
return [{ scan_error: true, slug: '', status: '', updated: '', hypothesis: '' }];
}
for (const entry of files) {
if (!entry.isFile())
continue;
if (!entry.name.endsWith('.md'))
continue;
const filePath = node_path_1.default.join(debugDir, entry.name);
let safeFilePath;
try {
safeFilePath = (0, security_cjs_1.requireSafePath)(filePath, planDir, 'debug session file', { allowAbsolute: true });
}
catch {
continue;
}
const content = (0, shell_command_projection_cjs_1.platformReadSync)(safeFilePath);
if (content === null)
continue;
const fm = extractFrontmatter(content);
const status = (fm.status || 'unknown').toLowerCase();
if (status === 'resolved' || status === 'complete')
continue;
// Extract hypothesis from "Current Focus" block if parseable
let hypothesis = '';
const focusMatch = content.match(/##\s*Current Focus[^\n]*\n([\s\S]*?)(?=\n##\s|$)/i);
if (focusMatch) {
const focusText = focusMatch[1].trim().split('\n')[0].trim();
hypothesis = (0, security_cjs_1.sanitizeForDisplay)(focusText.slice(0, 100));
}
const slug = node_path_1.default.basename(entry.name, '.md');
results.push({
slug: (0, security_cjs_1.sanitizeForDisplay)(slug),
status: (0, security_cjs_1.sanitizeForDisplay)(status),
updated: (0, security_cjs_1.sanitizeForDisplay)(fm.updated || fm.date || ''),
hypothesis,
});
}
return results;
}
// ─── scanQuickTasks ───────────────────────────────────────────────────────────
/**
* Scan .planning/quick/ for incomplete tasks.
* Incomplete if SUMMARY.md missing or status !== 'complete'.
*/
function scanQuickTasks(planDir) {
const quickDir = node_path_1.default.join(planDir, 'quick');
if (!node_fs_1.default.existsSync(quickDir))
return [];
let entries;
try {
entries = node_fs_1.default.readdirSync(quickDir, { withFileTypes: true });
}
catch {
return [{ scan_error: true, slug: '', date: '', status: '', description: '' }];
}
const results = [];
for (const entry of entries) {
if (!entry.isDirectory())
continue;
const dirName = entry.name;
const taskDir = node_path_1.default.join(quickDir, dirName);
let safeTaskDir;
try {
safeTaskDir = (0, security_cjs_1.requireSafePath)(taskDir, planDir, 'quick task dir', { allowAbsolute: true });
}
catch {
continue;
}
// workflows/quick.md mandates `${quick_id}-SUMMARY.md`; older flows used
// bare `SUMMARY.md`. Accept either to avoid false-positive "missing".
let summaryPath = null;
try {
const summaryFiles = node_fs_1.default.readdirSync(safeTaskDir, { withFileTypes: true })
.filter(e => e.isFile() && (e.name === 'SUMMARY.md' || e.name.endsWith('-SUMMARY.md')));
if (summaryFiles.length > 0) {
// Prefer the per-task `${quick_id}-SUMMARY.md` form when present.
const preferred = summaryFiles.find(e => e.name === `${dirName}-SUMMARY.md`)
|| summaryFiles.find(e => e.name.endsWith('-SUMMARY.md'))
|| summaryFiles[0];
summaryPath = node_path_1.default.join(safeTaskDir, preferred.name);
}
}
catch {
// fall through with summaryPath = null β†’ status: missing
}
let status = 'missing';
const description = '';
if (summaryPath && node_fs_1.default.existsSync(summaryPath)) {
let safeSum;
try {
safeSum = (0, security_cjs_1.requireSafePath)(summaryPath, planDir, 'quick task summary', { allowAbsolute: true });
}
catch {
continue;
}
const content = (0, shell_command_projection_cjs_1.platformReadSync)(safeSum);
if (content === null) {
status = 'unreadable';
}
else {
const fm = extractFrontmatter(content);
status = (fm.status || 'unknown').toLowerCase();
}
}
if (status === 'complete')
continue;
// Parse date and slug from directory name: YYYYMMDD-slug or YYYY-MM-DD-slug
let date = '';
let slug = (0, security_cjs_1.sanitizeForDisplay)(dirName);
const dateMatch = dirName.match(/^(\d{4}-?\d{2}-?\d{2})-(.+)$/);
if (dateMatch) {
date = dateMatch[1];
slug = (0, security_cjs_1.sanitizeForDisplay)(dateMatch[2]);
}
results.push({
slug,
date,
status: (0, security_cjs_1.sanitizeForDisplay)(status),
description,
});
}
return results;
}
// ─── scanThreads ──────────────────────────────────────────────────────────────
/**
* Scan .planning/threads/ for open threads.
* Open if status in ['open', 'in_progress', 'in progress'] (case-insensitive).
*/
function scanThreads(planDir) {
const threadsDir = node_path_1.default.join(planDir, 'threads');
if (!node_fs_1.default.existsSync(threadsDir))
return [];
let files;
try {
files = node_fs_1.default.readdirSync(threadsDir, { withFileTypes: true });
}
catch {
return [{ scan_error: true, slug: '', status: '', updated: '', title: '' }];
}
const openStatuses = new Set(['open', 'in_progress', 'in progress']);
const results = [];
for (const entry of files) {
if (!entry.isFile())
continue;
if (!entry.name.endsWith('.md'))
continue;
const filePath = node_path_1.default.join(threadsDir, entry.name);
let safeFilePath;
try {
safeFilePath = (0, security_cjs_1.requireSafePath)(filePath, planDir, 'thread file', { allowAbsolute: true });
}
catch {
continue;
}
const content = (0, shell_command_projection_cjs_1.platformReadSync)(safeFilePath);
if (content === null)
continue;
const fm = extractFrontmatter(content);
let status = (fm.status || '').toLowerCase().trim();
// Fall back to scanning body for ## Status: OPEN / IN PROGRESS
if (!status) {
const bodyStatusMatch = content.match(/##\s*Status:\s*(OPEN|IN PROGRESS|IN_PROGRESS)/i);
if (bodyStatusMatch) {
status = bodyStatusMatch[1].toLowerCase().replace(/ /g, '_');
}
}
if (!openStatuses.has(status))
continue;
// Extract title from # Thread: heading or frontmatter title
let title = (0, security_cjs_1.sanitizeForDisplay)(fm.title || '');
if (!title) {
const headingMatch = content.match(/^#\s*Thread:\s*(.+)$/m);
if (headingMatch) {
title = (0, security_cjs_1.sanitizeForDisplay)(headingMatch[1].trim().slice(0, 100));
}
}
const slug = node_path_1.default.basename(entry.name, '.md');
results.push({
slug: (0, security_cjs_1.sanitizeForDisplay)(slug),
status: (0, security_cjs_1.sanitizeForDisplay)(status),
updated: (0, security_cjs_1.sanitizeForDisplay)(fm.updated || fm.date || ''),
title,
});
}
return results;
}
// ─── scanTodos ────────────────────────────────────────────────────────────────
/**
* Scan .planning/todos/pending/ for pending todos.
* Returns array of { filename, priority, area, summary }.
* Display limited to first 5 + count of remainder.
*/
function scanTodos(planDir) {
const pendingDir = node_path_1.default.join(planDir, 'todos', 'pending');
if (!node_fs_1.default.existsSync(pendingDir))
return [];
let files;
try {
files = node_fs_1.default.readdirSync(pendingDir, { withFileTypes: true });
}
catch {
return [{ scan_error: true, filename: '', priority: '', area: '', summary: '' }];
}
const mdFiles = files.filter(e => e.isFile() && e.name.endsWith('.md'));
const results = [];
const displayFiles = mdFiles.slice(0, 5);
for (const entry of displayFiles) {
const filePath = node_path_1.default.join(pendingDir, entry.name);
let safeFilePath;
try {
safeFilePath = (0, security_cjs_1.requireSafePath)(filePath, planDir, 'todo file', { allowAbsolute: true });
}
catch {
continue;
}
const content = (0, shell_command_projection_cjs_1.platformReadSync)(safeFilePath);
if (content === null)
continue;
const fm = extractFrontmatter(content);
// Extract first line of body after frontmatter
const bodyMatch = content.replace(/^---[\s\S]*?---\n?/, '');
const firstLine = bodyMatch.trim().split('\n')[0] || '';
const summary = (0, security_cjs_1.sanitizeForDisplay)(firstLine.slice(0, 100));
results.push({
filename: (0, security_cjs_1.sanitizeForDisplay)(entry.name),
priority: (0, security_cjs_1.sanitizeForDisplay)(fm.priority || ''),
area: (0, security_cjs_1.sanitizeForDisplay)(fm.area || ''),
summary,
});
}
if (mdFiles.length > 5) {
results.push({ _remainder_count: mdFiles.length - 5, filename: '', priority: '', area: '', summary: '' });
}
return results;
}
// ─── scanSeeds ────────────────────────────────────────────────────────────────
/**
* Scan .planning/seeds/SEED-*.md for unimplemented seeds.
* Unimplemented if status in ['dormant', 'active', 'triggered'].
*/
function scanSeeds(planDir) {
const seedsDir = node_path_1.default.join(planDir, 'seeds');
if (!node_fs_1.default.existsSync(seedsDir))
return [];
let files;
try {
files = node_fs_1.default.readdirSync(seedsDir, { withFileTypes: true });
}
catch {
return [{ scan_error: true, seed_id: '', slug: '', status: '', title: '' }];
}
const unimplementedStatuses = new Set(['dormant', 'active', 'triggered']);
const results = [];
for (const entry of files) {
if (!entry.isFile())
continue;
if (!entry.name.startsWith('SEED-') || !entry.name.endsWith('.md'))
continue;
const filePath = node_path_1.default.join(seedsDir, entry.name);
let safeFilePath;
try {
safeFilePath = (0, security_cjs_1.requireSafePath)(filePath, planDir, 'seed file', { allowAbsolute: true });
}
catch {
continue;
}
const content = (0, shell_command_projection_cjs_1.platformReadSync)(safeFilePath);
if (content === null)
continue;
const fm = extractFrontmatter(content);
const status = (fm.status || 'dormant').toLowerCase();
if (!unimplementedStatuses.has(status))
continue;
// Extract seed_id from filename or frontmatter
const seedIdMatch = entry.name.match(/^(SEED-[\w-]+)\.md$/);
const seed_id = seedIdMatch ? seedIdMatch[1] : node_path_1.default.basename(entry.name, '.md');
const slug = (0, security_cjs_1.sanitizeForDisplay)(seed_id.replace(/^SEED-/, ''));
let title = (0, security_cjs_1.sanitizeForDisplay)(fm.title || '');
if (!title) {
const headingMatch = content.match(/^#\s*(.+)$/m);
if (headingMatch)
title = (0, security_cjs_1.sanitizeForDisplay)(headingMatch[1].trim().slice(0, 100));
}
results.push({
seed_id: (0, security_cjs_1.sanitizeForDisplay)(seed_id),
slug,
status: (0, security_cjs_1.sanitizeForDisplay)(status),
title,
});
}
return results;
}
// ─── scanUatGaps ──────────────────────────────────────────────────────────────
/**
* Scan .planning/phases for UAT gaps (UAT files with status != 'complete').
*/
function scanUatGaps(planDir) {
const phasesDir = node_path_1.default.join(planDir, 'phases');
if (!node_fs_1.default.existsSync(phasesDir))
return [];
let dirs;
try {
dirs = node_fs_1.default.readdirSync(phasesDir, { withFileTypes: true })
.filter(e => e.isDirectory())
.map(e => e.name)
.sort();
}
catch {
return [{ scan_error: true, phase: '', file: '', status: '', open_scenario_count: 0 }];
}
const results = [];
for (const dir of dirs) {
const phaseDir = node_path_1.default.join(phasesDir, dir);
const phaseMatch = dir.match(/^(\d+[A-Z]?(?:\.\d+)*)/i);
const phaseNum = phaseMatch ? phaseMatch[1] : dir;
let files;
try {
files = node_fs_1.default.readdirSync(phaseDir);
}
catch {
continue;
}
for (const file of files.filter(f => f.includes('-UAT') && f.endsWith('.md'))) {
const filePath = node_path_1.default.join(phaseDir, file);
let safeFilePath;
try {
safeFilePath = (0, security_cjs_1.requireSafePath)(filePath, planDir, 'UAT file', { allowAbsolute: true });
}
catch {
continue;
}
const content = (0, shell_command_projection_cjs_1.platformReadSync)(safeFilePath);
if (content === null)
continue;
const fm = extractFrontmatter(content);
const status = (fm.status || 'unknown').toLowerCase();
const result = (fm.result || '').toLowerCase();
// Also accept `result: all_pass` as a fallback when status is absent
// β€” covers UATs that omit `status:`.
if (TERMINAL_UAT_STATUSES.has(status))
continue;
if (status === 'unknown' && result === 'all_pass')
continue;
// Count open scenarios
const pendingMatches = (content.match(/result:\s*(?:pending|\[pending\])/gi) || []).length;
results.push({
phase: (0, security_cjs_1.sanitizeForDisplay)(phaseNum),
file: (0, security_cjs_1.sanitizeForDisplay)(file),
status: (0, security_cjs_1.sanitizeForDisplay)(status),
open_scenario_count: pendingMatches,
});
}
}
return results;
}
// ─── scanVerificationGaps ─────────────────────────────────────────────────────
/**
* Scan .planning/phases for VERIFICATION gaps.
*/
function scanVerificationGaps(planDir) {
const phasesDir = node_path_1.default.join(planDir, 'phases');
if (!node_fs_1.default.existsSync(phasesDir))
return [];
let dirs;
try {
dirs = node_fs_1.default.readdirSync(phasesDir, { withFileTypes: true })
.filter(e => e.isDirectory())
.map(e => e.name)
.sort();
}
catch {
return [{ scan_error: true, phase: '', file: '', status: '' }];
}
const results = [];
for (const dir of dirs) {
const phaseDir = node_path_1.default.join(phasesDir, dir);
const phaseMatch = dir.match(/^(\d+[A-Z]?(?:\.\d+)*)/i);
const phaseNum = phaseMatch ? phaseMatch[1] : dir;
let files;
try {
files = node_fs_1.default.readdirSync(phaseDir);
}
catch {
continue;
}
for (const file of files.filter(f => f.includes('-VERIFICATION') && f.endsWith('.md'))) {
const filePath = node_path_1.default.join(phaseDir, file);
let safeFilePath;
try {
safeFilePath = (0, security_cjs_1.requireSafePath)(filePath, planDir, 'VERIFICATION file', { allowAbsolute: true });
}
catch {
continue;
}
const content = (0, shell_command_projection_cjs_1.platformReadSync)(safeFilePath);
if (content === null)
continue;
const fm = extractFrontmatter(content);
const status = (fm.status || 'unknown').toLowerCase();
if (status !== 'gaps_found' && status !== 'human_needed')
continue;
results.push({
phase: (0, security_cjs_1.sanitizeForDisplay)(phaseNum),
file: (0, security_cjs_1.sanitizeForDisplay)(file),
status: (0, security_cjs_1.sanitizeForDisplay)(status),
});
}
}
return results;
}
// ─── scanContextQuestions ─────────────────────────────────────────────────────
/**
* Scan .planning/phases for CONTEXT files with open_questions.
*/
function scanContextQuestions(planDir) {
const phasesDir = node_path_1.default.join(planDir, 'phases');
if (!node_fs_1.default.existsSync(phasesDir))
return [];
let dirs;
try {
dirs = node_fs_1.default.readdirSync(phasesDir, { withFileTypes: true })
.filter(e => e.isDirectory())
.map(e => e.name)
.sort();
}
catch {
return [{ scan_error: true, phase: '', file: '', question_count: 0, questions: [] }];
}
const results = [];
for (const dir of dirs) {
const phaseDir = node_path_1.default.join(phasesDir, dir);
const phaseMatch = dir.match(/^(\d+[A-Z]?(?:\.\d+)*)/i);
const phaseNum = phaseMatch ? phaseMatch[1] : dir;
let files;
try {
files = node_fs_1.default.readdirSync(phaseDir);
}
catch {
continue;
}
for (const file of files.filter(f => f.includes('-CONTEXT') && f.endsWith('.md'))) {
const filePath = node_path_1.default.join(phaseDir, file);
let safeFilePath;
try {
safeFilePath = (0, security_cjs_1.requireSafePath)(filePath, planDir, 'CONTEXT file', { allowAbsolute: true });
}
catch {
continue;
}
const content = (0, shell_command_projection_cjs_1.platformReadSync)(safeFilePath);
if (content === null)
continue;
const fm = extractFrontmatter(content);
// Check frontmatter open_questions field
let questions = [];
if (fm.open_questions) {
if (Array.isArray(fm.open_questions) && fm.open_questions.length > 0) {
questions = fm.open_questions.map(q => (0, security_cjs_1.sanitizeForDisplay)(String(q).slice(0, 200)));
}
}
// Also check for ## Open Questions section in body
if (questions.length === 0) {
const oqMatch = content.match(/##\s*Open Questions[^\n]*\n([\s\S]*?)(?=\n##\s|$)/i);
if (oqMatch) {
const oqBody = oqMatch[1].trim();
if (oqBody && oqBody.length > 0 && !/^\s*none\s*$/i.test(oqBody)) {
const items = oqBody.split('\n')
.map((l) => l.trim())
.filter((l) => l && l !== '-' && l !== '*')
.filter((l) => /^[-*\d]/.test(l) || l.includes('?'));
questions = items.slice(0, 3).map((q) => (0, security_cjs_1.sanitizeForDisplay)(q.slice(0, 200)));
}
}
}
if (questions.length === 0)
continue;
results.push({
phase: (0, security_cjs_1.sanitizeForDisplay)(phaseNum),
file: (0, security_cjs_1.sanitizeForDisplay)(file),
question_count: questions.length,
questions: questions.slice(0, 3),
});
}
}
return results;
}
// ─── auditOpenArtifacts ───────────────────────────────────────────────────────
/**
* Main audit function. Scans all .planning/ artifact categories.
*
* @param cwd - Project root directory
* @returns Structured audit result
*/
function auditOpenArtifacts(cwd) {
const planDir = planningDir(cwd);
const debugSessions = (() => {
try {
return scanDebugSessions(planDir);
}
catch {
return [{ scan_error: true, slug: '', status: '', updated: '', hypothesis: '' }];
}
})();
const quickTasks = (() => {
try {
return scanQuickTasks(planDir);
}
catch {
return [{ scan_error: true, slug: '', date: '', status: '', description: '' }];
}
})();
const threads = (() => {
try {
return scanThreads(planDir);
}
catch {
return [{ scan_error: true, slug: '', status: '', updated: '', title: '' }];
}
})();
const todos = (() => {
try {
return scanTodos(planDir);
}
catch {
return [{ scan_error: true, filename: '', priority: '', area: '', summary: '' }];
}
})();
const seeds = (() => {
try {
return scanSeeds(planDir);
}
catch {
return [{ scan_error: true, seed_id: '', slug: '', status: '', title: '' }];
}
})();
const uatGaps = (() => {
try {
return scanUatGaps(planDir);
}
catch {
return [{ scan_error: true, phase: '', file: '', status: '', open_scenario_count: 0 }];
}
})();
const verificationGaps = (() => {
try {
return scanVerificationGaps(planDir);
}
catch {
return [{ scan_error: true, phase: '', file: '', status: '' }];
}
})();
const contextQuestions = (() => {
try {
return scanContextQuestions(planDir);
}
catch {
return [{ scan_error: true, phase: '', file: '', question_count: 0, questions: [] }];
}
})();
// Count real items (not scan_error sentinels)
const countReal = (arr) => arr.filter(i => !i.scan_error && !i._remainder_count).length;
const counts = {
debug_sessions: countReal(debugSessions),
quick_tasks: countReal(quickTasks),
threads: countReal(threads),
todos: countReal(todos),
seeds: countReal(seeds),
uat_gaps: countReal(uatGaps),
verification_gaps: countReal(verificationGaps),
context_questions: countReal(contextQuestions),
total: 0,
};
counts.total = counts.debug_sessions + counts.quick_tasks + counts.threads + counts.todos + counts.seeds + counts.uat_gaps + counts.verification_gaps + counts.context_questions;
return {
scanned_at: new Date().toISOString(),
has_open_items: counts.total > 0,
counts,
items: {
debug_sessions: debugSessions,
quick_tasks: quickTasks,
threads,
todos,
seeds,
uat_gaps: uatGaps,
verification_gaps: verificationGaps,
context_questions: contextQuestions,
},
};
}
// ─── formatAuditReport ────────────────────────────────────────────────────────
/**
* Format the audit result as a human-readable report.
*
* @param auditResult - Result from auditOpenArtifacts()
* @returns Formatted report
*/
function formatAuditReport(auditResult) {
const { counts, items, has_open_items } = auditResult;
const lines = [];
const hr = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━';
lines.push(hr);
lines.push(' Milestone Close: Open Artifact Audit');
lines.push(hr);
if (!has_open_items) {
lines.push('');
lines.push(' All artifact types clear. Safe to proceed.');
lines.push('');
lines.push(hr);
return lines.join('\n');
}
// Debug sessions (blocking quality β€” red)
if (counts.debug_sessions > 0) {
lines.push('');
lines.push(`πŸ”΄ Debug Sessions (${counts.debug_sessions} open)`);
for (const item of items.debug_sessions.filter(i => !i.scan_error)) {
const hyp = item.hypothesis ? ` β€” ${item.hypothesis}` : '';
lines.push(` β€’ ${item.slug} [${item.status}]${hyp}`);
}
}
// UAT gaps (blocking quality β€” red)
if (counts.uat_gaps > 0) {
lines.push('');
lines.push(`πŸ”΄ UAT Gaps (${counts.uat_gaps} phases with incomplete UAT)`);
for (const item of items.uat_gaps.filter(i => !i.scan_error)) {
lines.push(` β€’ Phase ${item.phase}: ${item.file} [${item.status}] β€” ${item.open_scenario_count} pending scenarios`);
}
}
// Verification gaps (blocking quality β€” red)
if (counts.verification_gaps > 0) {
lines.push('');
lines.push(`πŸ”΄ Verification Gaps (${counts.verification_gaps} unresolved)`);
for (const item of items.verification_gaps.filter(i => !i.scan_error)) {
lines.push(` β€’ Phase ${item.phase}: ${item.file} [${item.status}]`);
}
}
// Quick tasks (incomplete work β€” yellow)
if (counts.quick_tasks > 0) {
lines.push('');
lines.push(`🟑 Quick Tasks (${counts.quick_tasks} incomplete)`);
for (const item of items.quick_tasks.filter(i => !i.scan_error)) {
const d = item.date ? ` (${item.date})` : '';
lines.push(` β€’ ${item.slug}${d} [${item.status}]`);
}
}
// Todos (incomplete work β€” yellow)
if (counts.todos > 0) {
const realTodos = items.todos.filter(i => !i.scan_error && !i._remainder_count);
const remainder = items.todos.find(i => i._remainder_count);
lines.push('');
lines.push(`🟑 Pending Todos (${counts.todos} pending)`);
for (const item of realTodos) {
const area = item.area ? ` [${item.area}]` : '';
const pri = item.priority ? ` (${item.priority})` : '';
lines.push(` β€’ ${item.filename}${area}${pri}`);
if (item.summary)
lines.push(` ${item.summary}`);
}
if (remainder) {
lines.push(` ... and ${remainder._remainder_count} more`);
}
}
// Threads (deferred decisions β€” blue)
if (counts.threads > 0) {
lines.push('');
lines.push(`πŸ”΅ Open Threads (${counts.threads} active)`);
for (const item of items.threads.filter(i => !i.scan_error)) {
const title = item.title ? ` β€” ${item.title}` : '';
lines.push(` β€’ ${item.slug} [${item.status}]${title}`);
}
}
// Seeds (deferred decisions β€” blue)
if (counts.seeds > 0) {
lines.push('');
lines.push(`πŸ”΅ Unimplemented Seeds (${counts.seeds} pending)`);
for (const item of items.seeds.filter(i => !i.scan_error)) {
const title = item.title ? ` β€” ${item.title}` : '';
lines.push(` β€’ ${item.seed_id} [${item.status}]${title}`);
}
}
// Context questions (deferred decisions β€” blue)
if (counts.context_questions > 0) {
lines.push('');
lines.push(`πŸ”΅ CONTEXT Open Questions (${counts.context_questions} phases with open questions)`);
for (const item of items.context_questions.filter(i => !i.scan_error)) {
lines.push(` β€’ Phase ${item.phase}: ${item.file} (${item.question_count} question${item.question_count !== 1 ? 's' : ''})`);
for (const q of item.questions) {
lines.push(` - ${q}`);
}
}
}
lines.push('');
lines.push(hr);
lines.push(` ${counts.total} item${counts.total !== 1 ? 's' : ''} require decisions before close.`);
lines.push(hr);
return lines.join('\n');
}
module.exports = { auditOpenArtifacts, formatAuditReport };