Files
wehub-resource-sync 23f7624596
ADR-166 MCP Bridge Security Lock / Static-source security lock (push) Failing after 0s
ADR-166 MCP Bridge Security Lock / Compose default binds loopback + Mongo has auth (push) Failing after 2s
CodeQL Advanced / Analyze (rust) (push) Failing after 0s
ADR-166 MCP Bridge Security Lock / plugin-agent-federation bindHost default (push) Failing after 1s
ADR-166 MCP Bridge Security Lock / Runtime behavior — 401 + terminal gate + fail-closed (push) Failing after 4s
business-pods-smoke / smoke (push) Failing after 1s
all-plugins-smoke / smoke-all (push) Failing after 2s
CI/CD Pipeline / Security & Code Quality (push) Failing after 1s
CI/CD Pipeline / Test Suite (ubuntu-latest) (push) Failing after 1s
CI/CD Pipeline / Build & Package (macos-latest) (push) Has been skipped
CI/CD Pipeline / Build & Package (ubuntu-latest) (push) Has been skipped
CI/CD Pipeline / Build & Package (windows-latest) (push) Has been skipped
CI/CD Pipeline / Documentation & Examples (push) Failing after 1s
Clone Tracker (14-day rolling) / Snapshot clones for ruflo ecosystem (push) Failing after 1s
CodeQL Advanced / Analyze (actions) (push) Failing after 1s
CodeQL Advanced / Analyze (javascript-typescript) (push) Failing after 1s
federation-peer-rust / stable-noop (push) Failing after 1s
metaharness-ci / score (push) Failing after 1s
metaharness-ci / router-compat (push) Failing after 0s
metaharness-ci / similarity-tests (push) Failing after 0s
no-agentbbs-smoke / smoke-without-agentbbs (push) Failing after 1s
V3 CI/CD Pipeline / Build V3 (windows-latest) (push) Has been skipped
codex-integration-audit / Codex integration audit (push) Failing after 1s
helpers-manifest-guard / guard (push) Failing after 1s
🔗 Cross-Agent Integration Tests / 🤝 Agent Coordination Tests (push) Has been skipped
🔗 Cross-Agent Integration Tests / 🧠 Memory Sharing Integration (push) Has been skipped
🔗 Cross-Agent Integration Tests / 🛡️ Fault Tolerance Tests (push) Has been skipped
🔗 Cross-Agent Integration Tests / ⚡ Performance Integration Tests (push) Has been skipped
metaharness-ci / mcp-scan (push) Failing after 1s
metaharness-ci / eject-dryrun (push) Failing after 1s
metaharness-ci / metaharness-real-data (push) Failing after 0s
no-cli-optdep-bloat-2561 / guard (push) Failing after 1s
no-metaharness-smoke / smoke-without-metaharness (push) Failing after 1s
no-phantom-agentic-flow-subpath / guard (push) Failing after 1s
🔄 Automated Rollback Manager / 🚨 Failure Detection (push) Failing after 1s
V3 CI/CD Pipeline / Plugin hooks smoke / ubuntu-latest / Node 22 (push) Failing after 1s
V3 CI/CD Pipeline / ruflo-graph-intelligence build + test smoke (#2044, ADR-123) (push) Failing after 1s
CVE Audit Gate / Audit root (critical-blocking) (push) Failing after 2s
cost-tracker-smoke / smoke (push) Failing after 3s
oia-audit-weekly / audit (push) Failing after 2s
ruflo-agent-smoke / ruflo-agent structural smoke (push) Failing after 1s
📊 Status Badges Update / 📊 Update Status Badges (push) Failing after 1s
V3 CI/CD Pipeline / Static regression guards (#2267 YAML + (push) Failing after 1s
V3 CI/CD Pipeline / Test V3 Packages (push) Failing after 0s
V3 CI/CD Pipeline / agent_execute provider routing smoke (#2042) (push) Failing after 0s
CVE Audit Gate / Audit v3 (critical-blocking) (push) Failing after 1s
federation-peer-rust / stable-native (push) Failing after 2s
🔗 Cross-Agent Integration Tests / 🚀 Integration Test Setup (push) Failing after 2s
neural-trader-smoke / runtime-smoke (push) Failing after 1s
V3 CI/CD Pipeline / Build V3 (macos-latest) (push) Has been skipped
V3 CI/CD Pipeline / Build V3 (ubuntu-latest) (push) Has been skipped
V3 CI/CD Pipeline / Type Check V3 (push) Failing after 1s
V3 CI/CD Pipeline / Smoke (no better-sqlite3) / ubuntu-latest / Node 24 (push) Failing after 1s
V3 CI/CD Pipeline / Smoke (no better-sqlite3) / ubuntu-latest / Node 22 (push) Failing after 2s
V3 CI/CD Pipeline / browser rvf create flag smoke (#2015) (push) Failing after 0s
V3 CI/CD Pipeline / Dependency review (#2046) (push) Has been skipped
V3 CI/CD Pipeline / Supply-chain audit (#2046) (push) Failing after 0s
V3 CI/CD Pipeline / witness marker drift smoke (#2021) (push) Failing after 1s
V3 CI/CD Pipeline / neural-trader portfolio CG smoke (#2068, ADR-126 Phase 3) (push) Failing after 1s
V3 CI/CD Pipeline / neural-trader backtest signing smoke (#2068, ADR-126 Phase 4) (push) Failing after 1s
V3 CI/CD Pipeline / kg-extract type-import classification smoke (#2049) (push) Failing after 0s
V3 CI/CD Pipeline / witness verify precondition smoke (#1880) (push) Failing after 2s
V3 CI/CD Pipeline / neural-trader pipeline risk-gate smoke (#2068, ADR-126 Phase 5) (push) Failing after 0s
V3 CI/CD Pipeline / neural-trader feature attribution smoke (#2068, ADR-126 Phase 6) (push) Failing after 0s
V3 CI/CD Pipeline / plugin-registry signature verification smoke (#1922, CWE-347) (push) Failing after 4s
V3 CI/CD Pipeline / memory stats legacy-DB smoke (#2120) (push) Failing after 4s
V3 CI/CD Pipeline / github deprecated actions smoke (#2089, ADR-127 Phase 3) (push) Failing after 1s
V3 CI/CD Pipeline / graph query + pathfinder smoke (ADR-130 P2+P5) (push) Has been skipped
V3 CI/CD Pipeline / graph trajectory hooks smoke (ADR-130 P3) (push) Has been skipped
V3 CI/CD Pipeline / graph plugin adapter smoke (ADR-130 P4) (push) Has been skipped
V3 CI/CD Pipeline / graph benchmark (ADR-130 P6) (push) Has been skipped
V3 CI/CD Pipeline / statusline generator delegation smoke (#2195) (push) Failing after 1s
V3 CI/CD Pipeline / wizard init regression guard (#2206 (push) Failing after 1s
V3 CI/CD Pipeline / memory no-stray-db smoke (ADR-125 P7) (push) Failing after 1s
V3 CI/CD Pipeline / github-safe injection smoke (#2089, ADR-127 Phase 1) (push) Failing after 1s
V3 CI/CD Pipeline / github actions pin smoke (#2089, ADR-127 Phase 1) (push) Failing after 1s
V3 CI/CD Pipeline / github attribution opt-in smoke (#2089, ADR-127 Phase 4) (push) Failing after 1s
V3 CI/CD Pipeline / pre-bash hook safety smoke (#2017) (push) Failing after 1s
V3 CI/CD Pipeline / Memory import smoke / ubuntu-latest (push) Failing after 0s
V3 CI/CD Pipeline / MCP protocol smoke / ubuntu-latest (push) Failing after 2s
V3 CI/CD Pipeline / ruvllm WASM auto-init smoke (#2086) (push) Failing after 4s
V3 CI/CD Pipeline / MCP paired-tool round-trip smoke (#1889) (push) Failing after 1s
V3 CI/CD Pipeline / Plugin package install-safety (#1902/#1903/#1904) (push) Failing after 1s
V3 CI/CD Pipeline / Tool description discoverability (ADR-112) (push) Failing after 3s
V3 CI/CD Pipeline / CLI npx-install smoke (#1147 / (22) (push) Failing after 1s
V3 CI/CD Pipeline / CLI npx-install smoke (#1147 / (24) (push) Failing after 1s
V3 CI/CD Pipeline / Windows hook shim smoke (#2132) / ubuntu-latest (push) Failing after 2s
V3 CI/CD Pipeline / Windows hook execution smoke (#2132) / ubuntu-latest (push) Failing after 1s
V3 CI/CD Pipeline / Windows init hooks smoke (#2132) / ubuntu-latest (push) Failing after 1s
V3 CI/CD Pipeline / Vector-index dimension audit (#1947) (push) Failing after 0s
V3 CI/CD Pipeline / Hook-command install safety (#1921) (push) Failing after 1s
V3 CI/CD Pipeline / ToolOutputGuardrail smoke (ADR-131, (push) Failing after 1s
V3 CI/CD Pipeline / init-bundle invariants smoke (#2095, ADR-128 Phase 5) (push) Failing after 1s
V3 CI/CD Pipeline / wasm provider bridge smoke (ADR-129 P1) (push) Failing after 2s
V3 CI/CD Pipeline / wasm gallery CRUD smoke (ADR-129 P3) (push) Failing after 1s
V3 CI/CD Pipeline / wasm plugin bridge smoke (ADR-129 P4) (push) Failing after 0s
V3 CI/CD Pipeline / wasm compose smoke (ADR-129 P2) (push) Failing after 4s
V3 CI/CD Pipeline / graph schema smoke (ADR-130 P1) (push) Failing after 0s
Validate Marketplace / validate (push) Failing after 1s
🔍 Verification Pipeline / 🚀 Setup Verification (push) Failing after 1s
🔍 Verification Pipeline / 🛡️ Security Verification (push) Has been skipped
🔍 Verification Pipeline / 📝 Code Quality (push) Has been skipped
🔍 Verification Pipeline / 🧪 Test Verification (${{ matrix.os }}, Node ${{ matrix.node }}) (push) Has been skipped
🔍 Verification Pipeline / 🏗️ Build Verification (push) Has been skipped
🔍 Verification Pipeline / 📚 Documentation Verification (push) Has been skipped
CVE Audit Gate / High-severity report (warn only) (push) Has been cancelled
🔄 Automated Rollback Manager / 🔄 Execute Rollback (push) Has been cancelled
🔄 Automated Rollback Manager / ✅ Post-Rollback Verification (push) Has been cancelled
🔄 Automated Rollback Manager / 📊 Rollback Monitoring (push) Has been cancelled
V3 CI/CD Pipeline / Windows init hooks smoke (#2132) / windows-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows hook execution smoke (#2132) / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows hook execution smoke (#2132) / windows-latest (push) Has been cancelled
🔄 Automated Rollback Manager / ⏳ Manual Rollback Approval (push) Has been cancelled
V3 CI/CD Pipeline / MCP protocol smoke / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Memory import smoke / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows hook shim smoke (#2132) / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows hook shim smoke (#2132) / windows-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows init hooks smoke (#2132) / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Witness verify (signed manifest) / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Witness verify (signed manifest) / ubuntu-latest (push) Has been cancelled
V3 CI/CD Pipeline / Witness verify (signed manifest) / windows-latest (push) Has been cancelled
V3 CI/CD Pipeline / Publish to npm (alpha) (push) Has been cancelled
V3 CI/CD Pipeline / Smoke (no better-sqlite3) / macos-latest / Node 22 (push) Has been cancelled
V3 CI/CD Pipeline / Plugin hooks smoke / macos-latest / Node 22 (push) Has been cancelled
CI/CD Pipeline / Deploy & Release (push) Has been cancelled
CI/CD Pipeline / CI Status (push) Has been cancelled
🔗 Cross-Agent Integration Tests / 📊 Integration Test Report (push) Has been cancelled
🔄 Automated Rollback Manager / 🔍 Pre-Rollback Validation (push) Has been cancelled
🔍 Verification Pipeline / ⚡ Performance Verification (push) Has been cancelled
🔍 Verification Pipeline / 📊 Verification Report (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:02:19 +08:00

323 lines
14 KiB
JavaScript
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env node
// One-shot ADR importer for the ruflo-adr plugin.
//
// Walks the working directory (or ADR_ROOT override), parses every ADR file
// under */docs/adr/ or */docs/adrs/, persists records to the `adr-patterns`
// namespace, persists causal edges to the `adr-edges` namespace, prints a
// summary with status counts + relationship breakdown + dangling-ref check.
//
// Handles two ADR formats:
// 1. v3-style: `# ADR-097: Title` heading + `**Status**: Proposed` line
// 2. plugin-style: YAML frontmatter (`status: Proposed`) at file head
//
// Usage:
// node scripts/import.mjs # markdown summary to stdout
// IMPORT_FORMAT=json node scripts/import.mjs # JSON summary
// IMPORT_DRY_RUN=1 node scripts/import.mjs # parse + summarize, skip memory_store
// ADR_ROOT=/path/to/repo node scripts/import.mjs # override scan root (default: cwd)
//
// Why a script, not raw MCP calls: 70+ ADRs × multiple memory_store calls each
// is hundreds of MCP round-trips. spawnSync over the CLI is materially faster
// and avoids shell-quoting pitfalls in the ADR titles.
import { readFileSync, readdirSync, statSync } from 'node:fs';
import { join, basename } from 'node:path';
import { spawnSync } from 'node:child_process';
// ADR-100 / #1748 Issue 3 — CLI_CORE=1 routes to lite cli-core (~2s cold-cache).
// Note: cli-core's JsonMemoryBackend overwrites by default, so the
// "exists" / UNIQUE-constraint detection below collapses to "ok" under CLI_CORE.
// Re-running import in CLI_CORE mode is therefore idempotent (records refreshed)
// rather than incremental (records skipped). For incremental imports across
// many runs, leave CLI_CORE unset.
const CLI_PKG = process.env.CLI_CORE === '1'
? '@claude-flow/cli-core@alpha'
: '@claude-flow/cli@latest';
const ROOT = process.env.ADR_ROOT || process.cwd();
// #2474 bonus: \`.claude/worktrees/*\` mirrors the repo so every ADR was
// indexed 2-3x. Skip the whole \`.claude\` tree — it's all ruflo runtime
// state (worktrees, scheduled tasks, etc.), never authored content.
const SKIP_DIRS = new Set(['node_modules', '.git', 'dist', 'v2', '.next', '.turbo', 'build', '.claude']);
// #2474 Bug 4: parseId padded ADR numbers, but extractAdrRefs's
// padStart-then-strip pipeline produced different results for the same
// numeric value. A repo mixing `0001-foo.md` (parseId → ADR-0001) with
// a body line `Supersedes: ADR-0001` (extractAdrRefs → ADR-001) drops
// every edge as dangling. Single normalizer keeps both paths in lockstep.
function normalizeAdrId(raw) {
const digits = String(raw).replace(/^ADR-?/i, '').trim();
if (!/^\d+$/.test(digits)) return `ADR-${raw}`;
// Canonical form: ≤3 digits → zero-pad to 3 (legacy default);
// ≥4 digits → keep as-is. Either way, the same numeric input from
// a filename and from a body reference now produce the same key.
return digits.length >= 4 ? `ADR-${digits}` : `ADR-${digits.padStart(3, '0')}`;
}
function findAdrs(dir, out = []) {
let entries;
try { entries = readdirSync(dir); } catch { return out; }
for (const e of entries) {
if (SKIP_DIRS.has(e)) continue;
const p = join(dir, e);
let st;
try { st = statSync(p); } catch { continue; }
if (st.isDirectory()) {
findAdrs(p, out);
} else if (e.endsWith('.md') && (p.includes('/docs/adr/') || p.includes('/docs/adrs/'))) {
out.push(p);
}
}
return out;
}
function parseAdr(path) {
const text = readFileSync(path, 'utf-8');
const id = parseId(path, text);
const title = parseTitle(text);
const status = parseStatus(text);
const date = parseDate(text);
const tags = parseTags(text);
const context = parseContextFirstParagraph(text);
const links = parseLinks(text, id);
return { id, title, status, date, tags, context, links, file: path.replace(ROOT + '/', '') };
}
function parseId(path, text) {
const fm = /^---\s*$([\s\S]*?)^---\s*$/m.exec(text);
if (fm) {
const m = /^id:\s*(\S+)/m.exec(fm[1]);
if (m) return normalizeAdrId(m[1]);
}
const fname = basename(path, '.md');
const m = /^(ADR-?\d+|\d{3,4})/i.exec(fname);
if (m) return normalizeAdrId(m[1]);
return fname;
}
function parseTitle(text) {
const fm = /^---\s*$([\s\S]*?)^---\s*$/m.exec(text);
if (fm) {
const m = /^title:\s*(.+)$/m.exec(fm[1]);
if (m) return m[1].trim();
}
const m = /^#\s*(?:ADR-?\d+:?\s*)?(.+?)$/m.exec(text);
return m ? m[1].trim() : '(untitled)';
}
function parseStatus(text) {
const fm = /^---\s*$([\s\S]*?)^---\s*$/m.exec(text);
if (fm) {
const m = /^status:\s*(.+)$/im.exec(fm[1]);
if (m) return m[1].trim();
}
// #2474 Bug 2: also accept `**Status:**` (colon inside the bold span),
// the widely-used Nygard / MADR style. Previous regex only matched
// `**Status**:` (colon outside) and dropped every Nygard-style ADR
// to status=Unknown. Now the colon can sit on either side of the `**`.
// Strip parenthetical qualifiers like "Proposed (v3.6.x)" -> "Proposed".
let m = /^\*\*Status:?\*\*:?\s*([A-Za-z][A-Za-z\- ]*?)(?:\s*\(.*?\))?\s*$/m.exec(text);
if (m) return m[1].trim();
// Also handle full-bold MADR style: **Status: Value** (entire phrase bolded)
m = /^\*\*Status:\s*([A-Za-z][A-Za-z\- ]*?)(?:\s*\([^)]*\))?\*\*\s*$/m.exec(text);
return m ? m[1].trim() : 'Unknown';
}
function parseDate(text) {
const fm = /^---\s*$([\s\S]*?)^---\s*$/m.exec(text);
if (fm) {
const m = /^date:\s*(\S+)/m.exec(fm[1]);
if (m) return m[1];
}
const m = /^\*\*Date\*\*:\s*(\S+)/m.exec(text);
return m ? m[1] : '';
}
function parseTags(text) {
const fm = /^---\s*$([\s\S]*?)^---\s*$/m.exec(text);
if (fm) {
const m = /^tags:\s*\[([^\]]+)\]/m.exec(fm[1]);
if (m) return m[1].split(',').map((s) => s.trim()).filter(Boolean);
}
const m = /^\*\*Tags\*\*:\s*(.+)$/m.exec(text);
return m ? m[1].split(',').map((s) => s.trim()).filter(Boolean) : [];
}
function parseContextFirstParagraph(text) {
const m = /^##\s*Context\s*$\s*([\s\S]+?)(?=^##\s|\Z)/m.exec(text);
if (!m) return '';
return m[1].trim().split(/\n\s*\n/)[0].replace(/\s+/g, ' ').slice(0, 400);
}
// Extract ADR-NNN references from a link line. CRITICAL: must distinguish ADR
// references from GitHub issue numbers (#1697 etc.) which the prior version
// false-positively captured as "ADR-1697". We only recognize bare numbers as
// ADR refs when they appear in a known ADR-link section AND they don't look
// like GitHub issues (no leading #, no leading "issue").
function parseLinks(text, selfId) {
const out = [];
// Frontmatter relationships
const fm = /^---\s*$([\s\S]*?)^---\s*$/m.exec(text);
if (fm) {
for (const [field, relation] of [
['supersedes', 'supersedes'],
['amended-by', 'amends'],
['amends', 'amends'],
['related', 'related'],
['depends-on', 'depends-on'],
]) {
const re = new RegExp(`^${field}:\\s*\\[?([^\\]\\n]+)\\]?$`, 'mi');
const m = re.exec(fm[1]);
if (m) for (const ref of extractAdrRefs(m[1])) {
if (relation === 'supersedes') out.push({ from: ref, to: selfId, relation });
else out.push({ from: selfId, to: ref, relation });
}
}
}
// Body relationship lines. #2474 Bug 3: loosened to accept both colon
// placements (`**Supersedes:**` and `**Supersedes**:`) and an optional
// parenthetical qualifier like `**Supersedes (partial):**` — same
// tolerance as parseStatus.
const REL = (label) => new RegExp(`^\\*\\*${label}(?:\\s*\\([^)]*\\))?:?\\*\\*:?\\s*(.+)$`, 'mi');
const supersedes = REL('Supersedes').exec(text);
if (supersedes) for (const ref of extractAdrRefs(supersedes[1])) out.push({ from: ref, to: selfId, relation: 'supersedes' });
const amended = REL('(?:Amended[ -]by|Amends)').exec(text);
if (amended) for (const ref of extractAdrRefs(amended[1])) out.push({ from: selfId, to: ref, relation: 'amends' });
const related = REL('Related').exec(text);
if (related) for (const ref of extractAdrRefs(related[1])) out.push({ from: selfId, to: ref, relation: 'related' });
const dependsOn = REL('Depends[ -]on').exec(text);
if (dependsOn) for (const ref of extractAdrRefs(dependsOn[1])) out.push({ from: selfId, to: ref, relation: 'depends-on' });
return out;
}
function extractAdrRefs(s) {
const refs = new Set();
// Strip GitHub issue / commit references first to prevent false positives.
const cleaned = s
.replace(/#\d+/g, '') // #1697
.replace(/issue[s]?\s*\d+/gi, '') // issue 1697
.replace(/PR\s*\d+/gi, '') // PR 1234
.replace(/commit\s*[`a-f0-9]+/gi, '') // commit `abc123`
.replace(/`[^`]*`/g, ''); // any backtick-quoted span
const re = /\bADR-?(\d+)\b/gi;
let m;
// #2474 Bug 4: route every ADR ref through the same normalizer parseId
// uses, so a Supersedes: ADR-0001 line lands on the same node key as
// the file ADR-0001.md (was: padStart-then-strip produced ADR-001).
while ((m = re.exec(cleaned))) refs.add(normalizeAdrId(m[1]));
return [...refs];
}
function memoryStore(namespace, key, value) {
const valueStr = typeof value === 'string' ? value : JSON.stringify(value);
// #2474 Bug 1 (fatal): ADR titles like "ADR-005 — Repository …" contain
// a U+2014 em-dash. \`npm exec\` runs argv validation BEFORE handing args
// to the underlying bin, and \`commander\`-style argv with a non-ASCII
// dash that starts an arg makes it reject with:
// npm error arg Argument starts with non-ascii dash, this is probably invalid: — …
// Result: every store failed → \`Records stored: 0/N\`.
//
// Use the \`--flag=value\` form so npm sees a single \`--value=…\` token
// and doesn't try to interpret the leading character of the value.
// This works on both legacy and current npm; the underlying CLI accepts
// \`--flag=value\` and \`--flag value\` equivalently.
const r = spawnSync('npx', [
CLI_PKG, 'memory', 'store',
`--namespace=${namespace}`,
`--key=${key}`,
`--value=${valueStr}`,
], { stdio: ['ignore', 'pipe', 'pipe'], encoding: 'utf-8' });
if (r.status !== 0) {
if (/UNIQUE constraint/i.test(r.stderr || r.stdout || '')) return 'exists';
return 'error: ' + (r.stderr || '').slice(0, 100);
}
return 'ok';
}
const dryRun = process.env.IMPORT_DRY_RUN === '1';
const fmt = process.env.IMPORT_FORMAT || 'markdown';
const files = findAdrs(ROOT);
const adrs = files.map(parseAdr);
const byId = new Map();
const allEdges = [];
for (const a of adrs) {
byId.set(a.id, a);
allEdges.push(...a.links);
}
let storedRecords = 0, storedEdges = 0;
const errors = [];
if (!dryRun) {
for (const a of adrs) {
const r = memoryStore('adr-patterns', `${a.id}::${basename(a.file, '.md')}`,
`${a.title}${a.context || '(no context)'}\n\nfile: ${a.file}\nstatus: ${a.status}\ndate: ${a.date}\ntags: ${a.tags.join(',')}`);
if (r === 'ok' || r === 'exists') storedRecords++;
else errors.push(`${a.id} ${a.file}: ${r}`);
}
for (const e of allEdges) {
const key = `${e.relation}:${e.from}->${e.to}:${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
const r = memoryStore('adr-edges', key, JSON.stringify({ ...e, capturedAt: new Date().toISOString() }));
if (r === 'ok' || r === 'exists') storedEdges++;
}
}
const danglingRefs = allEdges.filter((e) => !byId.has(e.to));
const supersededIds = new Set(allEdges.filter((x) => x.relation === 'supersedes').map((x) => x.from));
const statusMismatches = [];
for (const id of supersededIds) {
const a = byId.get(id);
if (a && !/superseded/i.test(a.status)) statusMismatches.push({ id, status: a.status, file: a.file });
}
const byStatus = {};
for (const a of adrs) {
const k = (a.status || 'unknown').toLowerCase();
byStatus[k] = (byStatus[k] || 0) + 1;
}
const byRelation = {};
for (const e of allEdges) byRelation[e.relation] = (byRelation[e.relation] || 0) + 1;
const bySource = {};
for (const a of adrs) {
const src = a.file.split('/docs/')[0];
bySource[src] = (bySource[src] || 0) + 1;
}
const result = {
scannedRoot: ROOT,
total: adrs.length,
sourceDirs: Object.keys(bySource).length,
storedRecords, storedEdges, dryRun,
byStatus, byRelation, bySource,
edges: allEdges.length,
danglingRefs, statusMismatches, errors,
};
if (fmt === 'json') {
console.log(JSON.stringify(result, null, 2));
process.exit(0);
}
console.log('## ADR Index Summary');
console.log('');
console.log(`Total ADRs: **${result.total}** across ${result.sourceDirs} source dirs (root: ${ROOT})`);
console.log(`Records stored to \`adr-patterns\`: ${result.storedRecords}/${result.total}${dryRun ? ' (dry-run, skipped)' : ''}`);
console.log(`Edges stored to \`adr-edges\`: ${result.storedEdges}/${result.edges}${dryRun ? ' (dry-run, skipped)' : ''}`);
console.log('');
console.log('### By status');
for (const [k, n] of Object.entries(byStatus).sort((a, b) => b[1] - a[1])) console.log(`- ${k}: ${n}`);
console.log('');
console.log(`### Relationships: **${result.edges}** edges`);
for (const [k, n] of Object.entries(byRelation).sort((a, b) => b[1] - a[1])) console.log(`- ${k}: ${n}`);
console.log('');
console.log('### Issues found');
console.log(`- Dangling refs (edge → non-existent ADR): ${danglingRefs.length}`);
for (const d of danglingRefs.slice(0, 10)) console.log(` - ${d.relation} ${d.from}${d.to} (missing)`);
console.log(`- Status mismatches (superseded but not marked): ${statusMismatches.length}`);
for (const m of statusMismatches.slice(0, 10)) console.log(` - ${m.id} status='${m.status}' (${m.file})`);
console.log(`- Storage errors: ${errors.length}`);
for (const e of errors.slice(0, 5)) console.log(` - ${e}`);
console.log('');
console.log('### Source breakdown');
for (const [s, n] of Object.entries(bySource).sort((a, b) => b[1] - a[1]).slice(0, 12)) console.log(`- ${s}: ${n}`);