lib/project-facts
lib/project-facts.js
Functions(19)
Read and JSON-parse a file, returning `fallback` on any error.
| Parameter | Type | Optional | Description |
|---|---|---|---|
filePath | string | ||
fallback | * |
Returns:
* Signature
readJsonSafe(filePath: any, fallback: any): any
Node version facts: the package's declared minimum vs. what CI actually
tests against. Surfaced separately and explicitly -- not collapsed into
one answer -- because they may legitimately differ (see story-dev-onboarding-dashboard AC1).
| Parameter | Type | Optional | Description |
|---|---|---|---|
rootDir | string |
Returns:
{declaredMin: (string|null), ciTested: number[], matches: boolean} Signature
getNodeVersions(rootDir: any): any
Which package manager this repo uses, detected from lockfile presence.
| Parameter | Type | Optional | Description |
|---|---|---|---|
rootDir | string |
Returns:
{manager: string, lockfile: (string|null)} Signature
getPackageManager(rootDir: any): any
Global (machine-wide) dependencies required to work on this repo.
Static, but reasoned from the same repo-state signals as the rest of this
module -- zero devDependencies historically meant zero global tooling
beyond Node/npm/git; the new code-multivitals devDependency doesn't
change that (it's invoked via its programmatic API, never a global install
or npx network fetch in CI since `npm ci` already resolves it).
| Parameter | Type | Optional | Description |
|---|---|---|---|
rootDir | string |
Returns:
{required: string[], conditionallyRequired: string[]} Signature
getGlobalDependencies(rootDir: any): any
Count files directly inside `dirPath`, grouped by extension (e.g.
`{ ".ts": 12, ".test.js": 3 }`). Non-recursive -- one directory's own
files only, computed in the same readdir pass the walk already does.
| Parameter | Type | Optional | Description |
|---|---|---|---|
dirPath | string |
Returns:
object Signature
summarizeFileExtensions(dirPath: any): any
Recursively describe one directory: its known/fallback description, a
file-extension summary, and (while under `maxDepth`) its child
directories in the same shape.
| Parameter | Type | Optional | Description |
|---|---|---|---|
dirPath | string | ||
name | string | ||
depth | number | - 1-based depth of this directory from rootDir. | |
maxDepth | number |
Returns:
object Signature
describeDirectory(dirPath: any, name: any, depth: any, maxDepth: any): any
Nested map of the project structure, generated by walking the actual
filesystem (not hand-maintained). Each entry reports its own
description, a per-extension file-count summary, and -- while under the
configured depth -- its child directories in the same shape.
| Parameter | Type | Optional | Description |
|---|---|---|---|
rootDir | string | ||
options | {depth: number} | yes | - max walk depth from rootDir, default 3. |
Returns:
{name: string, description: string, files: object, children: (Array|undefined)}[] Signature
getProjectStructure(rootDir: any, options: any): any
Resolve npm workspace packages declared in the root `package.json`'s
`workspaces` field into concrete facts, read from each package's own
`package.json` -- never hand-typed. Supports `"dir/*"` glob-style
patterns (this repo's own convention, and the overwhelmingly common
shape in practice) plus exact directory entries.
| Parameter | Type | Optional | Description |
|---|---|---|---|
rootDir | string |
Returns:
{name: string, path: string, description: (string|null)}[] Signature
getWorkspacePackages(rootDir: any): any
Collect every `dependencies`/`devDependencies`/`peerDependencies` key
from one `package.json`.
| Parameter | Type | Optional | Description |
|---|---|---|---|
pkgJsonPath | string |
Returns:
Set<string> Signature
collectDependencyNames(pkgJsonPath: any): any
Walk a `getProjectStructure()` result (including nested `children`)
collecting every extension key seen, so the file-heuristic fallback
can reuse Phase 1's already-computed walk instead of touching the
filesystem a second time.
| Parameter | Type | Optional | Description |
|---|---|---|---|
structure | object[] |
Returns:
Set<string> Signature
collectExtensionsFromStructure(structure: any): any
Dependency-based framework/stack detection: which of a fixed set of
frameworks (React, Next.js, Angular, Vue, Express, NestJS) this project
appears to use, with evidence for every signal -- never a bare label.
Reads `package.json` dependency fields (root + every workspace package
from `getWorkspacePackages`) as primary evidence; falls back to
`getProjectStructure`'s existing file-extension counts (no second
filesystem walk) when a framework-shaped file extension is present but
no matching dependency was found anywhere.
reuse instead of walking the tree again (2026-07-31: getAllFacts() already
walks once at depth 4 for the `structure` field -- passing it through here
avoids a second, redundant filesystem walk). Falls back to its own walk
when called standalone (e.g. directly from tests).
| Parameter | Type | Optional | Description |
|---|---|---|---|
rootDir | string | ||
precomputedStructure | object[] | yes | - a `getProjectStructure()` result to |
Returns:
{name: string, confidence: ("dependency"|"file-heuristic"), evidence: string}[] Signature
getFrameworkSignals(rootDir: any, precomputedStructure: any): any
Collect every directory name appearing anywhere in a
`getProjectStructure()` result (including nested `children`,
recursively) into a Set. Reused by getArchitectureSignals() instead of
a second filesystem walk.
| Parameter | Type | Optional | Description |
|---|---|---|---|
structure | object[] |
Returns:
Set<string> Signature
collectDirectoryNamesFromStructure(structure: any): any
Architecture-pattern signals: independent, evidence-backed
observations (never a single forced "the architecture is X" label --
see adr-architecture-pattern-signals.md). Every rule is deterministic
and reuses Phase 1 (`getProjectStructure`, `getWorkspacePackages`) and
Phase 2 (`getFrameworkSignals`) output -- zero new filesystem calls.
param doc -- threaded through to both the local dirName check and the
internal getFrameworkSignals() call so this function costs zero extra
filesystem walks when called from getAllFacts().
| Parameter | Type | Optional | Description |
|---|---|---|---|
rootDir | string | ||
precomputedStructure | object[] | yes | - see getFrameworkSignals()'s |
Returns:
{name: string, evidence: string}[] Signature
getArchitectureSignals(rootDir: any, precomputedStructure: any): any
Recursively find the first structure node matching `name` at any depth
(e.g. "features" nested as src/features/, not just a rootDir-level
directory) -- most real projects put this kind of folder under src/,
not at the repo root, so a depth-1-only search would miss them.
| Parameter | Type | Optional | Description |
|---|---|---|---|
structure | object[] | ||
name | string |
Returns:
object|null Signature
findStructureNodeByName(structure: any, name: any): any
A features/modules node "is" Vertical Slice (rather than plain
Feature-Based) when at least one of its own child directories itself
contains 2+ of the layer-shaped subdirectory names above.
| Parameter | Type | Optional | Description |
|---|---|---|---|
node | object | - a getProjectStructure() node (already has .children). |
Returns:
boolean Signature
isVerticalSliceNode(node: any): any
Detects which of a fixed reference catalog of common architectural
patterns (ARCHITECTURE_PATTERN_DEFINITIONS) this project's own directory
names, dependencies, and root-level files match. Every returned entry
carries the same evidence-or-nothing discipline as
getArchitectureSignals() -- a pattern only appears here when `detect()`
found real, cited evidence, never a guess. This is the deeper companion
to getArchitectureSignals()'s lighter "what kind of project is this"
signals -- "what architecture pattern does this codebase actually use".
to reuse instead of walking the filesystem again (getAllFacts() passes
its own already-computed `structure` field here). Falls back to a fresh
depth-4 walk when omitted, e.g. when this function is called standalone.
Depth is capped at 4, not deeper: measured directly against this repo's
own tree, depth 3->4 cost ~700ms extra (709ms -> 1413ms) but depth 4->5
cost ~6.4s more (7.8s) and depth 6 took ~19s -- a generated/versioned
output directory sitting inside the scanned root (this repo's own
docs-dashboard/site-versions/, which nests full copies of prior site
generations) blows up combinatorially past depth 4. A real user's
--out directory can just as easily live inside the directory being
documented, so this cap protects every caller, not just this repo's
own dogfooding case.
| Parameter | Type | Optional | Description |
|---|---|---|---|
rootDir | string | ||
precomputedStructure | object[] | yes | - a `getProjectStructure()` result |
Returns:
{name: string, description: string, link: string, evidence: string}[] Signature
getArchitecturePatterns(rootDir: any, precomputedStructure: any): any
Test-tooling facts: no framework, hand-rolled runner, no HTTP API.
| Parameter | Type | Optional | Description |
|---|---|---|---|
rootDir | string |
Returns:
{framework: (string|null), runner: string, suiteCount: (number|null), hasHttpApi: boolean} Signature
getTestInfo(rootDir: any): any
Variables & Constants(91)
const fs: any
constlib/project-facts.js
----------------------------------------
Onboarding-facts generator for the internal project dashboard (Track A,
see docs/backlog/adr-phase-j-project-dashboard.md). Every fact is derived
by reading the repo's actual current state -- package.json, CI workflow
YAML, the real directory tree -- never hand-typed prose that can drift
out of sync with reality.
Signature
const fs: any
Signature
const KNOWN_DIR_DESCRIPTIONS: Object
Signature
const FRAMEWORK_MARKERS: Object
Signature
const FILE_HEURISTIC_RULES: Array
Signature
const LAYERED_DIR_NAME_GROUPS: Array
Signature
const dependencyFrameworks: any
Signature
const matchedLayeredGroups: any
Signature
const ARCHITECTURE_PATTERN_DEFINITIONS: Array
Signature
const VERTICAL_SLICE_LAYER_DIR_NAMES: Array