Skip to content
Merged
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
18 changes: 17 additions & 1 deletion app/(app)/workspaces/[id]/learning-map/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { getLearningMapByWorkspaceId } from "@/lib/services/learning-map.service";
import { getWorkspaceById } from "@/lib/services/workspace.service";
import { getConceptLinksByWorkspaceId } from "@/lib/services/concept-link.service";
import { getQuestionsByWorkspaceId } from "@/lib/services/explain-back.service";
import { buildKnowledgeGraph, type KnowledgeGraphData } from "@/lib/services/knowledge-graph.service";
import { notFound } from "next/navigation";
import { ArrowLeft, CheckSquare, Square, Sparkles, FileText } from "lucide-react";
Expand Down Expand Up @@ -33,10 +34,11 @@ export default async function LearningMapPage({
const { id: workspaceId } = await params;
const { q } = await searchParams;

const [wsResult, mapResult, clResult, indexed] = await Promise.all([
const [wsResult, mapResult, clResult, qResult, indexed] = await Promise.all([
getWorkspaceById(workspaceId),
getLearningMapByWorkspaceId(workspaceId),
getConceptLinksByWorkspaceId(workspaceId),
getQuestionsByWorkspaceId(workspaceId),
hasSearchIndex(workspaceId),
]);

Expand All @@ -50,6 +52,19 @@ export default async function LearningMapPage({
const workspace = wsResult.data;
const map = mapResult.ok ? mapResult.data : null;
const conceptLinks = clResult.ok ? clResult.data : [];
const questions = qResult.ok ? qResult.data : [];

// Per-module related counts (#27/#28) — group the flat question/concept lists
// by moduleKey so each module card shows what belongs to it.
const relatedByModule = new Map<string, { questions: number; concepts: number }>();
const bump = (key: string | null, kind: "questions" | "concepts") => {
if (!key) return;
const cur = relatedByModule.get(key) ?? { questions: 0, concepts: 0 };
cur[kind]++;
relatedByModule.set(key, cur);
};
for (const q of questions) bump(q.moduleKey, "questions");
for (const cl of conceptLinks) bump(cl.moduleKey, "concepts");

const completedCheckpoints = map?.checkpoints.filter((c) => c.completed).length ?? 0;
const totalCheckpoints = map?.checkpoints.length ?? 0;
Expand Down Expand Up @@ -261,6 +276,7 @@ export default async function LearningMapPage({
module={mod}
mapId={map.id}
workspaceId={workspaceId}
related={relatedByModule.get(mod.key)}
/>
))}
</div>
Expand Down
30 changes: 29 additions & 1 deletion components/learning-map/ModuleCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import {
deleteModuleAction,
} from "@/app/(app)/workspaces/[id]/learning-map/actions";
import type { LearningModule } from "@/lib/services/learning-map.service";
import { Trash2 } from "lucide-react";
import Link from "next/link";
import { Trash2, HelpCircle, Lightbulb } from "lucide-react";

const DIFFICULTY_COLOR = {
beginner: "text-emerald-400 bg-emerald-500/10",
Expand All @@ -21,12 +22,15 @@ interface ModuleCardProps {
module: LearningModule;
mapId: string;
workspaceId: string;
/** Cross-surface counts (#27/#28): questions + concepts tagged to this module. */
related?: { questions: number; concepts: number };
}

export default function ModuleCard({
module,
mapId,
workspaceId,
related,
}: ModuleCardProps) {
return (
<div className="bg-[#0d1117] border border-zinc-800 rounded-lg p-4 space-y-3">
Expand Down Expand Up @@ -91,6 +95,30 @@ export default function ModuleCard({
</div>
)}

{/* Related surfaces (#27/#28) — this module's questions + concepts */}
{related && (related.questions > 0 || related.concepts > 0) && (
<div className="flex flex-wrap items-center gap-3 text-xs">
{related.questions > 0 && (
<Link
href={`/workspaces/${workspaceId}/explain-back`}
className="inline-flex items-center gap-1 text-cyan-500 hover:text-cyan-300 transition-colors"
>
<HelpCircle size={12} />
{related.questions} question{related.questions === 1 ? "" : "s"}
</Link>
)}
{related.concepts > 0 && (
<Link
href={`/workspaces/${workspaceId}/dsa-bridge`}
className="inline-flex items-center gap-1 text-violet-400 hover:text-violet-300 transition-colors"
>
<Lightbulb size={12} />
{related.concepts} concept{related.concepts === 1 ? "" : "s"}
</Link>
)}
</div>
)}

{/* Confidence row */}
<div className="pt-1 border-t border-zinc-800">
<p className="text-xs text-zinc-600 mb-2">Understanding level</p>
Expand Down
4 changes: 2 additions & 2 deletions docs/COUNCIL_REVIEW_LOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -569,5 +569,5 @@ Given 44 findings across two audits (many overlapping), what is the correct orde
| REVIEW-008 | Whole-project review — polish sprint: GitHub-pull on refresh, blended progress formula (#26), conceptType form enum (#30); defer cohesion/search/graph | ✅ Implemented — 10/10 tests | Pre-1.0 |
| REVIEW-009 | Landing strategy — preserve 15 commits (rebase-and-merge, no squash); skip paid AI smoke test; F2/F4/F5 cleanup as 3 free commits | ✅ Implemented — cleanup done | Pre-1.0 |
| REVIEW-010 | CodeSensei program — visible-identity rename, 8-lens local senior-review engine (findings-only optional AI), Obsidian-grade graph | ✅ Implemented — 16/16 tests | v0.2 |
| REVIEW-011 | Road to deployed 1.0 — single-problem megasprints (MS1–MS7) + full deep rename to NirmiqCodeSensei; load-balancing recorded N/A | 🔄 In progress (MS1✅ MS2✅ MS3✅) | v0.2→1.0 |
| REVIEW-012 | MS3 cross-feature module links (#27/#28) — defer: no analyzer-produced module associations to populate an FK; revisit in MS4 as a soft `moduleId` column | ✅ Deferred (reasoned) | v0.2→1.0 |
| REVIEW-011 | Road to deployed 1.0 — single-problem megasprints (MS1–MS7) + full deep rename to NirmiqCodeSensei; load-balancing recorded N/A | 🔄 In progress (MS1✅ MS2✅ MS3✅ MS4✅) | v0.2→1.0 |
| REVIEW-012 | MS3 cross-feature module links (#27/#28) — deferred from MS3, then **built in MS4** as a soft `module_key` column with deterministic tagging on both AI + offline paths (no AI-schema dependency) | ✅ Implemented (MS4) | v0.2→1.0 |
2 changes: 1 addition & 1 deletion docs/MEGASPRINT_ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ free + BYOK path (`ANTHROPIC_API_KEY` optional; offline analyzer is the default)
| **MS1** ✅ | Identity | Final distribution-ready name everywhere, incl. DB file / env / MCP internals, done once and safely | **DONE** — deep rename shipped; repo → `SheeshDarth/NirmiqCodeSensei`; DB boot-migration + `NCS_*` env fallback + `ncs_*` tools; gate green (16/16) |
| **MS2** ✅ | Security | The app ingests users' private source code — it must be provably safe | **DONE** — symlink-confined walk, shell-free git, realpath+credential-dir blocks, prod CSP no `unsafe-eval`, `npm audit` critical-gate; self-scan security lens **A/100** |
| **MS3** ✅ | Architecture & Data Integrity | Kill load-bearing hacks (description-as-path, no backup) | **DONE** — `sourcePath` column (migration 0008) retires the "Imported from:" hack; DB durability (`synchronous=NORMAL`, `busy_timeout`, boot integrity check, WAL checkpoint on exit) + downloadable backup; workspace error/loading boundaries; graph reconciliation already handled (`graphJson ?? buildKnowledgeGraph`). **#27/#28 module FKs deferred to MS4** (no analyzer-produced associations to populate them — REVIEW-012). Gate green (19/19) |
| **MS4** 🔄 | Algorithms & Analysis Depth | The analysis *is* the product — make it rigorous and calibrated | **In progress** — ✅ codeHealth scoring calibrated to code *density* not project size (self-scan F→**B(76)**, overall **A(95)**); size-relative `computeCodeHealthScore` + relativity test. ✅ incremental re-analysis — `computeSourceFingerprint` (path\|size\|mtime sha256) + `learning_maps.source_fingerprint` (0009); reanalyze short-circuits `{unchanged:true}` on an untouched tree. ✅ [benchmarks](BENCHMARKS.md) — 300-file full analysis <200ms, flat ~4ms lens pass, incremental skip ~8× cheaper; bounded by MAX_FILES/AST caps. Remaining: fold in #27/#28 module associations (REVIEW-012) |
| **MS4** | Algorithms & Analysis Depth | The analysis *is* the product — make it rigorous and calibrated | **DONE** — ✅ codeHealth calibrated to code *density* not project size (self-scan F→**B(76)**, overall **A(95)**); `computeCodeHealthScore` + relativity test. ✅ incremental re-analysis — `computeSourceFingerprint` (path\|size\|mtime sha256) + `learning_maps.source_fingerprint` (0009); reanalyze short-circuits `{unchanged:true}`. ✅ [benchmarks](BENCHMARKS.md) — 300-file analysis <200ms, flat ~4ms lens pass, incremental ~8× cheaper. ✅ #27/#28 module associations — soft `module_key` on questions/concepts (0010), deterministic tagging on both AI + offline paths, module cards link to their questions/concepts |
| **MS5** | Quality & Reliability (QA) | 16 tests is a foundation, not production confidence | Critical path e2e-covered; CI green on Win/mac/Linux from a clean clone |
| **MS6** | Framework & Performance | Production Next.js build quality | Standalone build runs; perf/a11y budgets met; no UI-blocking analysis |
| **MS7** | Distribution & Release | Turn the repo into installable, versioned software — the actual "deploy" | `npx nirmiqcodesensei@latest` runs on a fresh machine; tagged v1.0.0 GitHub Release; CHANGELOG current; scaling-N/A ADR recorded |
Expand Down
2 changes: 2 additions & 0 deletions lib/db/migrations/0010_tiresome_goliath.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ALTER TABLE `concept_links` ADD `module_key` text;--> statement-breakpoint
ALTER TABLE `explain_back_questions` ADD `module_key` text;
Loading
Loading