kirograph 1.1.1 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/README.md +3 -2
  2. package/dist/bin/commands/memory.js +12 -2
  3. package/dist/bin/commands/memory.js.map +2 -2
  4. package/dist/bin/commands/wiki.js +3 -2
  5. package/dist/bin/commands/wiki.js.map +2 -2
  6. package/dist/bin/kirograph.js +1 -1
  7. package/dist/config.js +8 -0
  8. package/dist/config.js.map +2 -2
  9. package/dist/db/memory-schema.sql +2 -1
  10. package/dist/mcp/handlers/memory.js +2 -1
  11. package/dist/mcp/handlers/memory.js.map +2 -2
  12. package/dist/mcp/handlers/wiki.js +4 -2
  13. package/dist/mcp/handlers/wiki.js.map +2 -2
  14. package/dist/mcp/tools.js +2 -1
  15. package/dist/mcp/tools.js.map +2 -2
  16. package/dist/memory/database.js +17 -4
  17. package/dist/memory/database.js.map +2 -2
  18. package/dist/memory/index.js +18 -1
  19. package/dist/memory/index.js.map +2 -2
  20. package/dist/memory/schema.js +35 -0
  21. package/dist/memory/schema.js.map +2 -2
  22. package/dist/memory/types.js.map +1 -1
  23. package/dist/patterns/loader.js +2 -146
  24. package/dist/patterns/loader.js.map +3 -3
  25. package/dist/security/manifest/plugins/maven.js +33 -3
  26. package/dist/security/manifest/plugins/maven.js.map +2 -2
  27. package/dist/security/manifest/plugins/npm.js +32 -3
  28. package/dist/security/manifest/plugins/npm.js.map +2 -2
  29. package/dist/shared/json-schema.js +136 -0
  30. package/dist/shared/json-schema.js.map +7 -0
  31. package/dist/shared/yaml.js +174 -0
  32. package/dist/shared/yaml.js.map +7 -0
  33. package/dist/wiki/index.js +3 -1
  34. package/dist/wiki/index.js.map +2 -2
  35. package/dist/wiki/lint.js +14 -1
  36. package/dist/wiki/lint.js.map +2 -2
  37. package/dist/wiki/typed-pages.js +67 -0
  38. package/dist/wiki/typed-pages.js.map +7 -0
  39. package/dist/wiki/types.js.map +1 -1
  40. package/package.json +1 -1
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/security/manifest/plugins/npm.ts"],
4
- "sourcesContent": ["/**\n * NPM Version Extraction Plugin for KiroGraph-Sec\n *\n * Extends the existing architecture npm parser to extract version constraints,\n * resolved versions (from lock files), and dependency scopes for security analysis.\n *\n * Reuses `npmParser` from `src/architecture/manifest/npm.ts` for discovery and\n * basic parsing, then layers on version/scope extraction.\n */\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport type { ParsedDependency } from '../../types';\nimport { logWarn } from '../../../errors';\n\n/** Scope fields in package.json mapped to their ParsedDependency scope */\nconst SCOPE_FIELDS: Array<{ field: string; scope: ParsedDependency['scope'] }> = [\n { field: 'dependencies', scope: 'production' },\n { field: 'devDependencies', scope: 'development' },\n { field: 'optionalDependencies', scope: 'optional' },\n];\n\n/**\n * Resolved version map: package name \u2192 resolved version string.\n * Built from package-lock.json or yarn.lock.\n */\ntype ResolvedVersionMap = Map<string, string>;\n\n/**\n * Parse an npm package.json manifest and extract dependency declarations\n * with version constraints, scopes, and resolved versions from lock files.\n *\n * @param manifestPath - Absolute path to the package.json file\n * @param projectRoot - Absolute path to the project root directory\n * @returns Array of parsed dependencies with version and scope information\n */\nexport async function parseNpmManifest(\n manifestPath: string,\n projectRoot: string,\n): Promise<ParsedDependency[]> {\n const manifestDir = path.dirname(manifestPath);\n const relativeManifest = path.relative(projectRoot, manifestPath).replace(/\\\\/g, '/');\n\n // Read and parse the package.json\n let pkg: Record<string, unknown>;\n try {\n const raw = fs.readFileSync(manifestPath, 'utf8');\n const parsed = JSON.parse(raw);\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n logWarn(`[sec:npm] Invalid package.json structure at ${relativeManifest}`);\n return [];\n }\n pkg = parsed as Record<string, unknown>;\n } catch (err) {\n logWarn(`[sec:npm] Failed to read/parse ${relativeManifest}: ${err instanceof Error ? err.message : String(err)}`);\n return [];\n }\n\n // Attempt to load resolved versions from lock file\n const resolvedVersions = loadResolvedVersions(manifestDir);\n\n // Extract the package-level license\n const license = extractNpmLicense(pkg);\n\n // Extract dependencies from each scope field\n const dependencies: ParsedDependency[] = [];\n const warnings: Array<{ name: string; reason: string }> = [];\n\n for (const { field, scope } of SCOPE_FIELDS) {\n const depsObj = pkg[field];\n if (depsObj === undefined || depsObj === null) continue;\n\n if (typeof depsObj !== 'object' || Array.isArray(depsObj)) {\n logWarn(`[sec:npm] Invalid \"${field}\" field in ${relativeManifest} \u2014 expected object, got ${typeof depsObj}`);\n continue;\n }\n\n const entries = Object.entries(depsObj as Record<string, unknown>);\n for (const [name, constraint] of entries) {\n // Validate the dependency entry\n if (!isValidDependencyName(name)) {\n logWarn(`[sec:npm] Invalid dependency name \"${name}\" in ${relativeManifest} (${field}) \u2014 skipping`);\n warnings.push({ name, reason: 'invalid name' });\n continue;\n }\n\n if (typeof constraint !== 'string' || constraint.trim() === '') {\n logWarn(`[sec:npm] Invalid version constraint for \"${name}\" in ${relativeManifest} (${field}) \u2014 skipping`);\n warnings.push({ name, reason: 'invalid constraint' });\n continue;\n }\n\n const resolvedVersion = resolvedVersions.get(name);\n\n dependencies.push({\n name,\n declaredConstraint: constraint,\n resolvedVersion,\n scope,\n ecosystem: 'npm',\n sourceManifest: relativeManifest,\n ...(license !== undefined ? { license } : {}),\n });\n }\n }\n\n // Also handle peerDependencies \u2014 default to production scope\n const peerDeps = pkg.peerDependencies;\n if (peerDeps && typeof peerDeps === 'object' && !Array.isArray(peerDeps)) {\n const peerEntries = Object.entries(peerDeps as Record<string, unknown>);\n for (const [name, constraint] of peerEntries) {\n if (!isValidDependencyName(name)) {\n logWarn(`[sec:npm] Invalid peer dependency name \"${name}\" in ${relativeManifest} \u2014 skipping`);\n warnings.push({ name, reason: 'invalid name' });\n continue;\n }\n\n if (typeof constraint !== 'string' || constraint.trim() === '') {\n logWarn(`[sec:npm] Invalid version constraint for peer dependency \"${name}\" in ${relativeManifest} \u2014 skipping`);\n warnings.push({ name, reason: 'invalid constraint' });\n continue;\n }\n\n // Only add if not already declared in another scope\n const alreadyDeclared = dependencies.some(d => d.name === name);\n if (!alreadyDeclared) {\n const resolvedVersion = resolvedVersions.get(name);\n dependencies.push({\n name,\n declaredConstraint: constraint,\n resolvedVersion,\n scope: 'production',\n ecosystem: 'npm',\n sourceManifest: relativeManifest,\n ...(license !== undefined ? { license } : {}),\n });\n }\n }\n }\n\n // Transitive dependencies (present only in the lock file, never declared\n // directly in package.json) would otherwise never become a Dependency_Node,\n // so they'd silently skip vulnerability scanning entirely. Add them here.\n const directNames = new Set(dependencies.map(d => d.name));\n const lockRelativePath = relativeManifest.replace(/package\\.json$/, 'package-lock.json');\n const npmLockPath = path.join(manifestDir, 'package-lock.json');\n if (fs.existsSync(npmLockPath)) {\n // package-lock.json (v2/v3) carries a per-package \"dev\" flag \u2014 parse it\n // directly so transitive dev-only dependencies keep the right scope.\n dependencies.push(...extractTransitiveNpmDependencies(manifestDir, lockRelativePath, directNames));\n } else {\n // pnpm-lock.yaml, yarn.lock, or a lockfileVersion 1 package-lock.json:\n // resolvedVersions already has every package the lock file resolved,\n // direct or transitive \u2014 it just doesn't carry a dev/prod distinction,\n // so default newly-added transitive entries to 'production'.\n for (const [name, version] of resolvedVersions) {\n if (directNames.has(name)) continue;\n dependencies.push({\n name,\n declaredConstraint: version,\n resolvedVersion: version,\n scope: 'production',\n ecosystem: 'npm',\n sourceManifest: relativeManifest,\n });\n }\n }\n\n return dependencies;\n}\n\n/**\n * Find every npm package name that resolves to more than one distinct\n * version somewhere in package-lock.json's dependency tree. npm's nested\n * node_modules layout can install different versions of the same package\n * at different paths (e.g. a top-level `qs@6.16.0` alongside a\n * `body-parser`-nested `qs@6.15.3`) \u2014 but a Dependency_Node only records one\n * `resolved_version`. Used by vulnerability enrichment to also check every\n * other installed copy, not just whichever version ended up recorded.\n *\n * Only the top-level package-lock.json (lockfile v2/v3 \"packages\" map) is\n * checked \u2014 a monorepo with per-package lock files, or pnpm/yarn projects,\n * won't get this extra check (they still get the single recorded version).\n *\n * Returns a map from package name to the full set of distinct versions\n * found; names with only one version are omitted.\n */\nexport function findNpmDuplicateVersions(projectRoot: string): Map<string, Set<string>> {\n const versionsByName = new Map<string, Set<string>>();\n\n const lockPath = path.join(projectRoot, 'package-lock.json');\n if (!fs.existsSync(lockPath)) return versionsByName;\n\n let lockData: unknown;\n try {\n lockData = JSON.parse(fs.readFileSync(lockPath, 'utf8'));\n } catch {\n return versionsByName;\n }\n\n if (typeof lockData !== 'object' || lockData === null) return versionsByName;\n const packages = (lockData as Record<string, unknown>).packages;\n if (typeof packages !== 'object' || packages === null) return versionsByName;\n\n for (const [key, value] of Object.entries(packages as Record<string, unknown>)) {\n if (key === '') continue; // skip the root project entry\n if (typeof value !== 'object' || value === null) continue;\n const pkg = value as Record<string, unknown>;\n\n const version = pkg.version;\n if (typeof version !== 'string') continue;\n\n const name = key.replace(/^.*node_modules\\//, '');\n if (!name) continue;\n\n if (!versionsByName.has(name)) versionsByName.set(name, new Set());\n versionsByName.get(name)!.add(version);\n }\n\n for (const [name, versions] of versionsByName) {\n if (versions.size <= 1) versionsByName.delete(name);\n }\n\n return versionsByName;\n}\n\n/**\n * Extract every package listed in package-lock.json's \"packages\" map (lockfile\n * v2/v3) that isn't already a direct dependency. These are transitive-only\n * packages \u2014 e.g. a sub-dependency three levels deep \u2014 that `npm audit` reports\n * on but that package.json never mentions.\n */\nfunction extractTransitiveNpmDependencies(\n manifestDir: string,\n lockRelativePath: string,\n directNames: Set<string>,\n): ParsedDependency[] {\n const lockPath = path.join(manifestDir, 'package-lock.json');\n if (!fs.existsSync(lockPath)) return [];\n\n let lockData: unknown;\n try {\n lockData = JSON.parse(fs.readFileSync(lockPath, 'utf8'));\n } catch (err) {\n logWarn(`[sec:npm] Failed to parse ${lockRelativePath} for transitive dependencies: ${err instanceof Error ? err.message : String(err)}`);\n return [];\n }\n\n if (typeof lockData !== 'object' || lockData === null) return [];\n const packages = (lockData as Record<string, unknown>).packages;\n if (typeof packages !== 'object' || packages === null) return [];\n\n const transitive: ParsedDependency[] = [];\n const seen = new Set<string>();\n\n for (const [key, value] of Object.entries(packages as Record<string, unknown>)) {\n if (key === '') continue; // skip the root project entry\n if (typeof value !== 'object' || value === null) continue;\n const pkg = value as Record<string, unknown>;\n\n const name = key.replace(/^.*node_modules\\//, '');\n if (!name || directNames.has(name) || seen.has(name)) continue;\n\n const version = pkg.version;\n if (typeof version !== 'string') continue;\n\n seen.add(name);\n transitive.push({\n name,\n // No real semver range exists at the project level for a transitive\n // package \u2014 the resolved version is the only constraint we have.\n declaredConstraint: version,\n resolvedVersion: version,\n scope: pkg.dev === true ? 'development' : 'production',\n ecosystem: 'npm',\n sourceManifest: lockRelativePath,\n });\n }\n\n return transitive;\n}\n\n/**\n * Attempt to load resolved versions from package-lock.json, pnpm-lock.yaml,\n * or yarn.lock in the given directory. Returns an empty map if none is available.\n */\nfunction loadResolvedVersions(manifestDir: string): ResolvedVersionMap {\n const map: ResolvedVersionMap = new Map();\n\n // Try package-lock.json first (preferred \u2014 npm)\n const lockPath = path.join(manifestDir, 'package-lock.json');\n if (fs.existsSync(lockPath)) {\n try {\n const lockContent = fs.readFileSync(lockPath, 'utf8');\n const lockData = JSON.parse(lockContent);\n extractFromPackageLock(lockData, map);\n return map;\n } catch (err) {\n logWarn(`[sec:npm] Failed to parse package-lock.json: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n\n // Try pnpm-lock.yaml (pnpm)\n const pnpmLockPath = path.join(manifestDir, 'pnpm-lock.yaml');\n if (fs.existsSync(pnpmLockPath)) {\n try {\n const pnpmContent = fs.readFileSync(pnpmLockPath, 'utf8');\n extractFromPnpmLock(pnpmContent, map);\n return map;\n } catch (err) {\n logWarn(`[sec:npm] Failed to parse pnpm-lock.yaml: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n\n // Fall back to yarn.lock\n const yarnLockPath = path.join(manifestDir, 'yarn.lock');\n if (fs.existsSync(yarnLockPath)) {\n try {\n const yarnContent = fs.readFileSync(yarnLockPath, 'utf8');\n extractFromYarnLock(yarnContent, map);\n return map;\n } catch (err) {\n logWarn(`[sec:npm] Failed to parse yarn.lock: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n\n return map;\n}\n\n/**\n * Extract resolved versions from pnpm-lock.yaml (v5, v6, v9 formats).\n *\n * v5/v6: packages section with keys like \"/express/4.18.2\" or \"/express@4.18.2\"\n * v9: snapshots section with keys like \"express@4.18.2\" and\n * packages section with \"express@4.18.2: {version: ...}\" (optional)\n */\nfunction extractFromPnpmLock(content: string, map: ResolvedVersionMap): void {\n // v9 format: package entries under `packages:` or `snapshots:`\n // Key format: \"name@version\" or \"@scope/name@version\"\n // We extract name@version pairs from both sections.\n\n // Match lines like: /express/4.18.2: or /express@4.18.2: or express@4.18.2:\n for (const m of content.matchAll(/^[\\s]*\\/?(@?[a-zA-Z0-9._-]+(?:\\/[a-zA-Z0-9._-]+)?)[@/]([0-9][a-zA-Z0-9._-]*)(?:\\(.*?\\))?:/gm)) {\n const name = m[1].replace(/^\\//, ''); // strip leading slash for scoped packages\n const version = m[2];\n if (name && version && !map.has(name)) {\n map.set(name, version);\n }\n }\n}\n\n/**\n * Extract resolved versions from package-lock.json (supports v2 and v3 format).\n */\nfunction extractFromPackageLock(lockData: unknown, map: ResolvedVersionMap): void {\n if (typeof lockData !== 'object' || lockData === null) return;\n const lock = lockData as Record<string, unknown>;\n\n // lockfileVersion 2/3: uses \"packages\" field with \"\" as root\n const packages = lock.packages;\n if (typeof packages === 'object' && packages !== null) {\n for (const [key, value] of Object.entries(packages as Record<string, unknown>)) {\n if (key === '') continue; // skip root\n if (typeof value !== 'object' || value === null) continue;\n const pkg = value as Record<string, unknown>;\n const version = pkg.version;\n if (typeof version !== 'string') continue;\n\n // Key format: \"node_modules/<name>\" or \"node_modules/@scope/name\"\n const name = key.replace(/^.*node_modules\\//, '');\n if (name && !map.has(name)) {\n map.set(name, version);\n }\n }\n return;\n }\n\n // lockfileVersion 1: uses \"dependencies\" field\n const dependencies = lock.dependencies;\n if (typeof dependencies === 'object' && dependencies !== null) {\n extractFromLockV1Dependencies(dependencies as Record<string, unknown>, map);\n }\n}\n\n/**\n * Recursively extract versions from lockfileVersion 1 dependencies.\n */\nfunction extractFromLockV1Dependencies(\n deps: Record<string, unknown>,\n map: ResolvedVersionMap,\n): void {\n for (const [name, value] of Object.entries(deps)) {\n if (typeof value !== 'object' || value === null) continue;\n const dep = value as Record<string, unknown>;\n const version = dep.version;\n if (typeof version === 'string' && !map.has(name)) {\n map.set(name, version);\n }\n // Recurse into nested dependencies\n const nested = dep.dependencies;\n if (typeof nested === 'object' && nested !== null) {\n extractFromLockV1Dependencies(nested as Record<string, unknown>, map);\n }\n }\n}\n\n/**\n * Extract resolved versions from yarn.lock (v1 format).\n * yarn.lock uses a custom format, not JSON.\n */\nfunction extractFromYarnLock(content: string, map: ResolvedVersionMap): void {\n const lines = content.split('\\n');\n let currentPackageName: string | null = null;\n\n for (const line of lines) {\n // Skip comments and empty lines\n if (line.startsWith('#') || line.trim() === '') {\n currentPackageName = null;\n continue;\n }\n\n // Package header line: \"package-name@version-range:\" or \"@scope/name@range:\"\n // Can also be: \"package-name@range1, package-name@range2:\"\n if (!line.startsWith(' ') && line.endsWith(':')) {\n const header = line.slice(0, -1); // remove trailing ':'\n // Extract package name from the first entry (before @version)\n const name = extractPackageNameFromYarnHeader(header);\n currentPackageName = name;\n continue;\n }\n\n // Version line: \" version \"x.y.z\"\"\n if (currentPackageName && line.trim().startsWith('version ')) {\n const versionMatch = line.match(/version\\s+\"([^\"]+)\"/);\n if (versionMatch && !map.has(currentPackageName)) {\n map.set(currentPackageName, versionMatch[1]);\n }\n currentPackageName = null;\n }\n }\n}\n\n/**\n * Extract the package name from a yarn.lock header line.\n * Handles formats like:\n * - \"express@^4.17.1\"\n * - \"@types/node@^18.0.0\"\n * - \"express@^4.17.1, express@^4.18.0\"\n * - '\"express@^4.17.1\"'\n */\nfunction extractPackageNameFromYarnHeader(header: string): string | null {\n // Take the first entry if comma-separated\n const firstEntry = header.split(',')[0].trim();\n // Remove surrounding quotes if present\n const cleaned = firstEntry.replace(/^[\"']|[\"']$/g, '');\n\n // Find the last @ that separates name from version\n // For scoped packages (@scope/name@version), we need the last @\n const lastAtIndex = cleaned.lastIndexOf('@');\n if (lastAtIndex <= 0) return null; // no @ or starts with @ (scoped without version)\n\n // For scoped packages, check if the first char is @\n if (cleaned.startsWith('@')) {\n // @scope/name@version \u2014 find the @ after the scope\n const afterScope = cleaned.indexOf('/', 1);\n if (afterScope === -1) return null;\n const atAfterName = cleaned.indexOf('@', afterScope);\n if (atAfterName === -1) return cleaned; // no version part\n return cleaned.slice(0, atAfterName);\n }\n\n return cleaned.slice(0, lastAtIndex);\n}\n\n/**\n * Extract the license field from a parsed package.json object.\n * Handles both string format (`\"MIT\"`) and object format (`{\"type\":\"MIT\"}`).\n */\nfunction extractNpmLicense(pkg: Record<string, unknown>): string | undefined {\n const raw = pkg['license'];\n if (typeof raw === 'string' && raw.trim() !== '') {\n return raw.trim();\n }\n if (typeof raw === 'object' && raw !== null && !Array.isArray(raw)) {\n const licObj = raw as Record<string, unknown>;\n if (typeof licObj['type'] === 'string' && licObj['type'].trim() !== '') {\n return licObj['type'].trim();\n }\n }\n // Handle legacy \"licenses\" array\n const licenses = pkg['licenses'];\n if (Array.isArray(licenses) && licenses.length > 0) {\n const first = licenses[0];\n if (typeof first === 'object' && first !== null) {\n const lic = first as Record<string, unknown>;\n if (typeof lic['type'] === 'string' && lic['type'].trim() !== '') {\n return lic['type'].trim();\n }\n }\n if (typeof first === 'string' && first.trim() !== '') {\n return first.trim();\n }\n }\n return undefined;\n}\n\n/**\n * Validate that a dependency name follows npm naming conventions.\n * Must be non-empty, not start with a dot or underscore (unless scoped),\n * and contain only valid characters.\n */\nfunction isValidDependencyName(name: string): boolean {\n if (!name || typeof name !== 'string') return false;\n if (name.length > 214) return false;\n\n // Scoped packages: @scope/name\n if (name.startsWith('@')) {\n const parts = name.split('/');\n if (parts.length !== 2) return false;\n if (!parts[0] || !parts[1]) return false;\n return true;\n }\n\n // Unscoped: must not start with . or _\n if (name.startsWith('.') || name.startsWith('_')) return false;\n\n return true;\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * NPM Version Extraction Plugin for KiroGraph-Sec\n *\n * Extends the existing architecture npm parser to extract version constraints,\n * resolved versions (from lock files), and dependency scopes for security analysis.\n *\n * Reuses `npmParser` from `src/architecture/manifest/npm.ts` for discovery and\n * basic parsing, then layers on version/scope extraction.\n */\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport type { ParsedDependency } from '../../types';\nimport { logWarn } from '../../../errors';\n\n/** Scope fields in package.json mapped to their ParsedDependency scope */\nconst SCOPE_FIELDS: Array<{ field: string; scope: ParsedDependency['scope'] }> = [\n { field: 'dependencies', scope: 'production' },\n { field: 'devDependencies', scope: 'development' },\n { field: 'optionalDependencies', scope: 'optional' },\n];\n\n/**\n * Resolved version map: package name \u2192 resolved version string.\n * Built from package-lock.json or yarn.lock.\n */\ntype ResolvedVersionMap = Map<string, string>;\n\n/** Package name \u2192 SPDX license string. Built from package-lock.json's \"packages\" map. */\ntype LicenseMap = Map<string, string>;\n\n/**\n * Parse an npm package.json manifest and extract dependency declarations\n * with version constraints, scopes, and resolved versions from lock files.\n *\n * @param manifestPath - Absolute path to the package.json file\n * @param projectRoot - Absolute path to the project root directory\n * @returns Array of parsed dependencies with version and scope information\n */\nexport async function parseNpmManifest(\n manifestPath: string,\n projectRoot: string,\n): Promise<ParsedDependency[]> {\n const manifestDir = path.dirname(manifestPath);\n const relativeManifest = path.relative(projectRoot, manifestPath).replace(/\\\\/g, '/');\n\n // Read and parse the package.json\n let pkg: Record<string, unknown>;\n try {\n const raw = fs.readFileSync(manifestPath, 'utf8');\n const parsed = JSON.parse(raw);\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n logWarn(`[sec:npm] Invalid package.json structure at ${relativeManifest}`);\n return [];\n }\n pkg = parsed as Record<string, unknown>;\n } catch (err) {\n logWarn(`[sec:npm] Failed to read/parse ${relativeManifest}: ${err instanceof Error ? err.message : String(err)}`);\n return [];\n }\n\n // Attempt to load resolved versions from lock file\n const resolvedVersions = loadResolvedVersions(manifestDir);\n\n // Per-package licenses, keyed by package name \u2014 read from package-lock.json's\n // \"packages\" map (npm v7+ lockfile v2/v3 records each installed package's own\n // \"license\" field there). This is each dependency's *own* license, not the\n // consuming project's \u2014 see loadNpmLicenses() for why that distinction matters.\n const licenses = loadNpmLicenses(manifestDir);\n\n // Extract dependencies from each scope field\n const dependencies: ParsedDependency[] = [];\n const warnings: Array<{ name: string; reason: string }> = [];\n\n for (const { field, scope } of SCOPE_FIELDS) {\n const depsObj = pkg[field];\n if (depsObj === undefined || depsObj === null) continue;\n\n if (typeof depsObj !== 'object' || Array.isArray(depsObj)) {\n logWarn(`[sec:npm] Invalid \"${field}\" field in ${relativeManifest} \u2014 expected object, got ${typeof depsObj}`);\n continue;\n }\n\n const entries = Object.entries(depsObj as Record<string, unknown>);\n for (const [name, constraint] of entries) {\n // Validate the dependency entry\n if (!isValidDependencyName(name)) {\n logWarn(`[sec:npm] Invalid dependency name \"${name}\" in ${relativeManifest} (${field}) \u2014 skipping`);\n warnings.push({ name, reason: 'invalid name' });\n continue;\n }\n\n if (typeof constraint !== 'string' || constraint.trim() === '') {\n logWarn(`[sec:npm] Invalid version constraint for \"${name}\" in ${relativeManifest} (${field}) \u2014 skipping`);\n warnings.push({ name, reason: 'invalid constraint' });\n continue;\n }\n\n const resolvedVersion = resolvedVersions.get(name);\n\n const license = licenses.get(name);\n\n dependencies.push({\n name,\n declaredConstraint: constraint,\n resolvedVersion,\n scope,\n ecosystem: 'npm',\n sourceManifest: relativeManifest,\n ...(license !== undefined ? { license } : {}),\n });\n }\n }\n\n // Also handle peerDependencies \u2014 default to production scope\n const peerDeps = pkg.peerDependencies;\n if (peerDeps && typeof peerDeps === 'object' && !Array.isArray(peerDeps)) {\n const peerEntries = Object.entries(peerDeps as Record<string, unknown>);\n for (const [name, constraint] of peerEntries) {\n if (!isValidDependencyName(name)) {\n logWarn(`[sec:npm] Invalid peer dependency name \"${name}\" in ${relativeManifest} \u2014 skipping`);\n warnings.push({ name, reason: 'invalid name' });\n continue;\n }\n\n if (typeof constraint !== 'string' || constraint.trim() === '') {\n logWarn(`[sec:npm] Invalid version constraint for peer dependency \"${name}\" in ${relativeManifest} \u2014 skipping`);\n warnings.push({ name, reason: 'invalid constraint' });\n continue;\n }\n\n // Only add if not already declared in another scope\n const alreadyDeclared = dependencies.some(d => d.name === name);\n if (!alreadyDeclared) {\n const resolvedVersion = resolvedVersions.get(name);\n const license = licenses.get(name);\n dependencies.push({\n name,\n declaredConstraint: constraint,\n resolvedVersion,\n scope: 'production',\n ecosystem: 'npm',\n sourceManifest: relativeManifest,\n ...(license !== undefined ? { license } : {}),\n });\n }\n }\n }\n\n // Transitive dependencies (present only in the lock file, never declared\n // directly in package.json) would otherwise never become a Dependency_Node,\n // so they'd silently skip vulnerability scanning entirely. Add them here.\n const directNames = new Set(dependencies.map(d => d.name));\n const lockRelativePath = relativeManifest.replace(/package\\.json$/, 'package-lock.json');\n const npmLockPath = path.join(manifestDir, 'package-lock.json');\n if (fs.existsSync(npmLockPath)) {\n // package-lock.json (v2/v3) carries a per-package \"dev\" flag \u2014 parse it\n // directly so transitive dev-only dependencies keep the right scope.\n dependencies.push(...extractTransitiveNpmDependencies(manifestDir, lockRelativePath, directNames));\n } else {\n // pnpm-lock.yaml, yarn.lock, or a lockfileVersion 1 package-lock.json:\n // resolvedVersions already has every package the lock file resolved,\n // direct or transitive \u2014 it just doesn't carry a dev/prod distinction,\n // so default newly-added transitive entries to 'production'.\n for (const [name, version] of resolvedVersions) {\n if (directNames.has(name)) continue;\n dependencies.push({\n name,\n declaredConstraint: version,\n resolvedVersion: version,\n scope: 'production',\n ecosystem: 'npm',\n sourceManifest: relativeManifest,\n });\n }\n }\n\n return dependencies;\n}\n\n/**\n * Find every npm package name that resolves to more than one distinct\n * version somewhere in package-lock.json's dependency tree. npm's nested\n * node_modules layout can install different versions of the same package\n * at different paths (e.g. a top-level `qs@6.16.0` alongside a\n * `body-parser`-nested `qs@6.15.3`) \u2014 but a Dependency_Node only records one\n * `resolved_version`. Used by vulnerability enrichment to also check every\n * other installed copy, not just whichever version ended up recorded.\n *\n * Only the top-level package-lock.json (lockfile v2/v3 \"packages\" map) is\n * checked \u2014 a monorepo with per-package lock files, or pnpm/yarn projects,\n * won't get this extra check (they still get the single recorded version).\n *\n * Returns a map from package name to the full set of distinct versions\n * found; names with only one version are omitted.\n */\nexport function findNpmDuplicateVersions(projectRoot: string): Map<string, Set<string>> {\n const versionsByName = new Map<string, Set<string>>();\n\n const lockPath = path.join(projectRoot, 'package-lock.json');\n if (!fs.existsSync(lockPath)) return versionsByName;\n\n let lockData: unknown;\n try {\n lockData = JSON.parse(fs.readFileSync(lockPath, 'utf8'));\n } catch {\n return versionsByName;\n }\n\n if (typeof lockData !== 'object' || lockData === null) return versionsByName;\n const packages = (lockData as Record<string, unknown>).packages;\n if (typeof packages !== 'object' || packages === null) return versionsByName;\n\n for (const [key, value] of Object.entries(packages as Record<string, unknown>)) {\n if (key === '') continue; // skip the root project entry\n if (typeof value !== 'object' || value === null) continue;\n const pkg = value as Record<string, unknown>;\n\n const version = pkg.version;\n if (typeof version !== 'string') continue;\n\n const name = key.replace(/^.*node_modules\\//, '');\n if (!name) continue;\n\n if (!versionsByName.has(name)) versionsByName.set(name, new Set());\n versionsByName.get(name)!.add(version);\n }\n\n for (const [name, versions] of versionsByName) {\n if (versions.size <= 1) versionsByName.delete(name);\n }\n\n return versionsByName;\n}\n\n/**\n * Extract every package listed in package-lock.json's \"packages\" map (lockfile\n * v2/v3) that isn't already a direct dependency. These are transitive-only\n * packages \u2014 e.g. a sub-dependency three levels deep \u2014 that `npm audit` reports\n * on but that package.json never mentions.\n */\nfunction extractTransitiveNpmDependencies(\n manifestDir: string,\n lockRelativePath: string,\n directNames: Set<string>,\n): ParsedDependency[] {\n const lockPath = path.join(manifestDir, 'package-lock.json');\n if (!fs.existsSync(lockPath)) return [];\n\n let lockData: unknown;\n try {\n lockData = JSON.parse(fs.readFileSync(lockPath, 'utf8'));\n } catch (err) {\n logWarn(`[sec:npm] Failed to parse ${lockRelativePath} for transitive dependencies: ${err instanceof Error ? err.message : String(err)}`);\n return [];\n }\n\n if (typeof lockData !== 'object' || lockData === null) return [];\n const packages = (lockData as Record<string, unknown>).packages;\n if (typeof packages !== 'object' || packages === null) return [];\n\n const transitive: ParsedDependency[] = [];\n const seen = new Set<string>();\n\n for (const [key, value] of Object.entries(packages as Record<string, unknown>)) {\n if (key === '') continue; // skip the root project entry\n if (typeof value !== 'object' || value === null) continue;\n const pkg = value as Record<string, unknown>;\n\n const name = key.replace(/^.*node_modules\\//, '');\n if (!name || directNames.has(name) || seen.has(name)) continue;\n\n const version = pkg.version;\n if (typeof version !== 'string') continue;\n\n seen.add(name);\n const license = normalizeLicenseField(pkg);\n transitive.push({\n name,\n // No real semver range exists at the project level for a transitive\n // package \u2014 the resolved version is the only constraint we have.\n declaredConstraint: version,\n resolvedVersion: version,\n scope: pkg.dev === true ? 'development' : 'production',\n ecosystem: 'npm',\n sourceManifest: lockRelativePath,\n ...(license !== undefined ? { license } : {}),\n });\n }\n\n return transitive;\n}\n\n/**\n * Attempt to load resolved versions from package-lock.json, pnpm-lock.yaml,\n * or yarn.lock in the given directory. Returns an empty map if none is available.\n */\nfunction loadResolvedVersions(manifestDir: string): ResolvedVersionMap {\n const map: ResolvedVersionMap = new Map();\n\n // Try package-lock.json first (preferred \u2014 npm)\n const lockPath = path.join(manifestDir, 'package-lock.json');\n if (fs.existsSync(lockPath)) {\n try {\n const lockContent = fs.readFileSync(lockPath, 'utf8');\n const lockData = JSON.parse(lockContent);\n extractFromPackageLock(lockData, map);\n return map;\n } catch (err) {\n logWarn(`[sec:npm] Failed to parse package-lock.json: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n\n // Try pnpm-lock.yaml (pnpm)\n const pnpmLockPath = path.join(manifestDir, 'pnpm-lock.yaml');\n if (fs.existsSync(pnpmLockPath)) {\n try {\n const pnpmContent = fs.readFileSync(pnpmLockPath, 'utf8');\n extractFromPnpmLock(pnpmContent, map);\n return map;\n } catch (err) {\n logWarn(`[sec:npm] Failed to parse pnpm-lock.yaml: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n\n // Fall back to yarn.lock\n const yarnLockPath = path.join(manifestDir, 'yarn.lock');\n if (fs.existsSync(yarnLockPath)) {\n try {\n const yarnContent = fs.readFileSync(yarnLockPath, 'utf8');\n extractFromYarnLock(yarnContent, map);\n return map;\n } catch (err) {\n logWarn(`[sec:npm] Failed to parse yarn.lock: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n\n return map;\n}\n\n/**\n * Load each installed package's own SPDX license from package-lock.json's\n * \"packages\" map (npm v7+ lockfile v2/v3). npm records the \"license\" field\n * from each package's own package.json there when it exists \u2014 this is the\n * dependency's *own* license, not the consuming project's, so it's the\n * correct source for license-compliance checks (issue #39 follow-up: the\n * project's own root package.json \"license\" field was previously applied to\n * every dependency, which is both wrong when present and leaves every\n * transitive-only package \u2014 the majority in most trees \u2014 as \"unknown\").\n *\n * pnpm-lock.yaml and yarn.lock don't carry a comparably structured per-package\n * license field, so this only covers package-lock.json for now; other lock\n * formats fall back to no license data, same as before this fix.\n */\nfunction loadNpmLicenses(manifestDir: string): LicenseMap {\n const map: LicenseMap = new Map();\n\n const lockPath = path.join(manifestDir, 'package-lock.json');\n if (!fs.existsSync(lockPath)) return map;\n\n let lockData: unknown;\n try {\n lockData = JSON.parse(fs.readFileSync(lockPath, 'utf8'));\n } catch (err) {\n logWarn(`[sec:npm] Failed to parse package-lock.json for license data: ${err instanceof Error ? err.message : String(err)}`);\n return map;\n }\n\n if (typeof lockData !== 'object' || lockData === null) return map;\n const packages = (lockData as Record<string, unknown>).packages;\n if (typeof packages !== 'object' || packages === null) return map;\n\n for (const [key, value] of Object.entries(packages as Record<string, unknown>)) {\n if (key === '') continue; // skip the root project entry\n if (typeof value !== 'object' || value === null) continue;\n const pkg = value as Record<string, unknown>;\n\n const name = key.replace(/^.*node_modules\\//, '');\n if (!name || map.has(name)) continue;\n\n const license = normalizeLicenseField(pkg);\n if (license !== undefined) map.set(name, license);\n }\n\n return map;\n}\n\n/**\n * Extract resolved versions from pnpm-lock.yaml (v5, v6, v9 formats).\n *\n * v5/v6: packages section with keys like \"/express/4.18.2\" or \"/express@4.18.2\"\n * v9: snapshots section with keys like \"express@4.18.2\" and\n * packages section with \"express@4.18.2: {version: ...}\" (optional)\n */\nfunction extractFromPnpmLock(content: string, map: ResolvedVersionMap): void {\n // v9 format: package entries under `packages:` or `snapshots:`\n // Key format: \"name@version\" or \"@scope/name@version\"\n // We extract name@version pairs from both sections.\n\n // Match lines like: /express/4.18.2: or /express@4.18.2: or express@4.18.2:\n for (const m of content.matchAll(/^[\\s]*\\/?(@?[a-zA-Z0-9._-]+(?:\\/[a-zA-Z0-9._-]+)?)[@/]([0-9][a-zA-Z0-9._-]*)(?:\\(.*?\\))?:/gm)) {\n const name = m[1].replace(/^\\//, ''); // strip leading slash for scoped packages\n const version = m[2];\n if (name && version && !map.has(name)) {\n map.set(name, version);\n }\n }\n}\n\n/**\n * Extract resolved versions from package-lock.json (supports v2 and v3 format).\n */\nfunction extractFromPackageLock(lockData: unknown, map: ResolvedVersionMap): void {\n if (typeof lockData !== 'object' || lockData === null) return;\n const lock = lockData as Record<string, unknown>;\n\n // lockfileVersion 2/3: uses \"packages\" field with \"\" as root\n const packages = lock.packages;\n if (typeof packages === 'object' && packages !== null) {\n for (const [key, value] of Object.entries(packages as Record<string, unknown>)) {\n if (key === '') continue; // skip root\n if (typeof value !== 'object' || value === null) continue;\n const pkg = value as Record<string, unknown>;\n const version = pkg.version;\n if (typeof version !== 'string') continue;\n\n // Key format: \"node_modules/<name>\" or \"node_modules/@scope/name\"\n const name = key.replace(/^.*node_modules\\//, '');\n if (name && !map.has(name)) {\n map.set(name, version);\n }\n }\n return;\n }\n\n // lockfileVersion 1: uses \"dependencies\" field\n const dependencies = lock.dependencies;\n if (typeof dependencies === 'object' && dependencies !== null) {\n extractFromLockV1Dependencies(dependencies as Record<string, unknown>, map);\n }\n}\n\n/**\n * Recursively extract versions from lockfileVersion 1 dependencies.\n */\nfunction extractFromLockV1Dependencies(\n deps: Record<string, unknown>,\n map: ResolvedVersionMap,\n): void {\n for (const [name, value] of Object.entries(deps)) {\n if (typeof value !== 'object' || value === null) continue;\n const dep = value as Record<string, unknown>;\n const version = dep.version;\n if (typeof version === 'string' && !map.has(name)) {\n map.set(name, version);\n }\n // Recurse into nested dependencies\n const nested = dep.dependencies;\n if (typeof nested === 'object' && nested !== null) {\n extractFromLockV1Dependencies(nested as Record<string, unknown>, map);\n }\n }\n}\n\n/**\n * Extract resolved versions from yarn.lock (v1 format).\n * yarn.lock uses a custom format, not JSON.\n */\nfunction extractFromYarnLock(content: string, map: ResolvedVersionMap): void {\n const lines = content.split('\\n');\n let currentPackageName: string | null = null;\n\n for (const line of lines) {\n // Skip comments and empty lines\n if (line.startsWith('#') || line.trim() === '') {\n currentPackageName = null;\n continue;\n }\n\n // Package header line: \"package-name@version-range:\" or \"@scope/name@range:\"\n // Can also be: \"package-name@range1, package-name@range2:\"\n if (!line.startsWith(' ') && line.endsWith(':')) {\n const header = line.slice(0, -1); // remove trailing ':'\n // Extract package name from the first entry (before @version)\n const name = extractPackageNameFromYarnHeader(header);\n currentPackageName = name;\n continue;\n }\n\n // Version line: \" version \"x.y.z\"\"\n if (currentPackageName && line.trim().startsWith('version ')) {\n const versionMatch = line.match(/version\\s+\"([^\"]+)\"/);\n if (versionMatch && !map.has(currentPackageName)) {\n map.set(currentPackageName, versionMatch[1]);\n }\n currentPackageName = null;\n }\n }\n}\n\n/**\n * Extract the package name from a yarn.lock header line.\n * Handles formats like:\n * - \"express@^4.17.1\"\n * - \"@types/node@^18.0.0\"\n * - \"express@^4.17.1, express@^4.18.0\"\n * - '\"express@^4.17.1\"'\n */\nfunction extractPackageNameFromYarnHeader(header: string): string | null {\n // Take the first entry if comma-separated\n const firstEntry = header.split(',')[0].trim();\n // Remove surrounding quotes if present\n const cleaned = firstEntry.replace(/^[\"']|[\"']$/g, '');\n\n // Find the last @ that separates name from version\n // For scoped packages (@scope/name@version), we need the last @\n const lastAtIndex = cleaned.lastIndexOf('@');\n if (lastAtIndex <= 0) return null; // no @ or starts with @ (scoped without version)\n\n // For scoped packages, check if the first char is @\n if (cleaned.startsWith('@')) {\n // @scope/name@version \u2014 find the @ after the scope\n const afterScope = cleaned.indexOf('/', 1);\n if (afterScope === -1) return null;\n const atAfterName = cleaned.indexOf('@', afterScope);\n if (atAfterName === -1) return cleaned; // no version part\n return cleaned.slice(0, atAfterName);\n }\n\n return cleaned.slice(0, lastAtIndex);\n}\n\n/**\n * Normalize a package's `license`/`licenses` field into a single SPDX-ish\n * string. Handles the string format (`\"MIT\"`), the object format\n * (`{\"type\":\"MIT\"}`), and the legacy `licenses` array format. Used both for\n * package-lock.json's per-package `license` entries and, if ever needed,\n * a raw package.json object.\n */\nfunction normalizeLicenseField(pkg: Record<string, unknown>): string | undefined {\n const raw = pkg['license'];\n if (typeof raw === 'string' && raw.trim() !== '') {\n return raw.trim();\n }\n if (typeof raw === 'object' && raw !== null && !Array.isArray(raw)) {\n const licObj = raw as Record<string, unknown>;\n if (typeof licObj['type'] === 'string' && licObj['type'].trim() !== '') {\n return licObj['type'].trim();\n }\n }\n // Handle legacy \"licenses\" array\n const licenses = pkg['licenses'];\n if (Array.isArray(licenses) && licenses.length > 0) {\n const first = licenses[0];\n if (typeof first === 'object' && first !== null) {\n const lic = first as Record<string, unknown>;\n if (typeof lic['type'] === 'string' && lic['type'].trim() !== '') {\n return lic['type'].trim();\n }\n }\n if (typeof first === 'string' && first.trim() !== '') {\n return first.trim();\n }\n }\n return undefined;\n}\n\n/**\n * Validate that a dependency name follows npm naming conventions.\n * Must be non-empty, not start with a dot or underscore (unless scoped),\n * and contain only valid characters.\n */\nfunction isValidDependencyName(name: string): boolean {\n if (!name || typeof name !== 'string') return false;\n if (name.length > 214) return false;\n\n // Scoped packages: @scope/name\n if (name.startsWith('@')) {\n const parts = name.split('/');\n if (parts.length !== 2) return false;\n if (!parts[0] || !parts[1]) return false;\n return true;\n }\n\n // Unscoped: must not start with . or _\n if (name.startsWith('.') || name.startsWith('_')) return false;\n\n return true;\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,136 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+ var json_schema_exports = {};
30
+ __export(json_schema_exports, {
31
+ SchemaLoadError: () => SchemaLoadError,
32
+ loadJsonSchema: () => loadJsonSchema,
33
+ validateAgainstSchema: () => validateAgainstSchema
34
+ });
35
+ module.exports = __toCommonJS(json_schema_exports);
36
+ var fs = __toESM(require("fs"));
37
+ const DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
38
+ function typeOf(value) {
39
+ if (value === null) return "null";
40
+ if (Array.isArray(value)) return "array";
41
+ return typeof value;
42
+ }
43
+ function describe(value) {
44
+ if (typeof value === "string") return JSON.stringify(value);
45
+ if (value === void 0) return "undefined";
46
+ return JSON.stringify(value);
47
+ }
48
+ function validateAgainstSchema(data, schema, pathPrefix = "") {
49
+ const errors = [];
50
+ const label = pathPrefix || "(root)";
51
+ if (schema.type) {
52
+ const actual = typeOf(data);
53
+ const matches = schema.type === "integer" ? actual === "number" && Number.isInteger(data) : actual === schema.type;
54
+ if (!matches) {
55
+ errors.push(`${label}: expected type ${schema.type}, got ${actual}`);
56
+ return errors;
57
+ }
58
+ }
59
+ if (schema.enum && !schema.enum.some((v) => v === data)) {
60
+ errors.push(`${label}: expected one of ${schema.enum.map(describe).join(", ")}, got ${describe(data)}`);
61
+ }
62
+ if (typeof data === "string") {
63
+ if (schema.pattern && !new RegExp(schema.pattern).test(data)) {
64
+ errors.push(`${label}: does not match pattern ${schema.pattern}`);
65
+ }
66
+ if (schema.minLength !== void 0 && data.length < schema.minLength) {
67
+ errors.push(`${label}: shorter than minLength ${schema.minLength}`);
68
+ }
69
+ if (schema.maxLength !== void 0 && data.length > schema.maxLength) {
70
+ errors.push(`${label}: longer than maxLength ${schema.maxLength}`);
71
+ }
72
+ if (schema.format === "date" && !DATE_RE.test(data)) {
73
+ errors.push(`${label}: expected format date (YYYY-MM-DD), got ${describe(data)}`);
74
+ }
75
+ }
76
+ if (typeof data === "number") {
77
+ if (schema.minimum !== void 0 && data < schema.minimum) {
78
+ errors.push(`${label}: below minimum ${schema.minimum}`);
79
+ }
80
+ if (schema.maximum !== void 0 && data > schema.maximum) {
81
+ errors.push(`${label}: above maximum ${schema.maximum}`);
82
+ }
83
+ }
84
+ if (schema.properties && typeOf(data) === "object") {
85
+ const obj = data;
86
+ for (const key of schema.required ?? []) {
87
+ if (!(key in obj) || obj[key] === void 0) {
88
+ errors.push(`${pathPrefix ? pathPrefix + "." : ""}${key}: missing required field`);
89
+ }
90
+ }
91
+ for (const [key, value] of Object.entries(obj)) {
92
+ const childPath = pathPrefix ? `${pathPrefix}.${key}` : key;
93
+ const propSchema = schema.properties[key];
94
+ if (!propSchema) {
95
+ if (schema.additionalProperties === false) {
96
+ errors.push(`${childPath}: unknown field, not allowed by schema`);
97
+ }
98
+ continue;
99
+ }
100
+ errors.push(...validateAgainstSchema(value, propSchema, childPath));
101
+ }
102
+ }
103
+ if (schema.items && Array.isArray(data)) {
104
+ data.forEach((item, i) => {
105
+ errors.push(...validateAgainstSchema(item, schema.items, `${pathPrefix}[${i}]`));
106
+ });
107
+ }
108
+ return errors;
109
+ }
110
+ class SchemaLoadError extends Error {
111
+ constructor(message) {
112
+ super(message);
113
+ this.name = "SchemaLoadError";
114
+ }
115
+ }
116
+ function loadJsonSchema(schemaPath) {
117
+ if (!fs.existsSync(schemaPath)) return null;
118
+ let raw;
119
+ try {
120
+ raw = fs.readFileSync(schemaPath, "utf8");
121
+ } catch (err) {
122
+ throw new SchemaLoadError(`Failed to read schema "${schemaPath}": ${err instanceof Error ? err.message : String(err)}`);
123
+ }
124
+ try {
125
+ return JSON.parse(raw);
126
+ } catch (err) {
127
+ throw new SchemaLoadError(`Schema "${schemaPath}" is not valid JSON: ${err instanceof Error ? err.message : String(err)}`);
128
+ }
129
+ }
130
+ // Annotate the CommonJS export names for ESM import in node:
131
+ 0 && (module.exports = {
132
+ SchemaLoadError,
133
+ loadJsonSchema,
134
+ validateAgainstSchema
135
+ });
136
+ //# sourceMappingURL=json-schema.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/shared/json-schema.ts"],
4
+ "sourcesContent": ["/**\n * KiroGraph \u2014 Minimal JSON Schema subset validator\n *\n * No runtime dependency (ajv, etc.) \u2014 this validates the subset of JSON\n * Schema actually needed for opt-in structured-field validation in the\n * memory (memorySchemaValidation) and wiki (wikiTypedPages) modules:\n * type, enum, required, properties (recursive), items (arrays),\n * additionalProperties, pattern, min/maxLength, minimum/maximum, and a\n * basic `format: \"date\"` check. Unknown schema keywords (title,\n * description, $schema, x-* extensions, etc.) are ignored rather than\n * rejected, so a schema authored for a fuller validator still loads here.\n */\n\nimport * as fs from 'fs';\n\nexport interface JsonSchema {\n type?: 'string' | 'number' | 'integer' | 'boolean' | 'array' | 'object' | 'null';\n enum?: unknown[];\n required?: string[];\n properties?: Record<string, JsonSchema>;\n items?: JsonSchema;\n additionalProperties?: boolean;\n pattern?: string;\n minLength?: number;\n maxLength?: number;\n minimum?: number;\n maximum?: number;\n format?: string;\n [key: string]: unknown;\n}\n\nconst DATE_RE = /^\\d{4}-\\d{2}-\\d{2}$/;\n\nfunction typeOf(value: unknown): string {\n if (value === null) return 'null';\n if (Array.isArray(value)) return 'array';\n return typeof value;\n}\n\nfunction describe(value: unknown): string {\n if (typeof value === 'string') return JSON.stringify(value);\n if (value === undefined) return 'undefined';\n return JSON.stringify(value);\n}\n\n/**\n * Validate `data` against `schema`, returning one human-readable message per\n * violation (empty array = valid). `pathPrefix` is prepended to field paths\n * for nested calls \u2014 pass '' at the top level.\n */\nexport function validateAgainstSchema(data: unknown, schema: JsonSchema, pathPrefix = ''): string[] {\n const errors: string[] = [];\n const label = pathPrefix || '(root)';\n\n if (schema.type) {\n const actual = typeOf(data);\n const matches = schema.type === 'integer'\n ? actual === 'number' && Number.isInteger(data)\n : actual === schema.type;\n if (!matches) {\n errors.push(`${label}: expected type ${schema.type}, got ${actual}`);\n return errors; // further checks would be meaningless against the wrong type\n }\n }\n\n if (schema.enum && !schema.enum.some(v => v === data)) {\n errors.push(`${label}: expected one of ${schema.enum.map(describe).join(', ')}, got ${describe(data)}`);\n }\n\n if (typeof data === 'string') {\n if (schema.pattern && !new RegExp(schema.pattern).test(data)) {\n errors.push(`${label}: does not match pattern ${schema.pattern}`);\n }\n if (schema.minLength !== undefined && data.length < schema.minLength) {\n errors.push(`${label}: shorter than minLength ${schema.minLength}`);\n }\n if (schema.maxLength !== undefined && data.length > schema.maxLength) {\n errors.push(`${label}: longer than maxLength ${schema.maxLength}`);\n }\n if (schema.format === 'date' && !DATE_RE.test(data)) {\n errors.push(`${label}: expected format date (YYYY-MM-DD), got ${describe(data)}`);\n }\n }\n\n if (typeof data === 'number') {\n if (schema.minimum !== undefined && data < schema.minimum) {\n errors.push(`${label}: below minimum ${schema.minimum}`);\n }\n if (schema.maximum !== undefined && data > schema.maximum) {\n errors.push(`${label}: above maximum ${schema.maximum}`);\n }\n }\n\n if (schema.properties && typeOf(data) === 'object') {\n const obj = data as Record<string, unknown>;\n for (const key of schema.required ?? []) {\n if (!(key in obj) || obj[key] === undefined) {\n errors.push(`${pathPrefix ? pathPrefix + '.' : ''}${key}: missing required field`);\n }\n }\n for (const [key, value] of Object.entries(obj)) {\n const childPath = pathPrefix ? `${pathPrefix}.${key}` : key;\n const propSchema = schema.properties[key];\n if (!propSchema) {\n if (schema.additionalProperties === false) {\n errors.push(`${childPath}: unknown field, not allowed by schema`);\n }\n continue;\n }\n errors.push(...validateAgainstSchema(value, propSchema, childPath));\n }\n }\n\n if (schema.items && Array.isArray(data)) {\n data.forEach((item, i) => {\n errors.push(...validateAgainstSchema(item, schema.items as JsonSchema, `${pathPrefix}[${i}]`));\n });\n }\n\n return errors;\n}\n\nexport class SchemaLoadError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'SchemaLoadError';\n }\n}\n\n/**\n * Load a `.schema.json` file from `schemaPath`. Returns null when the file\n * doesn't exist \u2014 schemas are opt-in per kind/type, so \"no schema for this\n * kind\" is a normal, silent no-op rather than an error. Throws\n * SchemaLoadError when the file exists but isn't valid JSON, since that's\n * an authoring mistake worth surfacing.\n */\nexport function loadJsonSchema(schemaPath: string): JsonSchema | null {\n if (!fs.existsSync(schemaPath)) return null;\n let raw: string;\n try {\n raw = fs.readFileSync(schemaPath, 'utf8');\n } catch (err) {\n throw new SchemaLoadError(`Failed to read schema \"${schemaPath}\": ${err instanceof Error ? err.message : String(err)}`);\n }\n try {\n return JSON.parse(raw) as JsonSchema;\n } catch (err) {\n throw new SchemaLoadError(`Schema \"${schemaPath}\" is not valid JSON: ${err instanceof Error ? err.message : String(err)}`);\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,174 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var yaml_exports = {};
20
+ __export(yaml_exports, {
21
+ parseYaml: () => parseYaml
22
+ });
23
+ module.exports = __toCommonJS(yaml_exports);
24
+ function parseYaml(text) {
25
+ const lines = text.split("\n");
26
+ return parseBlock(lines, 0, 0).result;
27
+ }
28
+ function parseBlock(lines, startIndex, baseIndent) {
29
+ const result = {};
30
+ let i = startIndex;
31
+ while (i < lines.length) {
32
+ const rawLine = lines[i];
33
+ const commentIdx = rawLine.indexOf(" #");
34
+ const line = commentIdx >= 0 ? rawLine.slice(0, commentIdx) : rawLine;
35
+ if (line.trim() === "" || line.trim().startsWith("#")) {
36
+ i++;
37
+ continue;
38
+ }
39
+ const indent = countIndent(rawLine);
40
+ if (indent < baseIndent) break;
41
+ if (indent > baseIndent && Object.keys(result).length === 0) {
42
+ i++;
43
+ continue;
44
+ }
45
+ if (indent > baseIndent) {
46
+ break;
47
+ }
48
+ const trimmed = line.trim();
49
+ const colonIdx = trimmed.indexOf(":");
50
+ if (colonIdx <= 0) {
51
+ i++;
52
+ continue;
53
+ }
54
+ const key = trimmed.slice(0, colonIdx).trim();
55
+ const rest = trimmed.slice(colonIdx + 1).trimStart();
56
+ if (rest === "" || rest === "|" || rest === ">") {
57
+ const nextContentLine = findNextContent(lines, i + 1);
58
+ if (nextContentLine === -1) {
59
+ result[key] = null;
60
+ i++;
61
+ continue;
62
+ }
63
+ const nextIndent = countIndent(lines[nextContentLine]);
64
+ if (nextIndent > indent) {
65
+ if (lines[nextContentLine].trim().startsWith("- ")) {
66
+ const { items, nextIndex } = parseSequence(lines, nextContentLine, nextIndent);
67
+ result[key] = items;
68
+ i = nextIndex;
69
+ } else {
70
+ const { result: subResult, nextIndex } = parseBlock(lines, nextContentLine, nextIndent);
71
+ result[key] = subResult;
72
+ i = nextIndex;
73
+ }
74
+ } else {
75
+ result[key] = null;
76
+ i++;
77
+ }
78
+ } else if (rest.startsWith("[")) {
79
+ result[key] = parseInlineArray(rest);
80
+ i++;
81
+ } else {
82
+ result[key] = parseScalar(rest);
83
+ i++;
84
+ }
85
+ }
86
+ return { result, nextIndex: i };
87
+ }
88
+ function parseSequence(lines, startIndex, baseIndent) {
89
+ const items = [];
90
+ let i = startIndex;
91
+ while (i < lines.length) {
92
+ const rawLine = lines[i];
93
+ if (rawLine.trim() === "" || rawLine.trim().startsWith("#")) {
94
+ i++;
95
+ continue;
96
+ }
97
+ const indent = countIndent(rawLine);
98
+ if (indent < baseIndent) break;
99
+ const trimmed = rawLine.trim();
100
+ if (!trimmed.startsWith("- ") && trimmed !== "-") {
101
+ break;
102
+ }
103
+ const itemValue = trimmed.startsWith("- ") ? trimmed.slice(2).trim() : "";
104
+ if (itemValue === "" || itemValue === "|" || itemValue === ">") {
105
+ const nextContentLine = findNextContent(lines, i + 1);
106
+ if (nextContentLine !== -1 && countIndent(lines[nextContentLine]) > indent) {
107
+ const { result: subResult, nextIndex } = parseBlock(lines, nextContentLine, countIndent(lines[nextContentLine]));
108
+ items.push(subResult);
109
+ i = nextIndex;
110
+ } else {
111
+ items.push(null);
112
+ i++;
113
+ }
114
+ } else if (itemValue.includes(":")) {
115
+ const itemIndent = indent + 2;
116
+ const continuationLines = [];
117
+ let j = i + 1;
118
+ while (j < lines.length) {
119
+ const nextRaw = lines[j];
120
+ if (nextRaw.trim() === "" || nextRaw.trim().startsWith("#")) {
121
+ j++;
122
+ continue;
123
+ }
124
+ if (countIndent(nextRaw) >= itemIndent) {
125
+ continuationLines.push(nextRaw);
126
+ j++;
127
+ } else {
128
+ break;
129
+ }
130
+ }
131
+ const blockLines = [" ".repeat(itemIndent) + itemValue, ...continuationLines];
132
+ const { result: subResult } = parseBlock(blockLines, 0, itemIndent);
133
+ items.push(subResult);
134
+ i = j;
135
+ } else {
136
+ items.push(parseScalar(itemValue));
137
+ i++;
138
+ }
139
+ }
140
+ return { items, nextIndex: i };
141
+ }
142
+ function parseInlineArray(text) {
143
+ const inner = text.replace(/^\[/, "").replace(/\].*$/, "");
144
+ if (inner.trim() === "") return [];
145
+ return inner.split(",").map((s) => parseScalar(s.trim()));
146
+ }
147
+ function parseScalar(text) {
148
+ if (text === "true") return true;
149
+ if (text === "false") return false;
150
+ if (text === "null" || text === "~") return null;
151
+ if (/^-?\d+$/.test(text)) return parseInt(text, 10);
152
+ if (/^-?\d+\.\d+$/.test(text)) return parseFloat(text);
153
+ if (text.startsWith('"') && text.endsWith('"') || text.startsWith("'") && text.endsWith("'")) {
154
+ return text.slice(1, -1);
155
+ }
156
+ return text;
157
+ }
158
+ function countIndent(line) {
159
+ let i = 0;
160
+ while (i < line.length && line[i] === " ") i++;
161
+ return i;
162
+ }
163
+ function findNextContent(lines, startIndex) {
164
+ for (let i = startIndex; i < lines.length; i++) {
165
+ const t = lines[i].trim();
166
+ if (t !== "" && !t.startsWith("#")) return i;
167
+ }
168
+ return -1;
169
+ }
170
+ // Annotate the CommonJS export names for ESM import in node:
171
+ 0 && (module.exports = {
172
+ parseYaml
173
+ });
174
+ //# sourceMappingURL=yaml.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/shared/yaml.ts"],
4
+ "sourcesContent": ["/**\n * KiroGraph \u2014 Minimal YAML parser\n *\n * js-yaml is not a declared dependency, so this handles the subset of YAML\n * used across KiroGraph (pattern rule files, wiki frontmatter):\n * - Flat key: value (string, boolean, number)\n * - key: [item1, item2] (inline array)\n * - key:\\n - item (block sequence for simple values)\n * - nested-block:\\n (nested block as raw sub-object \u2014 parsed recursively)\n * - Quoted strings: \"...\" and '...'\n * - Comments: # ...\n *\n * Originally lived inline in src/patterns/loader.ts; extracted so wiki\n * frontmatter parsing (typed-pages.ts) can reuse it without duplication.\n */\n\nexport function parseYaml(text: string): Record<string, unknown> {\n const lines = text.split('\\n');\n return parseBlock(lines, 0, 0).result;\n}\n\ninterface BlockResult {\n result: Record<string, unknown>;\n nextIndex: number;\n}\n\nfunction parseBlock(lines: string[], startIndex: number, baseIndent: number): BlockResult {\n const result: Record<string, unknown> = {};\n let i = startIndex;\n\n while (i < lines.length) {\n const rawLine = lines[i];\n // Strip trailing comments (but not inside strings \u2014 simplified approach)\n const commentIdx = rawLine.indexOf(' #');\n const line = commentIdx >= 0 ? rawLine.slice(0, commentIdx) : rawLine;\n\n // Skip blank lines\n if (line.trim() === '' || line.trim().startsWith('#')) {\n i++;\n continue;\n }\n\n const indent = countIndent(rawLine);\n\n // If we've de-indented past our base, stop\n if (indent < baseIndent) break;\n\n // Skip lines that are at an indent level below our block's base\n if (indent > baseIndent && Object.keys(result).length === 0) {\n // Haven't started the block yet \u2014 shouldn't happen but skip\n i++;\n continue;\n }\n\n if (indent > baseIndent) {\n // This belongs to a child block already being processed \u2014 stop\n break;\n }\n\n const trimmed = line.trim();\n\n // Detect key: ... pattern\n const colonIdx = trimmed.indexOf(':');\n if (colonIdx <= 0) {\n i++;\n continue;\n }\n\n const key = trimmed.slice(0, colonIdx).trim();\n const rest = trimmed.slice(colonIdx + 1).trimStart();\n\n if (rest === '' || rest === '|' || rest === '>') {\n // Value is on the following lines \u2014 could be a nested block or block scalar\n // Peek ahead: if next non-empty line is indented more, recurse\n const nextContentLine = findNextContent(lines, i + 1);\n if (nextContentLine === -1) {\n result[key] = null;\n i++;\n continue;\n }\n const nextIndent = countIndent(lines[nextContentLine]);\n if (nextIndent > indent) {\n // Check if next line is a sequence item (- ...)\n if (lines[nextContentLine].trim().startsWith('- ')) {\n const { items, nextIndex } = parseSequence(lines, nextContentLine, nextIndent);\n result[key] = items;\n i = nextIndex;\n } else {\n const { result: subResult, nextIndex } = parseBlock(lines, nextContentLine, nextIndent);\n result[key] = subResult;\n i = nextIndex;\n }\n } else {\n result[key] = null;\n i++;\n }\n } else if (rest.startsWith('[')) {\n // Inline array: [a, b, c]\n result[key] = parseInlineArray(rest);\n i++;\n } else {\n // Scalar value\n result[key] = parseScalar(rest);\n i++;\n }\n }\n\n return { result, nextIndex: i };\n}\n\nfunction parseSequence(lines: string[], startIndex: number, baseIndent: number): { items: unknown[]; nextIndex: number } {\n const items: unknown[] = [];\n let i = startIndex;\n\n while (i < lines.length) {\n const rawLine = lines[i];\n if (rawLine.trim() === '' || rawLine.trim().startsWith('#')) {\n i++;\n continue;\n }\n const indent = countIndent(rawLine);\n if (indent < baseIndent) break;\n\n const trimmed = rawLine.trim();\n if (!trimmed.startsWith('- ') && trimmed !== '-') {\n break;\n }\n\n const itemValue = trimmed.startsWith('- ') ? trimmed.slice(2).trim() : '';\n\n if (itemValue === '' || itemValue === '|' || itemValue === '>') {\n // Multi-line sequence item\n const nextContentLine = findNextContent(lines, i + 1);\n if (nextContentLine !== -1 && countIndent(lines[nextContentLine]) > indent) {\n const { result: subResult, nextIndex } = parseBlock(lines, nextContentLine, countIndent(lines[nextContentLine]));\n items.push(subResult);\n i = nextIndex;\n } else {\n items.push(null);\n i++;\n }\n } else if (itemValue.includes(':')) {\n // Inline key:value item \u2014 parse as sub-object, collecting any indented continuation lines\n const itemIndent = indent + 2;\n const continuationLines: string[] = [];\n let j = i + 1;\n while (j < lines.length) {\n const nextRaw = lines[j];\n if (nextRaw.trim() === '' || nextRaw.trim().startsWith('#')) { j++; continue; }\n if (countIndent(nextRaw) >= itemIndent) {\n continuationLines.push(nextRaw);\n j++;\n } else {\n break;\n }\n }\n const blockLines = [' '.repeat(itemIndent) + itemValue, ...continuationLines];\n const { result: subResult } = parseBlock(blockLines, 0, itemIndent);\n items.push(subResult);\n i = j;\n } else {\n items.push(parseScalar(itemValue));\n i++;\n }\n }\n\n return { items, nextIndex: i };\n}\n\nfunction parseInlineArray(text: string): unknown[] {\n const inner = text.replace(/^\\[/, '').replace(/\\].*$/, '');\n if (inner.trim() === '') return [];\n return inner.split(',').map(s => parseScalar(s.trim()));\n}\n\nfunction parseScalar(text: string): unknown {\n if (text === 'true') return true;\n if (text === 'false') return false;\n if (text === 'null' || text === '~') return null;\n if (/^-?\\d+$/.test(text)) return parseInt(text, 10);\n if (/^-?\\d+\\.\\d+$/.test(text)) return parseFloat(text);\n // Quoted string\n if ((text.startsWith('\"') && text.endsWith('\"')) || (text.startsWith(\"'\") && text.endsWith(\"'\"))) {\n return text.slice(1, -1);\n }\n return text;\n}\n\nfunction countIndent(line: string): number {\n let i = 0;\n while (i < line.length && line[i] === ' ') i++;\n return i;\n}\n\nfunction findNextContent(lines: string[], startIndex: number): number {\n for (let i = startIndex; i < lines.length; i++) {\n const t = lines[i].trim();\n if (t !== '' && !t.startsWith('#')) return i;\n }\n return -1;\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -44,7 +44,9 @@ class KiroGraphWiki {
44
44
  constructor(db, kirographDir, opts = {}) {
45
45
  this.wikiDb = new import_database.WikiDatabase(db);
46
46
  this.wikiDir = path.join(kirographDir, "wiki");
47
+ this.wikiSchemasDir = path.join(kirographDir, "wiki-schemas");
47
48
  this.autoResolveConflicts = opts.autoResolveConflicts ?? false;
49
+ this.typedPages = opts.typedPages ?? false;
48
50
  }
49
51
  initialize() {
50
52
  this.wikiDb.initialize();
@@ -119,7 +121,7 @@ class KiroGraphWiki {
119
121
  // ── Lint ───────────────────────────────────────────────────────────────────
120
122
  lint() {
121
123
  this.initialize();
122
- return (0, import_lint.lintWiki)(this.wikiDb);
124
+ return (0, import_lint.lintWiki)(this.wikiDb, this.typedPages ? { wikiSchemasDir: this.wikiSchemasDir } : {});
123
125
  }
124
126
  // ── Links ──────────────────────────────────────────────────────────────────
125
127
  /** Outgoing/broken/incoming `[[slug]]` links for one page. Null if the slug is unknown. */
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/wiki/index.ts"],
4
- "sourcesContent": ["/**\n * KiroGraph Wiki \u2014 Public API\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { WikiDatabase } from './database';\nimport { DEFAULT_SCHEMA } from './schema';\nimport { parseWikiDiff } from './schema';\nimport { buildIngestPrompt, applyDiff, reindexFromDisk, updateManifest } from './ingest';\nimport { lintWiki } from './lint';\nimport { getPageLinks, type WikiPageLinks } from './links';\nimport { renamePage, type RenamePageResult } from './rename';\nimport type {\n WikiPage,\n ScoredWikiPage,\n WikiDiff,\n WikiLintIssue,\n WikiStats,\n} from './types';\n\nexport type { WikiPage, ScoredWikiPage, WikiDiff, WikiLintIssue, WikiStats, WikiPageLinks, RenamePageResult };\n\nexport class KiroGraphWiki {\n private wikiDb: WikiDatabase;\n private wikiDir: string;\n private autoResolveConflicts: boolean;\n\n constructor(\n db: any,\n kirographDir: string,\n opts: { autoResolveConflicts?: boolean } = {}\n ) {\n this.wikiDb = new WikiDatabase(db);\n this.wikiDir = path.join(kirographDir, 'wiki');\n this.autoResolveConflicts = opts.autoResolveConflicts ?? false;\n }\n\n initialize(): void {\n this.wikiDb.initialize();\n fs.mkdirSync(this.wikiDir, { recursive: true });\n }\n\n // \u2500\u2500 Init \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n initWiki(): void {\n this.initialize();\n const schemaPath = path.join(this.wikiDir, 'SCHEMA.md');\n if (!fs.existsSync(schemaPath)) {\n fs.writeFileSync(schemaPath, DEFAULT_SCHEMA, 'utf8');\n }\n const manifestPath = path.join(this.wikiDir, 'MANIFEST.md');\n if (!fs.existsSync(manifestPath)) {\n fs.writeFileSync(manifestPath, '# Wiki Manifest\\n\\n(empty)\\n', 'utf8');\n }\n }\n\n // \u2500\u2500 Ingest \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /**\n * Returns the ingest prompt for the LLM (agent mode).\n * The agent uses this to produce a WIKI_DIFF, then calls applyDiff.\n */\n getIngestPrompt(source: string, sourceName = 'source'): string {\n this.initialize();\n return buildIngestPrompt(this.wikiDir, source, sourceName);\n }\n\n // \u2500\u2500 Queue (local synthesis mode) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n queueSource(sourceText: string, sourceName: string): void {\n this.initialize();\n this.wikiDb.queueSource(sourceName, sourceText);\n }\n\n getPendingQueue(): Array<{ id: number; sourceName: string; sourceText: string }> {\n this.initialize();\n return this.wikiDb.getPendingQueue();\n }\n\n clearQueue(): void {\n this.initialize();\n this.wikiDb.clearQueue();\n }\n\n getQueueCount(): number {\n this.initialize();\n return this.wikiDb.getQueueCount();\n }\n\n async synthesize(modelName: string, quiet = false) {\n this.initialize();\n const { runWikiLocalSynthesis } = await Promise.resolve().then(() => require('./synthesize.js'));\n return runWikiLocalSynthesis(this, modelName, quiet);\n }\n\n /**\n * Parse and apply a WIKI_DIFF string produced by the LLM.\n */\n applyDiff(rawDiff: string) {\n this.initialize();\n const diff = parseWikiDiff(rawDiff);\n return applyDiff(diff, this.wikiDir, this.wikiDb, {\n autoResolveConflicts: this.autoResolveConflicts,\n });\n }\n\n // \u2500\u2500 Search & Read \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n search(query: string, limit = 10): ScoredWikiPage[] {\n this.initialize();\n return this.wikiDb.search(query, limit);\n }\n\n getPage(slug: string): WikiPage | null {\n this.initialize();\n return this.wikiDb.getPage(slug);\n }\n\n listPages(): WikiPage[] {\n this.initialize();\n return this.wikiDb.listPages();\n }\n\n // \u2500\u2500 Lint \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n lint(): WikiLintIssue[] {\n this.initialize();\n return lintWiki(this.wikiDb);\n }\n\n // \u2500\u2500 Links \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /** Outgoing/broken/incoming `[[slug]]` links for one page. Null if the slug is unknown. */\n getLinks(slug: string): WikiPageLinks | null {\n this.initialize();\n return getPageLinks(this.wikiDb, slug);\n }\n\n /** Rename a page's slug, moving its file and rewriting every `[[slug]]` reference to it. */\n rename(fromSlug: string, toSlug: string): RenamePageResult {\n this.initialize();\n return renamePage(this.wikiDir, this.wikiDb, fromSlug, toSlug);\n }\n\n // \u2500\u2500 Reindex \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n reindex(): number {\n this.initialize();\n return reindexFromDisk(this.wikiDir, this.wikiDb);\n }\n\n // \u2500\u2500 Stats \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n getStats(): WikiStats {\n this.initialize();\n return this.wikiDb.getStats();\n }\n\n // \u2500\u2500 Context enrichment \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /**\n * Returns wiki pages relevant to a query, for inclusion in kirograph_context.\n * Returns up to `limit` pages with score >= threshold.\n */\n getContextPages(query: string, limit = 3, threshold = 0.4): WikiPage[] {\n this.initialize();\n return this.wikiDb\n .search(query, limit * 2)\n .filter(r => r.score >= threshold)\n .slice(0, limit)\n .map(r => r.page);\n }\n\n get dir(): string {\n return this.wikiDir;\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * KiroGraph Wiki \u2014 Public API\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { WikiDatabase } from './database';\nimport { DEFAULT_SCHEMA } from './schema';\nimport { parseWikiDiff } from './schema';\nimport { buildIngestPrompt, applyDiff, reindexFromDisk, updateManifest } from './ingest';\nimport { lintWiki } from './lint';\nimport { getPageLinks, type WikiPageLinks } from './links';\nimport { renamePage, type RenamePageResult } from './rename';\nimport type {\n WikiPage,\n ScoredWikiPage,\n WikiDiff,\n WikiLintIssue,\n WikiStats,\n} from './types';\n\nexport type { WikiPage, ScoredWikiPage, WikiDiff, WikiLintIssue, WikiStats, WikiPageLinks, RenamePageResult };\n\nexport class KiroGraphWiki {\n private wikiDb: WikiDatabase;\n private wikiDir: string;\n private wikiSchemasDir: string;\n private autoResolveConflicts: boolean;\n private typedPages: boolean;\n\n constructor(\n db: any,\n kirographDir: string,\n opts: { autoResolveConflicts?: boolean; typedPages?: boolean } = {}\n ) {\n this.wikiDb = new WikiDatabase(db);\n this.wikiDir = path.join(kirographDir, 'wiki');\n this.wikiSchemasDir = path.join(kirographDir, 'wiki-schemas');\n this.autoResolveConflicts = opts.autoResolveConflicts ?? false;\n this.typedPages = opts.typedPages ?? false;\n }\n\n initialize(): void {\n this.wikiDb.initialize();\n fs.mkdirSync(this.wikiDir, { recursive: true });\n }\n\n // \u2500\u2500 Init \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n initWiki(): void {\n this.initialize();\n const schemaPath = path.join(this.wikiDir, 'SCHEMA.md');\n if (!fs.existsSync(schemaPath)) {\n fs.writeFileSync(schemaPath, DEFAULT_SCHEMA, 'utf8');\n }\n const manifestPath = path.join(this.wikiDir, 'MANIFEST.md');\n if (!fs.existsSync(manifestPath)) {\n fs.writeFileSync(manifestPath, '# Wiki Manifest\\n\\n(empty)\\n', 'utf8');\n }\n }\n\n // \u2500\u2500 Ingest \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /**\n * Returns the ingest prompt for the LLM (agent mode).\n * The agent uses this to produce a WIKI_DIFF, then calls applyDiff.\n */\n getIngestPrompt(source: string, sourceName = 'source'): string {\n this.initialize();\n return buildIngestPrompt(this.wikiDir, source, sourceName);\n }\n\n // \u2500\u2500 Queue (local synthesis mode) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n queueSource(sourceText: string, sourceName: string): void {\n this.initialize();\n this.wikiDb.queueSource(sourceName, sourceText);\n }\n\n getPendingQueue(): Array<{ id: number; sourceName: string; sourceText: string }> {\n this.initialize();\n return this.wikiDb.getPendingQueue();\n }\n\n clearQueue(): void {\n this.initialize();\n this.wikiDb.clearQueue();\n }\n\n getQueueCount(): number {\n this.initialize();\n return this.wikiDb.getQueueCount();\n }\n\n async synthesize(modelName: string, quiet = false) {\n this.initialize();\n const { runWikiLocalSynthesis } = await Promise.resolve().then(() => require('./synthesize.js'));\n return runWikiLocalSynthesis(this, modelName, quiet);\n }\n\n /**\n * Parse and apply a WIKI_DIFF string produced by the LLM.\n */\n applyDiff(rawDiff: string) {\n this.initialize();\n const diff = parseWikiDiff(rawDiff);\n return applyDiff(diff, this.wikiDir, this.wikiDb, {\n autoResolveConflicts: this.autoResolveConflicts,\n });\n }\n\n // \u2500\u2500 Search & Read \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n search(query: string, limit = 10): ScoredWikiPage[] {\n this.initialize();\n return this.wikiDb.search(query, limit);\n }\n\n getPage(slug: string): WikiPage | null {\n this.initialize();\n return this.wikiDb.getPage(slug);\n }\n\n listPages(): WikiPage[] {\n this.initialize();\n return this.wikiDb.listPages();\n }\n\n // \u2500\u2500 Lint \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n lint(): WikiLintIssue[] {\n this.initialize();\n return lintWiki(this.wikiDb, this.typedPages ? { wikiSchemasDir: this.wikiSchemasDir } : {});\n }\n\n // \u2500\u2500 Links \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /** Outgoing/broken/incoming `[[slug]]` links for one page. Null if the slug is unknown. */\n getLinks(slug: string): WikiPageLinks | null {\n this.initialize();\n return getPageLinks(this.wikiDb, slug);\n }\n\n /** Rename a page's slug, moving its file and rewriting every `[[slug]]` reference to it. */\n rename(fromSlug: string, toSlug: string): RenamePageResult {\n this.initialize();\n return renamePage(this.wikiDir, this.wikiDb, fromSlug, toSlug);\n }\n\n // \u2500\u2500 Reindex \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n reindex(): number {\n this.initialize();\n return reindexFromDisk(this.wikiDir, this.wikiDb);\n }\n\n // \u2500\u2500 Stats \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n getStats(): WikiStats {\n this.initialize();\n return this.wikiDb.getStats();\n }\n\n // \u2500\u2500 Context enrichment \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /**\n * Returns wiki pages relevant to a query, for inclusion in kirograph_context.\n * Returns up to `limit` pages with score >= threshold.\n */\n getContextPages(query: string, limit = 3, threshold = 0.4): WikiPage[] {\n this.initialize();\n return this.wikiDb\n .search(query, limit * 2)\n .filter(r => r.score >= threshold)\n .slice(0, limit)\n .map(r => r.page);\n }\n\n get dir(): string {\n return this.wikiDir;\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["import_schema"]
7
7
  }
package/dist/wiki/lint.js CHANGED
@@ -22,11 +22,24 @@ __export(lint_exports, {
22
22
  });
23
23
  module.exports = __toCommonJS(lint_exports);
24
24
  var import_links = require("./links");
25
- function lintWiki(wikiDb) {
25
+ var import_typed_pages = require("./typed-pages");
26
+ function lintWiki(wikiDb, opts = {}) {
26
27
  const issues = [];
27
28
  const pages = wikiDb.listPages();
28
29
  const slugSet = new Set(pages.map((p) => p.slug));
29
30
  for (const page of pages) {
31
+ if (opts.wikiSchemasDir) {
32
+ const { type, fields } = (0, import_typed_pages.parseFrontmatter)(page.content);
33
+ if (type) {
34
+ for (const violation of (0, import_typed_pages.validatePageSchema)(fields, type, opts.wikiSchemasDir)) {
35
+ issues.push({
36
+ kind: "schema_error",
37
+ slug: page.slug,
38
+ detail: `[${type}] ${violation}`
39
+ });
40
+ }
41
+ }
42
+ }
30
43
  for (const linked of (0, import_links.extractLinks)(page.content)) {
31
44
  if (!slugSet.has(linked)) {
32
45
  issues.push({