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
323 lines
14 KiB
JavaScript
Executable File
323 lines
14 KiB
JavaScript
Executable File
#!/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}`);
|