project-librarian 0.5.9 → 0.6.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.
package/README.ko.md CHANGED
@@ -41,7 +41,7 @@ npx project-librarian@latest install --scope project --agents all
41
41
  npx project-librarian@latest update
42
42
  ```
43
43
 
44
- 이 명령은 관리 설정 파일, 에이전트 훅, 위키 운영/메타 파일, 기존 프로젝트 범위 스킬 복사본을 갱신합니다. 현재 `wiki/`는 보존하고 migration flag는 거부하므로 위키를 `wiki_legacy*`로 바꾸지 않습니다.
44
+ 이 명령은 관리 설정 파일, 에이전트 훅, 위키 운영/메타 파일, 기존 프로젝트 범위 스킬 복사본을 갱신합니다. 기존 공유 `.agents/skills/project-librarian/` 복사본도 특정 에이전트 설정 표면을 암묵적으로 추가하지 않고 갱신합니다. 현재 `wiki/`는 보존하고 migration flag는 거부하므로 위키를 `wiki_legacy*`로 바꾸지 않습니다.
45
45
 
46
46
  특정 프로젝트 표면을 의도적으로 추가하거나 갱신할 때는 `--agents`를 명시합니다.
47
47
 
@@ -77,9 +77,9 @@ Project Librarian은 작은 첫 읽기와 더 깊은 정본으로 가는 경로
77
77
  - **첫 읽기를 작게.** 시작 훅은 `wiki/startup.md`와 `wiki/index.md`만 주입하고, 에이전트는 필요할 때 더 깊은 페이지로 이동합니다.
78
78
  - **한 번 설정, 네 에이전트.** Codex, Claude Code, Cursor, Gemini CLI가 같은 저장소 로컬 기억 계약을 공유합니다.
79
79
  - **구조적인 위키 작성.** 새 프로젝트 내용은 작성하거나 취합하기 전에 분류하므로 PRD, 정책, UX, 데이터, API, QA, 릴리즈, 운영 메모가 하나의 잡다한 페이지로 합쳐지지 않습니다.
80
- - **검토 가능한 위키 그래프.** `--wiki-visualize`는 시작 컨텍스트를 늘리지 않고 `.project-wiki/` 아래에 정적 HTML 그래프를 작성합니다.
81
80
  - **측정된 주장.** 벤치마크에서 이긴 경우와 손해를 본 경우를 주장 경계와 함께 공개합니다.
82
81
  - **로컬 세션 핸드오프.** `--handoff-save`는 `.project-wiki/session/` 아래에 생성형 재개 메모를 저장하되 실행 기억을 프로젝트 정본으로 바꾸지 않습니다.
82
+ - **답변형 위키 토폴로지.** `--wiki-impact`, `--wiki-neighborhood`, topology diagnostics가 graph visualizer 없이 backlinks, decision 근거, router depth, 가까운 읽기 순서를 노출합니다.
83
83
  - **선택적 코드 근거.** SQLite 인덱스와 답변 형태 MCP 도구가 큰 저장소의 비싼 탐색 질문에 답하며, MCP SDK 의존성은 추가하지 않습니다.
84
84
  - **다시 실행해도 안전.** 부트스트랩은 멱등하고 기존 내용 보존을 우선합니다.
85
85
 
@@ -94,6 +94,7 @@ Project Librarian은 작은 첫 읽기와 더 깊은 정본으로 가는 경로
94
94
  | 기존 문서/위키 마이그레이션 | "Project Librarian으로 기존 docs/wiki 내용을 마이그레이션해줘." |
95
95
  | 진단 실행 | "Project Librarian 진단을 실행해줘." |
96
96
  | 프로젝트 기억 검색 | "Project Librarian 위키에서 authentication decisions를 찾아줘." |
97
+ | 가까운 위키 맥락 찾기 | "canonical/project-brief의 Project Librarian wiki neighborhood를 보여줘." |
97
98
  | 코드 근거 구축 | "Project Librarian 코드 근거를 `src`에 대해 구축해줘." |
98
99
  | 코드 영향 확인 | "`healthHandler`에 대한 Project Librarian 영향 근거를 보여줘." |
99
100
  | 세션 핸드오프 저장 | "현재 작업에 대한 Project Librarian 세션 핸드오프를 저장해줘." |
package/README.md CHANGED
@@ -41,7 +41,7 @@ To refresh an existing setup without migrating the wiki, run:
41
41
  npx project-librarian@latest update
42
42
  ```
43
43
 
44
- That updates managed setup files, agent hooks, wiki operating/meta files, and existing project-scoped skill copies. It preserves the current `wiki/` and rejects migration flags, so it will not rename the wiki to `wiki_legacy*`.
44
+ That updates managed setup files, agent hooks, wiki operating/meta files, and existing project-scoped skill copies. Existing shared `.agents/skills/project-librarian/` copies are refreshed without implying any agent-specific setup surface. It preserves the current `wiki/` and rejects migration flags, so it will not rename the wiki to `wiki_legacy*`.
45
45
 
46
46
  Use `--agents` when you intentionally want to add or refresh a specific project surface:
47
47
 
@@ -77,9 +77,9 @@ The core idea is not "write more docs." It is "keep the first agent read small,
77
77
  - **Small first read.** Startup hooks inject only `wiki/startup.md` and `wiki/index.md`; agents route to deeper pages on demand.
78
78
  - **One setup, four agents.** Codex, Claude Code, Cursor, and Gemini CLI share the same repo-local memory contract.
79
79
  - **Structured wiki writing.** New project content is classified before it is written or consolidated, so PRDs, policies, UX, data, APIs, QA, release, and operations notes do not collapse into one catch-all page.
80
- - **Inspectable wiki graph.** `--wiki-visualize` writes a static HTML graph under `.project-wiki/` without adding to startup context.
81
80
  - **Measured claims.** Benchmark wins and losses are published together, with claim boundaries attached.
82
81
  - **Local session handoff.** `--handoff-save` stores generated resume notes under `.project-wiki/session/` without turning execution memory into canonical project truth.
82
+ - **Answer-shaped wiki topology.** `--wiki-impact`, `--wiki-neighborhood`, and topology diagnostics expose backlinks, decision evidence, router depth, and nearby read order without a graph visualizer.
83
83
  - **Optional code evidence.** A SQLite index plus answer-shaped MCP tools answer expensive traversal questions on large repositories without adding an MCP SDK dependency.
84
84
  - **Safe to re-run.** Bootstrap is idempotent and preservation-first.
85
85
 
@@ -94,6 +94,7 @@ Ask your agent for the outcome you want; the installed skill maps the request to
94
94
  | Migrate existing docs/wiki content | "Use Project Librarian to migrate the existing docs/wiki content." |
95
95
  | Run diagnostics | "Run Project Librarian diagnostics." |
96
96
  | Search project memory | "Search the Project Librarian wiki for authentication decisions." |
97
+ | Find nearby wiki context | "Show Project Librarian wiki neighborhood for canonical/project-brief." |
97
98
  | Build code evidence | "Build Project Librarian code evidence for `src`." |
98
99
  | Inspect code impact | "Show Project Librarian impact evidence for `healthHandler`." |
99
100
  | Save a handoff | "Save a Project Librarian session handoff for the current work." |
package/SKILL.md CHANGED
@@ -37,6 +37,7 @@ Supported actions:
37
37
  Prefer an already installed local runner over network package execution:
38
38
 
39
39
  - In the project-librarian source repository, use `node dist/init-project-wiki.js` when `dist/init-project-wiki.js` exists.
40
+ - In a target repository with a shared project-scoped skill install, use `node .agents/skills/project-librarian/dist/init-project-wiki.js`.
40
41
  - In a target repository with a project-scoped Codex skill install, use `node .codex/skills/project-librarian/dist/init-project-wiki.js`.
41
42
  - In a target repository with a project-scoped Claude skill install, use `node .claude/skills/project-librarian/dist/init-project-wiki.js`.
42
43
  - In a target repository with a project-scoped Cursor skill install, use `node .cursor/skills/project-librarian/dist/init-project-wiki.js`.
@@ -20,6 +20,7 @@ const agentSurfaceProjectSkillFiles = {
20
20
  gemini: [".gemini/skills/project-librarian/SKILL.md"],
21
21
  };
22
22
  const projectLibrarianCommonInstallFiles = [
23
+ ".agents/skills/project-librarian/SKILL.md",
23
24
  "wiki/startup.md",
24
25
  "wiki/index.md",
25
26
  "wiki/AGENTS.md",
package/dist/args.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.codeIndexMode = exports.acknowledgeSmallRepoMode = exports.noGitConfigMode = exports.reviewMigrationMode = exports.pruneCheckStrictMode = exports.pruneCheckMode = exports.captureInboxMode = exports.refreshIndexMode = exports.issueDraftMode = exports.issueCreateMode = exports.handoffVerification = exports.handoffStatusMode = exports.handoffState = exports.handoffShowMode = exports.handoffSaveMode = exports.handoffPromoteInboxMode = exports.handoffInjectionStatusMode = exports.handoffInjectionEnableMode = exports.handoffInjectionDisableMode = exports.handoffOpenQuestions = exports.handoffNextActions = exports.handoffLastSuccessCommand = exports.handoffLastFailureCommand = exports.handoffInputMode = exports.handoffGoal = exports.handoffDecisions = exports.handoffClearMode = exports.handoffBlocked = exports.glossaryMode = exports.fixMode = exports.doctorMode = exports.qualityCheckMode = exports.linkCheckMode = exports.migrationQualityCheckMode = exports.migrationLintMode = exports.migrationDoctorMode = exports.lintMode = exports.migrateMode = exports.invalidAgentTargets = exports.missingValueOptions = exports.unexpectedValueOptions = exports.unknownOptions = exports.args = exports.commandArgs = exports.command = exports.unknownCommand = exports.helpMode = exports.agentTargets = exports.parsedArgs = exports.rawArgs = void 0;
4
- exports.issueDraftTitle = exports.issueBodyFile = exports.captureCategory = exports.captureContent = exports.captureTitle = exports.codeIndexScopes = exports.codeParser = exports.codeIndexOutput = exports.codeSearchSymbol = exports.codeReportSection = exports.codeQuerySql = exports.codeImpactTarget = exports.codeContextPackTarget = exports.wikiVisualizeOutput = exports.wikiVisualizeMode = exports.wikiImpactTarget = exports.wikiImpactMode = exports.queryTerm = exports.codeSearchSymbolMode = exports.codeQueryMode = exports.codeImpactMode = exports.codeParserMode = exports.codeContextPackMode = exports.codeFilesMode = exports.codeStatusMode = exports.codeReportMode = exports.codeIndexHealthMode = exports.codeIndexFullMode = exports.codeIndexMigrateMode = exports.codeIndexIncrementalMode = exports.codeIndexEngineMode = exports.codeIndexEngine = void 0;
4
+ exports.issueDraftTitle = exports.issueBodyFile = exports.captureCategory = exports.captureContent = exports.captureTitle = exports.codeIndexScopes = exports.codeParser = exports.codeIndexOutput = exports.codeSearchSymbol = exports.codeReportSection = exports.codeQuerySql = exports.codeImpactTarget = exports.codeContextPackTarget = exports.wikiNeighborhoodTarget = exports.wikiNeighborhoodMode = exports.wikiImpactTarget = exports.wikiImpactMode = exports.queryTerm = exports.codeSearchSymbolMode = exports.codeQueryMode = exports.codeImpactMode = exports.codeParserMode = exports.codeContextPackMode = exports.codeFilesMode = exports.codeStatusMode = exports.codeReportMode = exports.codeIndexHealthMode = exports.codeIndexFullMode = exports.codeIndexMigrateMode = exports.codeIndexIncrementalMode = exports.codeIndexEngineMode = exports.codeIndexEngine = void 0;
5
5
  exports.parseArgs = parseArgs;
6
6
  exports.argValue = argValue;
7
7
  exports.argValues = argValues;
@@ -72,10 +72,8 @@ const flagDefinitions = [
72
72
  { name: "--review-migration", value: "none", aliases: ["--semantic-migrate"] },
73
73
  { name: "--scope", value: "value" },
74
74
  { name: "--title", value: "value" },
75
- { name: "--wiki-graph-html", value: "none" },
76
75
  { name: "--wiki-impact", value: "value" },
77
- { name: "--wiki-visualize", value: "none" },
78
- { name: "--wiki-visualize-out", value: "value" },
76
+ { name: "--wiki-neighborhood", value: "value" },
79
77
  ];
80
78
  function definitionNames(definition) {
81
79
  return [definition.name, ...(definition.aliases ?? [])];
@@ -156,6 +154,7 @@ function parseArgs(argv) {
156
154
  const codeContextPackTarget = argValueFromAny("--code-context-pack");
157
155
  const codeQuerySql = argValueFromAny("--code-query");
158
156
  const codeSearchSymbol = argValueFromAny("--code-search-symbol");
157
+ const wikiNeighborhoodTarget = argValue("--wiki-neighborhood");
159
158
  const parsedAgentTargets = (0, agent_surfaces_1.parseAgentSurfaceValues)(argValues("--agents"));
160
159
  const handoffInputMode = [
161
160
  "--blocked",
@@ -254,8 +253,8 @@ function parseArgs(argv) {
254
253
  .filter((arg) => !knownFlags.has(arg)))),
255
254
  wikiImpactMode: hasFlag("--wiki-impact"),
256
255
  wikiImpactTarget: argValue("--wiki-impact"),
257
- wikiVisualizeMode: hasFlag("--wiki-visualize") || hasFlag("--wiki-graph-html"),
258
- wikiVisualizeOutput: argValue("--wiki-visualize-out"),
256
+ wikiNeighborhoodMode: hasFlag("--wiki-neighborhood"),
257
+ wikiNeighborhoodTarget,
259
258
  };
260
259
  }
261
260
  exports.parsedArgs = parseArgs(exports.rawArgs);
@@ -330,8 +329,8 @@ function argValues(name) {
330
329
  exports.queryTerm = exports.parsedArgs.queryTerm;
331
330
  exports.wikiImpactMode = exports.parsedArgs.wikiImpactMode;
332
331
  exports.wikiImpactTarget = exports.parsedArgs.wikiImpactTarget;
333
- exports.wikiVisualizeMode = exports.parsedArgs.wikiVisualizeMode;
334
- exports.wikiVisualizeOutput = exports.parsedArgs.wikiVisualizeOutput;
332
+ exports.wikiNeighborhoodMode = exports.parsedArgs.wikiNeighborhoodMode;
333
+ exports.wikiNeighborhoodTarget = exports.parsedArgs.wikiNeighborhoodTarget;
335
334
  exports.codeContextPackTarget = exports.parsedArgs.codeContextPackTarget;
336
335
  exports.codeImpactTarget = exports.parsedArgs.codeImpactTarget;
337
336
  exports.codeQuerySql = exports.parsedArgs.codeQuerySql;
package/dist/hooks.js CHANGED
@@ -149,6 +149,7 @@ const mcpServerName = "project-librarian";
149
149
  // runner wins; absent any local install we register the published binary.
150
150
  const localRunnerCandidates = [
151
151
  "tools/project-librarian/dist/init-project-wiki.js",
152
+ ".agents/skills/project-librarian/dist/init-project-wiki.js",
152
153
  ".codex/skills/project-librarian/dist/init-project-wiki.js",
153
154
  ".claude/skills/project-librarian/dist/init-project-wiki.js",
154
155
  ".cursor/skills/project-librarian/dist/init-project-wiki.js",
@@ -458,7 +459,7 @@ function wikiScope(files) {
458
459
  else if (file.startsWith(".gemini/hooks/") || file === ".gemini/settings.json") add("gemini-hooks");
459
460
  else if (file === "AGENTS.md" || file === "CLAUDE.md" || file === "GEMINI.md") add("agents");
460
461
  else if (file.startsWith(".githooks/")) add("git-hooks");
461
- else if (file.startsWith("tools/project-librarian/")) add("skill");
462
+ else if (file.startsWith("tools/project-librarian/") || file.startsWith(".agents/skills/project-librarian/")) add("skill");
462
463
  }
463
464
  return scopes.length === 0 ? "none" : scopes.join(", ");
464
465
  }
@@ -467,6 +468,7 @@ function validationTrailers() {
467
468
  const home = process.env.HOME || "";
468
469
  const lintScript = [
469
470
  "tools/project-librarian/dist/init-project-wiki.js",
471
+ ".agents/skills/project-librarian/dist/init-project-wiki.js",
470
472
  path.join(home, ".codex/skills/project-librarian/dist/init-project-wiki.js"),
471
473
  path.join(home, ".claude/skills/project-librarian/dist/init-project-wiki.js"),
472
474
  path.join(home, ".cursor/skills/project-librarian/dist/init-project-wiki.js"),
@@ -517,7 +519,8 @@ const wikiFiles = staged.filter((file) => {
517
519
  || file === ".gemini/settings.json"
518
520
  || file.startsWith(".gemini/hooks/")
519
521
  || file.startsWith(".githooks/")
520
- || file.startsWith("tools/project-librarian/");
522
+ || file.startsWith("tools/project-librarian/")
523
+ || file.startsWith(".agents/skills/project-librarian/");
521
524
  });
522
525
 
523
526
  if (wikiFiles.length === 0) process.exit(0);
@@ -9,7 +9,6 @@ const modes_1 = require("./modes");
9
9
  const migration_1 = require("./migration");
10
10
  const session_handoff_1 = require("./session-handoff");
11
11
  const templates_1 = require("./templates");
12
- const wiki_visualizer_1 = require("./wiki-visualizer");
13
12
  const workspace_1 = require("./workspace");
14
13
  function codeIndex() {
15
14
  return require("./code-index");
@@ -37,8 +36,8 @@ Options:
37
36
  --dry-run With install, preview copied skill files without writing them.
38
37
  --query <terms> Search wiki paths, metadata, titles, and bodies (answer-shaped, capped output).
39
38
  --wiki-impact <page-or-term> Show wiki backlinks, decision_ref citations, and router depth for matching pages.
40
- --wiki-visualize Write a static wiki graph visualizer to .project-wiki/wiki-graph.html.
41
- --wiki-visualize-out <path> With --wiki-visualize, write under a custom .project-wiki/ path.
39
+ --wiki-neighborhood <page-or-term>
40
+ Show a bounded read order for nearby wiki pages.
42
41
  --refresh-index Update the managed auto-discovered wiki index block.
43
42
  --capture-inbox Append a candidate note with --title, --content, and optional --category.
44
43
  --handoff-save Save local generated session handoff state under .project-wiki/session/.
@@ -147,10 +146,6 @@ if (args_1.codeReportSection && !args_1.codeReportMode) {
147
146
  console.error("--code-report-section is only supported with --code-report.");
148
147
  process.exit(1);
149
148
  }
150
- if (args_1.wikiVisualizeOutput && !args_1.wikiVisualizeMode) {
151
- console.error("--wiki-visualize-out is only supported with --wiki-visualize.");
152
- process.exit(1);
153
- }
154
149
  if (args_1.pruneCheckStrictMode && !args_1.pruneCheckMode) {
155
150
  console.error("--prune-check-strict is only supported with --prune-check.");
156
151
  process.exit(1);
@@ -266,16 +261,9 @@ function runInitCommand() {
266
261
  exitAfterStdoutDrain(0);
267
262
  return;
268
263
  }
269
- if (args_1.wikiVisualizeMode) {
270
- try {
271
- const output = (0, wiki_visualizer_1.writeWikiVisualizer)(args_1.wikiVisualizeOutput);
272
- console.log(`Project wiki visualizer written: ${output}`);
273
- exitAfterStdoutDrain(0);
274
- }
275
- catch (error) {
276
- console.error(error instanceof Error ? error.message : String(error));
277
- process.exit(1);
278
- }
264
+ if (args_1.wikiNeighborhoodMode) {
265
+ (0, modes_1.runWikiNeighborhoodMode)();
266
+ exitAfterStdoutDrain(0);
279
267
  return;
280
268
  }
281
269
  if (args_1.queryTerm) {
@@ -340,6 +328,7 @@ function runInitCommand() {
340
328
  const projectSkillSyncSurfaces = args_1.command === "update"
341
329
  ? (0, install_skill_1.installedProjectSkillSurfaces)().filter((surface) => (0, agent_surfaces_1.includesAgentSurface)(selectedAgentSurfaces, surface))
342
330
  : [];
331
+ const syncSharedProjectSkill = args_1.command === "update" && (0, install_skill_1.hasSharedProjectSkillInstall)();
343
332
  const shouldWriteSurface = (surface) => (0, agent_surfaces_1.includesAgentSurface)(selectedAgentSurfaces, surface);
344
333
  const writeCodexSurface = shouldWriteSurface("codex");
345
334
  const writeClaudeSurface = shouldWriteSurface("claude");
@@ -369,6 +358,10 @@ function runInitCommand() {
369
358
  for (const result of (0, install_skill_1.syncProjectSkillInstall)(surface))
370
359
  results.push(result);
371
360
  }
361
+ if (syncSharedProjectSkill) {
362
+ for (const result of (0, install_skill_1.syncSharedProjectSkillInstall)())
363
+ results.push(result);
364
+ }
372
365
  // B1 fallback: sync the CURRENT startup.md TL;DR into the managed AGENTS.md block
373
366
  // so non-interactive `codex exec` (which does not run SessionStart hooks) still
374
367
  // gets compact startup context. Routers are starter files written later in this
@@ -34,8 +34,11 @@ var __importStar = (this && this.__importStar) || (function () {
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.projectSkillTarget = projectSkillTarget;
37
+ exports.sharedProjectSkillTarget = sharedProjectSkillTarget;
37
38
  exports.installedProjectSkillSurfaces = installedProjectSkillSurfaces;
39
+ exports.hasSharedProjectSkillInstall = hasSharedProjectSkillInstall;
38
40
  exports.syncProjectSkillInstall = syncProjectSkillInstall;
41
+ exports.syncSharedProjectSkillInstall = syncSharedProjectSkillInstall;
39
42
  exports.runInstallSkillMode = runInstallSkillMode;
40
43
  const fs = __importStar(require("node:fs"));
41
44
  const os = __importStar(require("node:os"));
@@ -43,6 +46,7 @@ const path = __importStar(require("node:path"));
43
46
  const agent_surfaces_1 = require("./agent-surfaces");
44
47
  const args_1 = require("./args");
45
48
  const skillName = "project-librarian";
49
+ const sharedProjectSkillRelativeRoot = path.join(".agents", "skills", skillName);
46
50
  const packageFiles = [
47
51
  "SKILL.md",
48
52
  "dist",
@@ -121,6 +125,9 @@ function projectSkillRelativeRoot(agent) {
121
125
  function projectSkillTarget(agent) {
122
126
  return path.join(process.cwd(), projectSkillRelativeRoot(agent));
123
127
  }
128
+ function sharedProjectSkillTarget() {
129
+ return path.join(process.cwd(), sharedProjectSkillRelativeRoot);
130
+ }
124
131
  function installTarget(agent, scope) {
125
132
  const base = scope === "user" ? userAgentRoot(agent) : path.join(process.cwd(), projectAgentRoot(agent));
126
133
  return path.join(base, "skills", skillName);
@@ -247,6 +254,9 @@ function copyPath(source, target, targetRoot, dryRun) {
247
254
  function installedProjectSkillSurfaces() {
248
255
  return agent_surfaces_1.allAgentSurfaces.filter((agent) => fs.existsSync(path.join(projectSkillTarget(agent), "SKILL.md")));
249
256
  }
257
+ function hasSharedProjectSkillInstall() {
258
+ return fs.existsSync(path.join(sharedProjectSkillTarget(), "SKILL.md"));
259
+ }
250
260
  function copyPackageFiles(targetRoot, dryRun, labelRoot = targetRoot) {
251
261
  const root = packageRoot();
252
262
  const packageRows = packageFiles.map((relativePath) => {
@@ -269,6 +279,13 @@ function syncProjectSkillInstall(agent) {
269
279
  return [label, status];
270
280
  });
271
281
  }
282
+ function syncSharedProjectSkillInstall() {
283
+ return copyPackageFiles(sharedProjectSkillTarget(), false, sharedProjectSkillRelativeRoot).map(([label, status]) => {
284
+ if (status === "dry-run")
285
+ throw new Error("shared project skill sync does not support dry-run status");
286
+ return [label, status];
287
+ });
288
+ }
272
289
  function runInstallSkillMode() {
273
290
  const scope = installScope();
274
291
  const agents = installAgents();
package/dist/modes.js CHANGED
@@ -36,6 +36,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.buildRefreshIndexBlock = buildRefreshIndexBlock;
37
37
  exports.runQueryMode = runQueryMode;
38
38
  exports.runWikiImpactMode = runWikiImpactMode;
39
+ exports.runWikiNeighborhoodMode = runWikiNeighborhoodMode;
39
40
  exports.projectCandidatesContent = projectCandidatesContent;
40
41
  exports.appendProjectCandidate = appendProjectCandidate;
41
42
  exports.appendCaptureInbox = appendCaptureInbox;
@@ -325,6 +326,14 @@ function runWikiImpactMode() {
325
326
  const corpus = (0, wiki_corpus_1.loadWikiCorpus)();
326
327
  console.log((0, wiki_graph_1.wikiImpactAnswer)(corpus.pages, args_1.wikiImpactTarget.trim(), (0, wiki_corpus_1.wikiCorpusGraph)(corpus)));
327
328
  }
329
+ function runWikiNeighborhoodMode() {
330
+ if (!args_1.wikiNeighborhoodTarget.trim()) {
331
+ console.error("missing wiki neighborhood target: use --wiki-neighborhood \"page-or-term\"");
332
+ process.exit(1);
333
+ }
334
+ const corpus = (0, wiki_corpus_1.loadWikiCorpus)();
335
+ console.log((0, wiki_graph_1.wikiNeighborhoodAnswer)(corpus.pages, args_1.wikiNeighborhoodTarget.trim(), (0, wiki_corpus_1.wikiCorpusGraph)(corpus)));
336
+ }
328
337
  function projectCandidatesContent() {
329
338
  return `${(0, templates_1.metadata)("inbox", "on-demand", "wiki/meta/wiki-ops-v1-decisions.md", "candidates are adopted, rejected, or stale")}
330
339
  # Project Candidates Inbox
@@ -699,6 +708,7 @@ function collectLinkDiagnostics(corpus = (0, wiki_corpus_1.loadWikiCorpus)()) {
699
708
  }
700
709
  }
701
710
  }
711
+ diagnostics.push(...(0, wiki_diagnostics_1.collectTopologyDiagnostics)(corpus));
702
712
  return diagnostics.sort((a, b) => a.severity.localeCompare(b.severity) || a.file.localeCompare(b.file) || a.code.localeCompare(b.code));
703
713
  }
704
714
  function legacyWikiRoots() {
@@ -725,9 +735,10 @@ function migrationLegacyReferenceDiagnostics(files, corpus) {
725
735
  message: "new project truth must not link to or cite wiki_legacy*; migrate the meaning or keep unresolved material in migration inboxes",
726
736
  }));
727
737
  }
728
- function collectQualityDiagnostics(corpus = (0, wiki_corpus_1.loadWikiCorpus)()) {
738
+ function collectQualityDiagnostics(corpus = (0, wiki_corpus_1.loadWikiCorpus)(), options = {}) {
729
739
  const diagnostics = [];
730
740
  const files = corpus.files;
741
+ const currentDate = options.today ?? workspace_1.today;
731
742
  const titles = new Map();
732
743
  for (const file of files) {
733
744
  const text = (0, wiki_corpus_1.wikiCorpusText)(corpus, file);
@@ -742,7 +753,7 @@ function collectQualityDiagnostics(corpus = (0, wiki_corpus_1.loadWikiCorpus)())
742
753
  if (tldrExpected && !/##\s+TL;DR/.test(body)) {
743
754
  diagnostics.push({ code: "missing-tldr", severity: "warn", file, message: "add a compact TL;DR near the top" });
744
755
  }
745
- const reviewAge = updated ? (0, wiki_diagnostics_1.staleReviewAge)(updated, workspace_1.today) : null;
756
+ const reviewAge = updated ? (0, wiki_diagnostics_1.staleReviewAge)(updated, currentDate) : null;
746
757
  if (status === "active" && reviewAge !== null && /project-canonical|project-decisions|source-summary|wiki-meta/.test(scope)) {
747
758
  diagnostics.push({ code: "stale-review", severity: "warn", file, message: `updated ${reviewAge} days ago: ${updated}` });
748
759
  }
@@ -21,39 +21,39 @@
21
21
  "architecture": "arm64",
22
22
  "format": "elf",
23
23
  "path": "dist/native/linux-arm64/project-librarian-indexer",
24
- "sha256": "f3cc884acca4f5863fb6a6545a4a9f9a37a67872bfe780f4b14f0c4a8a667f16",
25
- "size": 3065520,
24
+ "sha256": "88146d5bbe5b77bb53e2cbe872ad6d2a8a93a58fa1b53671841cc94463443f46",
25
+ "size": 3065568,
26
26
  "triple": "linux-arm64"
27
27
  },
28
28
  {
29
29
  "architecture": "arm64",
30
30
  "format": "elf",
31
31
  "path": "dist/native/linux-arm64-musl/project-librarian-indexer",
32
- "sha256": "80f7440c4d115d05805b63b8118a172ddeb5a374b7154593e5b1d0b804a938ae",
33
- "size": 3100816,
32
+ "sha256": "bcb48020ae87fe3e5ebd6a8d986a91f83e67e5d69c4df9357a7c3a6c78c232b0",
33
+ "size": 3101344,
34
34
  "triple": "linux-arm64-musl"
35
35
  },
36
36
  {
37
37
  "architecture": "x64",
38
38
  "format": "elf",
39
39
  "path": "dist/native/linux-x64/project-librarian-indexer",
40
- "sha256": "3d93408db344567d7de6f477b58fdaf6aba19c943336fb7ab9720e3e9fa68c31",
41
- "size": 3110832,
40
+ "sha256": "22d32884276c23c4597bd611facd5880936b29600c8331bb3d585d2ab370a206",
41
+ "size": 3110688,
42
42
  "triple": "linux-x64"
43
43
  },
44
44
  {
45
45
  "architecture": "x64",
46
46
  "format": "elf",
47
47
  "path": "dist/native/linux-x64-musl/project-librarian-indexer",
48
- "sha256": "0055ec9fefa385d6d637fa1e457308c38f892e0fbdb389db3fcbdf546f0daca8",
49
- "size": 3247400,
48
+ "sha256": "811221a87fe5d8ef9f67f4eafe4386ef7aa03bdc1601e89fa191c0e0d7b0dcd5",
49
+ "size": 3247424,
50
50
  "triple": "linux-x64-musl"
51
51
  },
52
52
  {
53
53
  "architecture": "arm64",
54
54
  "format": "pe",
55
55
  "path": "dist/native/win32-arm64/project-librarian-indexer.exe",
56
- "sha256": "f8ef16c98ff97c4f1dd38e2e08a034a40bb219f114f8270d165feaddd8166d8f",
56
+ "sha256": "69aeecd89d8ec8e7e4e85adfdf15cd682df9eaa14b8e6d0e99f445eeb70e6267",
57
57
  "size": 2098688,
58
58
  "triple": "win32-arm64"
59
59
  },
@@ -61,7 +61,7 @@
61
61
  "architecture": "x64",
62
62
  "format": "pe",
63
63
  "path": "dist/native/win32-x64/project-librarian-indexer.exe",
64
- "sha256": "5a81b330741f11d10ab5d296e8cbc8c60f1596c30a45c5f31e41fab49e5b2603",
64
+ "sha256": "ae493dbea6ddd4cd6831ad9a21db07df0c6e2989602e6048dfbfa9aa1eb80ee5",
65
65
  "size": 2375680,
66
66
  "triple": "win32-x64"
67
67
  }
@@ -1,8 +1,14 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.staleReviewAgeDays = void 0;
3
+ exports.topologyFanoutThreshold = exports.topologyHubOverloadThreshold = exports.staleReviewAgeDays = void 0;
4
4
  exports.staleReviewAge = staleReviewAge;
5
+ exports.collectTopologyDiagnostics = collectTopologyDiagnostics;
6
+ const wiki_corpus_1 = require("./wiki-corpus");
7
+ const wiki_graph_1 = require("./wiki-graph");
8
+ const workspace_1 = require("./workspace");
5
9
  exports.staleReviewAgeDays = 30;
10
+ exports.topologyHubOverloadThreshold = 60;
11
+ exports.topologyFanoutThreshold = 8;
6
12
  function dateOnlyMillis(value) {
7
13
  if (!/^\d{4}-\d{2}-\d{2}$/.test(value))
8
14
  return null;
@@ -17,3 +23,107 @@ function staleReviewAge(updated, currentDate) {
17
23
  const ageDays = Math.floor((currentMillis - updatedMillis) / 86_400_000);
18
24
  return ageDays > exports.staleReviewAgeDays ? ageDays : null;
19
25
  }
26
+ function uniqueExisting(values, fileSet) {
27
+ return Array.from(new Set(values.filter((value) => fileSet.has(value)))).sort();
28
+ }
29
+ function isGeneratedScopedRouter(file) {
30
+ return /^wiki\/indexes\/auto-[a-z0-9-]+(?:-\d+)?\.md$/.test(file);
31
+ }
32
+ function isCanonicalTruthPage(file, text) {
33
+ return file.startsWith("wiki/canonical/") && (0, workspace_1.metadataValue)(text, "status") === "active";
34
+ }
35
+ function hasEvidenceClaimSignal(body) {
36
+ return /\b(source-backed|source backed|research-backed|external research|paper-backed|evidence-backed)\b/i.test(body);
37
+ }
38
+ function hasDecisionRefSignal(text) {
39
+ const decisionRef = (0, workspace_1.metadataValue)(text, "decision_ref").trim().toLowerCase();
40
+ return decisionRef !== "" && decisionRef !== "none";
41
+ }
42
+ function hasFocusedAuthoritySignal(text, body) {
43
+ return hasDecisionRefSignal(text) || hasEvidenceClaimSignal(body);
44
+ }
45
+ function hasEvidenceLink(file, corpus, graph = (0, wiki_corpus_1.wikiCorpusGraph)(corpus)) {
46
+ const outgoingLinks = graph.outgoingLinks.get(file) ?? [];
47
+ const sourceLink = outgoingLinks.some((link) => link.normalizedTarget.startsWith("wiki/sources/") && corpus.fileSet.has(link.normalizedTarget));
48
+ const decisionRef = graph.outgoingDecisionRef.get(file);
49
+ return sourceLink || Boolean(decisionRef && corpus.fileSet.has(decisionRef));
50
+ }
51
+ function isBroadReviewTrigger(trigger) {
52
+ const normalized = trigger.toLowerCase().trim();
53
+ if (!normalized)
54
+ return false;
55
+ return normalized === "changes"
56
+ || normalized === "project changes"
57
+ || normalized === "routine review"
58
+ || /^any\b.*changes$/.test(normalized)
59
+ || /^general\b.*changes$/.test(normalized);
60
+ }
61
+ function isTopologyHub(file) {
62
+ return file === "wiki/index.md"
63
+ || file.startsWith("wiki/meta/")
64
+ || /^wiki\/indexes\/(?!auto-)[^/]+\.md$/.test(file);
65
+ }
66
+ function collectTopologyDiagnostics(corpus) {
67
+ const diagnostics = [];
68
+ const graph = (0, wiki_corpus_1.wikiCorpusGraph)(corpus);
69
+ for (const file of corpus.files) {
70
+ if (isGeneratedScopedRouter(file))
71
+ continue;
72
+ const outgoing = uniqueExisting((graph.outgoingLinks.get(file) ?? [])
73
+ .map((link) => link.normalizedTarget)
74
+ .filter((target) => target !== file), corpus.fileSet);
75
+ if (isTopologyHub(file) && outgoing.length > exports.topologyHubOverloadThreshold) {
76
+ diagnostics.push({
77
+ code: "hub-overload",
78
+ severity: "warn",
79
+ file,
80
+ message: `${outgoing.length} outgoing wiki links exceed the topology hub threshold ${exports.topologyHubOverloadThreshold}; split or scope the route surface`,
81
+ });
82
+ }
83
+ }
84
+ for (const file of corpus.files) {
85
+ const text = (0, wiki_corpus_1.wikiCorpusText)(corpus, file);
86
+ if (isCanonicalTruthPage(file, text)) {
87
+ const body = (0, workspace_1.stripMetadataHeader)(text);
88
+ const incoming = uniqueExisting((graph.incomingLinks.get(file) ?? [])
89
+ .map((link) => link.file)
90
+ .filter((source) => source !== file), corpus.fileSet);
91
+ if (incoming.length > 0 && incoming.every(isGeneratedScopedRouter) && hasFocusedAuthoritySignal(text, body)) {
92
+ diagnostics.push({
93
+ code: "weak-authority-route",
94
+ severity: "warn",
95
+ file,
96
+ message: "active canonical page with authority signals is routed only by generated auto-index pages; add a focused route when this truth is durable",
97
+ });
98
+ }
99
+ if (hasEvidenceClaimSignal(body) && !hasEvidenceLink(file, corpus, graph)) {
100
+ diagnostics.push({
101
+ code: "missing-evidence-link",
102
+ severity: "warn",
103
+ file,
104
+ message: "canonical page makes a source-backed claim but has no source link or decision_ref evidence link",
105
+ });
106
+ }
107
+ }
108
+ }
109
+ for (const file of corpus.files) {
110
+ if (file === wiki_graph_1.wikiRouterRoot || isGeneratedScopedRouter(file))
111
+ continue;
112
+ const text = (0, wiki_corpus_1.wikiCorpusText)(corpus, file);
113
+ if ((0, workspace_1.metadataValue)(text, "status") !== "active")
114
+ continue;
115
+ const incoming = uniqueExisting((graph.incomingLinks.get(file) ?? [])
116
+ .map((link) => link.file)
117
+ .filter((source) => source !== file), corpus.fileSet);
118
+ const reviewTrigger = (0, workspace_1.metadataValue)(text, "review_trigger");
119
+ if (incoming.length >= exports.topologyFanoutThreshold && isBroadReviewTrigger(reviewTrigger)) {
120
+ diagnostics.push({
121
+ code: "stale-fanout",
122
+ severity: "warn",
123
+ file,
124
+ message: `${incoming.length} incoming links with broad review_trigger "${reviewTrigger}"; tighten review trigger before broad edits`,
125
+ });
126
+ }
127
+ }
128
+ return diagnostics.sort((a, b) => a.file.localeCompare(b.file) || a.code.localeCompare(b.code));
129
+ }