Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
185 changes: 185 additions & 0 deletions .nightshift/knowledge-silo-report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
# Knowledge Silo Analysis

> Generated: 2026-02-22 | Silo threshold: 80% | Recency window: 90 days

## Per-Module Bus Factor

| Module | Bus Factor | Top Author | Top % | Commits | Lines |
|--------|----------:|-----------:|------:|--------:|------:|
| src/commanders/textfresser | 1 | clockblocker | 100.0% | 500 | 20550 |
| src/documentaion | 1 | clockblocker | 100.0% | 139 | 12863 |
| src/main.ts | 1 | clockblocker | 100.0% | 249 | 10446 |
| src/types.ts | 1 | clockblocker | 100.0% | 32 | 265 |
| src/linguistics | 1 | clockblocker | 100.0% | 118 | 3441 |
| src/prompt-smith/codegen | 1 | clockblocker | 100.0% | 181 | 5136 |
| src/prompt-smith/prompt-parts | 1 | clockblocker | 100.0% | 361 | 5574 |
| src/prompt-smith/schemas | 1 | clockblocker | 100.0% | 77 | 757 |
| src/prompt-smith/index.ts | 1 | clockblocker | 100.0% | 18 | 237 |
| src/commanders/librarian | 1 | clockblocker | 100.0% | 1315 | 57537 |
| src/stateless-helpers | 1 | clockblocker | 100.0% | 85 | 2853 |
| src/managers/obsidian | 1 | clockblocker | 100.0% | 554 | 21034 |
| src/types | 1 | clockblocker | 100.0% | 227 | 10617 |
| src/utils | 1 | clockblocker | 100.0% | 17 | 590 |
| src/managers/overlay-manager | 1 | clockblocker | 100.0% | 144 | 3284 |
| src/main-stripped.ts | 1 | clockblocker | 100.0% | 5 | 31 |
| src/prompt-smith/types.ts | 1 | clockblocker | 100.0% | 5 | 39 |
| src/global-state | 1 | clockblocker | 100.0% | 11 | 127 |
| src/todo.md | 1 | clockblocker | 100.0% | 7 | 121 |

## Identified Knowledge Silos

### 🔴 src/commanders/textfresser — HIGH

- **Top author**: clockblocker (100.0% of commits)
- **Bus factor**: 1
- **Total commits**: 500
- **Last other-author commit**: never

### 🔴 src/documentaion — HIGH

- **Top author**: clockblocker (100.0% of commits)
- **Bus factor**: 1
- **Total commits**: 139
- **Last other-author commit**: never

### 🔴 src/main.ts — HIGH

- **Top author**: clockblocker (100.0% of commits)
- **Bus factor**: 1
- **Total commits**: 249
- **Last other-author commit**: never

### 🔴 src/types.ts — HIGH

- **Top author**: clockblocker (100.0% of commits)
- **Bus factor**: 1
- **Total commits**: 32
- **Last other-author commit**: never

### 🔴 src/linguistics — HIGH

- **Top author**: clockblocker (100.0% of commits)
- **Bus factor**: 1
- **Total commits**: 118
- **Last other-author commit**: never

### 🔴 src/prompt-smith/codegen — HIGH

- **Top author**: clockblocker (100.0% of commits)
- **Bus factor**: 1
- **Total commits**: 181
- **Last other-author commit**: never

### 🔴 src/prompt-smith/prompt-parts — HIGH

- **Top author**: clockblocker (100.0% of commits)
- **Bus factor**: 1
- **Total commits**: 361
- **Last other-author commit**: never

### 🔴 src/prompt-smith/schemas — HIGH

- **Top author**: clockblocker (100.0% of commits)
- **Bus factor**: 1
- **Total commits**: 77
- **Last other-author commit**: never

### 🔴 src/prompt-smith/index.ts — HIGH

- **Top author**: clockblocker (100.0% of commits)
- **Bus factor**: 1
- **Total commits**: 18
- **Last other-author commit**: never

### 🔴 src/commanders/librarian — HIGH

- **Top author**: clockblocker (100.0% of commits)
- **Bus factor**: 1
- **Total commits**: 1315
- **Last other-author commit**: never

### 🔴 src/stateless-helpers — HIGH

- **Top author**: clockblocker (100.0% of commits)
- **Bus factor**: 1
- **Total commits**: 85
- **Last other-author commit**: never

### 🔴 src/managers/obsidian — HIGH

- **Top author**: clockblocker (100.0% of commits)
- **Bus factor**: 1
- **Total commits**: 554
- **Last other-author commit**: never

### 🔴 src/types — HIGH

- **Top author**: clockblocker (100.0% of commits)
- **Bus factor**: 1
- **Total commits**: 227
- **Last other-author commit**: never

### 🔴 src/utils — HIGH

- **Top author**: clockblocker (100.0% of commits)
- **Bus factor**: 1
- **Total commits**: 17
- **Last other-author commit**: never

### 🔴 src/managers/overlay-manager — HIGH

- **Top author**: clockblocker (100.0% of commits)
- **Bus factor**: 1
- **Total commits**: 144
- **Last other-author commit**: never

### 🔴 src/main-stripped.ts — HIGH

- **Top author**: clockblocker (100.0% of commits)
- **Bus factor**: 1
- **Total commits**: 5
- **Last other-author commit**: never

### 🔴 src/prompt-smith/types.ts — HIGH

- **Top author**: clockblocker (100.0% of commits)
- **Bus factor**: 1
- **Total commits**: 5
- **Last other-author commit**: never

### 🔴 src/global-state — HIGH

- **Top author**: clockblocker (100.0% of commits)
- **Bus factor**: 1
- **Total commits**: 11
- **Last other-author commit**: never

### 🔴 src/todo.md — HIGH

- **Top author**: clockblocker (100.0% of commits)
- **Bus factor**: 1
- **Total commits**: 7
- **Last other-author commit**: never

## Recommended Cross-Training Areas

**Priority 1 — Immediate attention:**
- `src/commanders/textfresser`: Only clockblocker has meaningful ownership. Pair-program or do code reviews with a second contributor.
- `src/documentaion`: Only clockblocker has meaningful ownership. Pair-program or do code reviews with a second contributor.
- `src/main.ts`: Only clockblocker has meaningful ownership. Pair-program or do code reviews with a second contributor.
- `src/types.ts`: Only clockblocker has meaningful ownership. Pair-program or do code reviews with a second contributor.
- `src/linguistics`: Only clockblocker has meaningful ownership. Pair-program or do code reviews with a second contributor.
- `src/prompt-smith/codegen`: Only clockblocker has meaningful ownership. Pair-program or do code reviews with a second contributor.
- `src/prompt-smith/prompt-parts`: Only clockblocker has meaningful ownership. Pair-program or do code reviews with a second contributor.
- `src/prompt-smith/schemas`: Only clockblocker has meaningful ownership. Pair-program or do code reviews with a second contributor.
- `src/prompt-smith/index.ts`: Only clockblocker has meaningful ownership. Pair-program or do code reviews with a second contributor.
- `src/commanders/librarian`: Only clockblocker has meaningful ownership. Pair-program or do code reviews with a second contributor.
- `src/stateless-helpers`: Only clockblocker has meaningful ownership. Pair-program or do code reviews with a second contributor.
- `src/managers/obsidian`: Only clockblocker has meaningful ownership. Pair-program or do code reviews with a second contributor.
- `src/types`: Only clockblocker has meaningful ownership. Pair-program or do code reviews with a second contributor.
- `src/utils`: Only clockblocker has meaningful ownership. Pair-program or do code reviews with a second contributor.
- `src/managers/overlay-manager`: Only clockblocker has meaningful ownership. Pair-program or do code reviews with a second contributor.
- `src/main-stripped.ts`: Only clockblocker has meaningful ownership. Pair-program or do code reviews with a second contributor.
- `src/prompt-smith/types.ts`: Only clockblocker has meaningful ownership. Pair-program or do code reviews with a second contributor.
- `src/global-state`: Only clockblocker has meaningful ownership. Pair-program or do code reviews with a second contributor.
- `src/todo.md`: Only clockblocker has meaningful ownership. Pair-program or do code reviews with a second contributor.
85 changes: 64 additions & 21 deletions scripts/knowledge-silo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,32 @@
* Analyzes git history to detect modules where a single contributor dominates
* ownership, indicating a "knowledge silo" risk (low bus factor).
*
* Usage: bun scripts/knowledge-silo.ts [--days=N] [--threshold=N]
* Usage: bun scripts/knowledge-silo.ts [--days=N] [--threshold=N] [--json] [--output=<path>]
* --days Recency window for silo detection (default: 90)
* --threshold Ownership % above which a single author is flagged (default: 80)
* --json Output machine-readable JSON instead of markdown
* --output Write output to a file instead of stdout
*/

import { existsSync } from "node:fs";
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
import { dirname } from "node:path";
import { $ } from "bun";

// ── Config ──────────────────────────────────────────────────────────────────

interface CliArgs {
export interface CliArgs {
recencyDays: number;
siloThreshold: number;
json: boolean;
output: string | null;
}

function parseArgs(): CliArgs {
const args = process.argv.slice(2);
let recencyDays = 90;
let siloThreshold = 80;
let json = false;
let output: string | null = null;

for (const arg of args) {
const daysMatch = arg.match(/^--days=(\d+)$/);
Expand All @@ -37,30 +44,39 @@ function parseArgs(): CliArgs {
siloThreshold = Number(thresholdMatch[1]);
continue;
}
if (arg === "--json") {
json = true;
continue;
}
const outputMatch = arg.match(/^--output=(.+)$/);
if (outputMatch) {
output = outputMatch[1]!;
continue;
}
}

return { recencyDays, siloThreshold };
return { json, output, recencyDays, siloThreshold };
}

// ── Types ───────────────────────────────────────────────────────────────────

interface AuthorStats {
export interface AuthorStats {
linesAdded: number;
linesDeleted: number;
commits: number;
lastCommitDate: Date;
}

interface FileStats {
export interface FileStats {
authors: Map<string, AuthorStats>;
}

interface ModuleStats {
export interface ModuleStats {
files: number;
authors: Map<string, AuthorStats>;
}

interface SiloReport {
export interface SiloReport {
module: string;
busFactor: number;
topAuthor: string;
Expand All @@ -75,7 +91,7 @@ interface SiloReport {
// ── Module Classification ───────────────────────────────────────────────────

/** Maps a file path to its logical module name (2-level deep for key dirs). */
function classifyModule(filePath: string): string | null {
export function classifyModule(filePath: string): string | null {
if (!filePath.startsWith("src/")) return null;

const parts = filePath.replace("src/", "").split("/");
Expand Down Expand Up @@ -165,7 +181,7 @@ async function parseGitLog(): Promise<Map<string, FileStats>> {

// ── Aggregation ─────────────────────────────────────────────────────────────

function aggregateByModule(
export function aggregateByModule(
fileStats: Map<string, FileStats>,
): Map<string, ModuleStats> {
const modules = new Map<string, ModuleStats>();
Expand Down Expand Up @@ -210,7 +226,7 @@ function aggregateByModule(
* Bus factor = minimum number of authors whose combined commit share
* exceeds 50% of the module's total commits.
*/
function computeBusFactor(authors: Map<string, AuthorStats>): number {
export function computeBusFactor(authors: Map<string, AuthorStats>): number {
const totalCommits = [...authors.values()].reduce(
(sum, a) => sum + a.commits,
0,
Expand All @@ -232,11 +248,11 @@ function computeBusFactor(authors: Map<string, AuthorStats>): number {

// ── Silo Detection ─────────────────────────────────────────────────────────

function detectSilos(
export function detectSilos(
modules: Map<string, ModuleStats>,
config: CliArgs,
config: Pick<CliArgs, "recencyDays" | "siloThreshold">,
now: Date = new Date(),
): SiloReport[] {
const now = new Date();
const reports: SiloReport[] = [];

for (const [moduleName, mod] of modules) {
Expand Down Expand Up @@ -408,6 +424,19 @@ function formatReport(reports: SiloReport[], config: CliArgs): string {
return lines.join("\n");
}

// ── JSON Report ─────────────────────────────────────────────────────────────

function formatJson(reports: SiloReport[]): string {
return JSON.stringify(
reports.map((r) => ({
...r,
lastOtherAuthorDate: r.lastOtherAuthorDate?.toISOString() ?? null,
})),
null,
2,
);
}

// ── Main ────────────────────────────────────────────────────────────────────

async function main() {
Expand All @@ -424,12 +453,26 @@ async function main() {
}

const reports = detectSilos(modules, config);
const markdown = formatReport(reports, config);

console.log(markdown);
const output = config.json
? formatJson(reports)
: formatReport(reports, config);

if (config.output) {
const dir = dirname(config.output);
if (!existsSync(dir)) {
mkdirSync(dir, { recursive: true });
}
writeFileSync(config.output, output, "utf-8");
console.log(`Report written to ${config.output}`);
} else {
console.log(output);
}
}

main().catch((err) => {
console.error("Failed to run knowledge silo analysis:", err);
process.exit(1);
});
// Only run main when executed directly (not imported for tests)
if (import.meta.main) {
main().catch((err) => {
console.error("Failed to run knowledge silo analysis:", err);
process.exit(1);
});
}
Loading