project-librarian 0.2.1 → 0.3.0

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/dist/templates.js CHANGED
@@ -1,8 +1,67 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.starterFiles = exports.decisionPolicy = exports.wikiOperatingModel = exports.inboxIndexBlock = exports.glossaryIndexBlock = exports.glossary = exports.index = exports.startup = exports.metadata = exports.wikiAgentsSection = exports.cursorRule = exports.geminiSection = exports.claudeSection = exports.agentsSection = void 0;
3
+ exports.defaultStarterFilePaths = exports.starterFiles = exports.documentTaxonomy = exports.decisionPolicy = exports.wikiOperatingModel = exports.inboxIndexBlock = exports.glossaryIndexBlock = exports.glossary = exports.index = exports.startup = exports.metadata = exports.wikiAgentsSection = exports.cursorRule = exports.geminiSection = exports.claudeSection = exports.STARTUP_TLDR_MAX_CHARS = exports.startupTldrSyncLabel = exports.codeEvidenceTrustContract = exports.wikiTrustContract = void 0;
4
+ exports.extractStartupTldr = extractStartupTldr;
5
+ exports.agentsSection = agentsSection;
4
6
  const workspace_1 = require("./workspace");
5
- exports.agentsSection = `<!-- PROJECT-WIKI-FIRST:START -->
7
+ // B4 (gated on B2, which ships in the same phase): a single-sentence trust
8
+ // contract in the managed AGENTS.md block. It is the substitution mechanism that
9
+ // stops repo-wide re-verification greps; the --doctor router-truth rule (B2)
10
+ // guards against trusting a stale router.
11
+ exports.wikiTrustContract = "Wiki decision documents are authoritative for project decisions: do not re-verify them against the repository unless directly conflicting code evidence appears, since the `--doctor` router-truth rule guards against stale routers.";
12
+ // B4 analogue for code evidence: a single-sentence trust contract making the
13
+ // code-evidence tool/report outputs authoritative for code-structure questions,
14
+ // gated on the same staleness check the tools surface (`--code-status` /
15
+ // `code_status`). Mirrors wikiTrustContract scale; kept budget-conscious. The
16
+ // closing clause is the scale-conditional guidance (2026-06-12 decision, stageR1
17
+ // evidence): on small repos simple lookups measured cheaper via direct reads.
18
+ exports.codeEvidenceTrustContract = "Code-evidence tool and report outputs (`--code-impact`, `--code-report`, and the `project-librarian mcp` tools) are authoritative for code-structure questions: do not re-verify them with repo-wide greps unless `--code-status`/`code_status` reports staleness; on small repos below the measured scale threshold, prefer direct reads over these tools for simple lookups (measured cheaper at small scale).";
19
+ // B1 fallback: label for the auto-synced startup TL;DR sub-block embedded in the
20
+ // managed AGENTS.md marker section. Non-interactive `codex exec` does not run
21
+ // SessionStart hooks (measured 2026-06-10), so AGENTS.md is the only startup
22
+ // context carrier there; the sync stays TL;DR-only per token discipline.
23
+ exports.startupTldrSyncLabel = "Startup TL;DR (auto-synced for non-interactive sessions; source: wiki/startup.md)";
24
+ // Hard cap on the extracted TL;DR text embedded in AGENTS.md. The startup hook
25
+ // budget is 3500 chars for the full wiki/startup.md; the TL;DR sub-section must
26
+ // stay well under that so the managed block remains token-efficient. 2000 chars is
27
+ // a documented hard bound: it leaves headroom for frontmatter, other sections, and
28
+ // the AGENTS.md surrounding content. Per the no-fallback rule, we NEVER truncate —
29
+ // if the extracted bullets exceed this limit the sync fails loudly so the author
30
+ // knows to trim the TL;DR.
31
+ exports.STARTUP_TLDR_MAX_CHARS = 2000;
32
+ // Extract the `## TL;DR` bullet list from a startup.md body (TL;DR section ONLY —
33
+ // never Recent Decisions or Project State). Returns the `- ` bullet lines between
34
+ // the `## TL;DR` heading and the next `## ` heading. Throws loudly when the
35
+ // startup body has no `## TL;DR` section, that section has no bullets, or the
36
+ // extracted text exceeds STARTUP_TLDR_MAX_CHARS (no fallback, no silent truncation).
37
+ function extractStartupTldr(startupMarkdown) {
38
+ const match = startupMarkdown.match(/^##\s+TL;DR[^\n]*\n([\s\S]*?)(?=\n##\s|(?![\s\S]))/m);
39
+ if (!match) {
40
+ throw new Error("cannot sync startup TL;DR into AGENTS.md: wiki/startup.md has no \"## TL;DR\" section");
41
+ }
42
+ const bullets = (match[1] ?? "")
43
+ .split(/\r?\n/)
44
+ .map((line) => line.trimEnd())
45
+ .filter((line) => /^\s*-\s+\S/.test(line));
46
+ if (bullets.length === 0) {
47
+ throw new Error("cannot sync startup TL;DR into AGENTS.md: the wiki/startup.md \"## TL;DR\" section has no bullet items");
48
+ }
49
+ const result = bullets.join("\n");
50
+ if (result.length > exports.STARTUP_TLDR_MAX_CHARS) {
51
+ throw new Error(`cannot sync startup TL;DR into AGENTS.md: extracted TL;DR is ${result.length} chars, which exceeds the ${exports.STARTUP_TLDR_MAX_CHARS}-char limit; trim the ## TL;DR section in wiki/startup.md`);
52
+ }
53
+ return result;
54
+ }
55
+ // Build the managed AGENTS.md marker section. The startup TL;DR is synced in as a
56
+ // clearly labeled sub-block (B1 fallback) so non-interactive Codex sessions, which
57
+ // never run the SessionStart hook, still receive compact startup context; the
58
+ // trust contract sentence (B4) is appended to the during-conversation rules. Only
59
+ // this marker block changes; user content outside the markers is untouched, and
60
+ // because the section is built deterministically from the current startup TL;DR a
61
+ // re-run with unchanged startup yields the same section ("exists" via
62
+ // upsertMarkedSection).
63
+ function agentsSection(startupTldr) {
64
+ return `<!-- PROJECT-WIKI-FIRST:START -->
6
65
  ## Wiki-First Planning
7
66
 
8
67
  This project uses \`./wiki\` as the durable project-planning source of truth.
@@ -13,13 +72,21 @@ At the start of every session:
13
72
  2. Review \`wiki/index.md\` as the router for which files to read next.
14
73
  3. Read detailed \`wiki/canonical/\`, \`wiki/decisions/\`, \`wiki/meta/\`, and \`wiki/sources/\` files on demand only when the current question needs them.
15
74
 
75
+ ### ${exports.startupTldrSyncLabel}
76
+
77
+ ${startupTldr}
78
+
16
79
  During conversation:
17
80
 
18
81
  - Update \`./wiki\` in the same turn when project planning content is added, changed, or removed.
82
+ - Classify new project-planning content with \`wiki/meta/document-taxonomy.md\` before writing or consolidating it.
19
83
  - Do not store non-project LLM memory, assistant preferences, collaboration reminders, or workflow instructions in project wiki canonical or decision docs.
20
84
  - Follow \`wiki/AGENTS.md\` for detailed rules when editing files under \`wiki/\`.
21
85
  - Let \`.githooks/prepare-commit-msg\` append wiki trailers automatically for staged wiki, hook, AGENTS, or project-librarian files.
86
+ - ${exports.wikiTrustContract}
87
+ - ${exports.codeEvidenceTrustContract}
22
88
  <!-- PROJECT-WIKI-FIRST:END -->`;
89
+ }
23
90
  exports.claudeSection = `<!-- PROJECT-WIKI-CLAUDE:START -->
24
91
  # Claude Code Project Instructions
25
92
 
@@ -79,6 +146,14 @@ Storage boundaries:
79
146
  - Do not store non-project LLM memory, assistant preferences, collaboration reminders, or workflow instructions in \`canonical/\` or \`decisions/\`; use root \`AGENTS.md\`, compatibility instruction files, hooks, rules, or skills instead.
80
147
  - During migration review, preserve useful meaning while converting it to the current wiki structure. Legacy files, sections, blocks, and wording may be retained when review confirms they belong in the new topic shape and remain current project truth. Do not link to or cite \`wiki_legacy*\` from the new wiki; cite current-project evidence when possible and keep unresolved or ambiguous material in migration inboxes.
81
148
 
149
+ Classification rules:
150
+
151
+ - Before adding or consolidating project content, classify it with \`meta/document-taxonomy.md\`.
152
+ - Write current agreement to the narrowest durable canonical document that fits the taxonomy; do not append unrelated material to \`canonical/project-brief.md\`.
153
+ - If one input crosses several lifecycle areas, split it into separate canonical updates and link the related pages.
154
+ - If the input explains why a direction changed, update the relevant decision log or Decision Pack in addition to canonical truth.
155
+ - If an external artifact is the better source of truth (for example Figma, OpenAPI, ERD, issue tracker, or code), keep a concise canonical summary and link the external source as the authoritative location.
156
+
82
157
  Update rules:
83
158
 
84
159
  - Every wiki knowledge markdown file should include compact metadata with \`status\`, \`updated\`, \`scope\`, \`read_budget\`, \`decision_ref\`, and \`review_trigger\`. This \`wiki/AGENTS.md\` instruction file is excluded from that wiki-page metadata requirement.
@@ -117,14 +192,12 @@ exports.startup = `${(0, exports.metadata)("startup-router", "short", "wiki/meta
117
192
  - At session start, read only this file and \`wiki/index.md\` first; read detailed files on demand.
118
193
  - Project canonical content language is not fixed by this bootstrap. The LLM should choose the language that best matches the user and project context.
119
194
  - Update the wiki in the same turn when project-planning content changes.
195
+ - Classify new project-planning content with \`wiki/meta/document-taxonomy.md\` before writing or consolidating it.
120
196
 
121
197
  ## Read On Demand
122
198
 
123
199
  - [[index]]: document router.
124
- - [[canonical/project-brief]]: read only when product direction, audience, scope, success criteria, or core scenarios matter.
125
- - [[canonical/open-questions]]: read only when unresolved questions or next clarification items matter.
126
- - [[canonical/assumptions]]: read only when temporary assumptions or unverified premises matter.
127
- - [[canonical/risks]]: read only when risks, revisit triggers, or uncertainty matter.
200
+ - [[meta/document-taxonomy]]: read only when classifying or reorganizing project wiki content.
128
201
 
129
202
  ## Project State
130
203
 
@@ -179,22 +252,7 @@ This file is a router, not a file to expand into every answer. Read only the fil
179
252
 
180
253
  ## Canonical
181
254
 
182
- - [[canonical/project-brief]]
183
- - Read: product direction, audience, scope, success criteria, core scenarios.
184
- - Update: product, audience, scope, or success criteria changes.
185
- - Token budget: medium.
186
- - [[canonical/open-questions]]
187
- - Read: unresolved questions or next clarifications.
188
- - Update: questions are added, answered, or moved.
189
- - Token budget: short.
190
- - [[canonical/assumptions]]
191
- - Read: temporary assumptions or unverified premises.
192
- - Update: assumptions are added, validated, or retired.
193
- - Token budget: short.
194
- - [[canonical/risks]]
195
- - Read: risks, revisit triggers, uncertainty.
196
- - Update: risks are added, mitigated, or resolved.
197
- - Token budget: short.
255
+ No empty canonical starter pages are created by default. Create focused pages under \`wiki/canonical/\` only when durable project truth exists, then route them here or with \`--refresh-index\`.
198
256
 
199
257
  ## Project Decisions
200
258
 
@@ -202,18 +260,14 @@ This file is a router, not a file to expand into every answer. Read only the fil
202
260
  - Read: recent important project decisions.
203
261
  - Update: a decision belongs in startup context.
204
262
  - Token budget: short.
263
+ - [[decisions/README]]
264
+ - Read: decision directory structure or decision routing conventions.
265
+ - Update: decision directory conventions change.
266
+ - Token budget: short.
205
267
  - [[decisions/log]]
206
268
  - Read: project decision timing matters.
207
269
  - Update: a trivial decision needs timestamp tracking.
208
270
  - Token budget: on-demand.
209
- - [[decisions/decision-pack-template]]
210
- - Read: creating a Decision Pack.
211
- - Update: Decision Pack format changes.
212
- - Token budget: short.
213
- - [[decisions/full-adr-template]]
214
- - Read: creating a Full ADR.
215
- - Update: Full ADR format changes.
216
- - Token budget: short.
217
271
 
218
272
  ## Wiki Meta
219
273
 
@@ -221,6 +275,10 @@ This file is a router, not a file to expand into every answer. Read only the fil
221
275
  - Read: wiki operation, hooks, bootstrap, maintenance, language policy.
222
276
  - Update: wiki operation or startup behavior changes.
223
277
  - Token budget: medium.
278
+ - [[meta/document-taxonomy]]
279
+ - Read: classifying, writing, consolidating, splitting, or reorganizing project wiki content.
280
+ - Update: wiki information architecture or service-documentation categories change.
281
+ - Token budget: medium.
224
282
  - [[meta/decision-policy]]
225
283
  - Read: decision level, ADR need, canonical/decision split.
226
284
  - Update: decision classification or ADR criteria changes.
@@ -281,6 +339,7 @@ exports.wikiOperatingModel = `${(0, exports.metadata)("wiki-meta", "medium", "wi
281
339
  - Operating documents generated by bootstrap are English by default.
282
340
  - Project canonical content language is selected from user/project context, not hardcoded by this bootstrap.
283
341
  - Search, index refresh, inbox capture, and lifecycle checks are explicit script modes.
342
+ - New project content is classified through [[meta/document-taxonomy]] before it is written or consolidated.
284
343
 
285
344
  ## Purpose
286
345
 
@@ -299,6 +358,18 @@ Karpathy's LLM Wiki pattern favors a continuously maintained markdown wiki over
299
358
  5. Router: read/update/token-budget guidance in \`wiki/index.md\`.
300
359
  6. Wiki meta: operating rules, decision policy, bootstrap, migration, lint, and language policy under \`wiki/meta/\`.
301
360
 
361
+ ## Content Classification Procedure
362
+
363
+ Before writing or reorganizing project-planning content:
364
+
365
+ 1. Identify the content's lifecycle area with [[meta/document-taxonomy]].
366
+ 2. Decide whether the content is current truth, decision rationale, source evidence, an unresolved candidate, or a wiki operating rule.
367
+ 3. Write current truth to the narrowest relevant \`canonical/\` page, decision rationale to \`decisions/\`, source notes to \`sources/\`, candidates to \`inbox/\`, and wiki operating rules to \`meta/\`.
368
+ 4. Split multi-area inputs instead of making one catch-all document.
369
+ 5. Link upstream and downstream pages when the content derives from another artifact or produces another artifact.
370
+
371
+ Do not treat \`canonical/project-brief.md\` as a default dumping ground. It should summarize direction, audience, scope, and success criteria; detailed product, policy, UX, data, engineering, QA, release, or operations truth should move into focused pages when it grows.
372
+
302
373
  ## Language Policy
303
374
 
304
375
  - Bootstrap-generated operating documents are English.
@@ -371,6 +442,106 @@ Use a Full ADR when the decision affects product direction, architecture, public
371
442
  - Do not inject full canonical or decision bodies into startup context.
372
443
  - Read long decision files only when \`wiki/index.md\` routing says they are relevant.
373
444
  `;
445
+ exports.documentTaxonomy = `${(0, exports.metadata)("wiki-meta", "medium", "wiki/meta/wiki-ops-v1-decisions.md", "wiki information architecture, documentation categories, or content classification rules change")}
446
+ # Document Taxonomy
447
+
448
+ ## TL;DR
449
+
450
+ - Classify new project-planning content before writing it into the wiki.
451
+ - Use this page to classify content into \`canonical/\`, \`decisions/\`, \`sources/\`, \`inbox/\`, or \`meta/\`.
452
+ - Keep \`canonical/project-brief.md\` compact; move detailed truth into focused canonical pages.
453
+ - Preserve derivation links: evidence -> strategy -> requirements -> design/data/engineering -> QA -> release/operations -> feedback.
454
+
455
+ ## Top-Level Flow
456
+
457
+ \`\`\`text
458
+ 0. Source-of-truth governance
459
+ -> 1. Research and evidence
460
+ -> 2. Strategy and business model
461
+ -> 3. Product scope and requirements
462
+ -> 4. Policies and rules
463
+ -> 5. UX and content
464
+ -> 6. Design
465
+ -> 7. Data and analytics
466
+ -> 8. Engineering
467
+ -> 9. Security, legal, compliance
468
+ -> 10. QA and verification
469
+ -> 11. Release and operations
470
+ -> 12. Business operations
471
+ -> 13. Improvement, migration, and end-of-life
472
+ -> next product scope, policy, or roadmap change
473
+ \`\`\`
474
+
475
+ ## Storage Decision
476
+
477
+ | Content Type | Store In | Notes |
478
+ | --- | --- | --- |
479
+ | Current valid project truth | \`wiki/canonical/\` | Split by topic and read frequency. |
480
+ | Why a choice was made | \`wiki/decisions/\` | Use log, Decision Pack, or ADR by impact. |
481
+ | Source material or summarized evidence | \`wiki/sources/\` | Keep links, checked dates, and applicability. |
482
+ | Unreviewed or ambiguous material | \`wiki/inbox/\` | Do not treat as canonical truth. |
483
+ | Wiki operation, taxonomy, hooks, migration, lint, language rules | \`wiki/meta/\` | Keep outside project canonical truth. |
484
+ | Better external source of truth | External artifact plus a concise wiki route | Examples: Figma, OpenAPI, ERD, Jira, code. |
485
+
486
+ ## Lifecycle Areas
487
+
488
+ | Area | Put Here | Usually Derives From | Usually Produces |
489
+ | --- | --- | --- | --- |
490
+ | 0. Governance | source-of-truth map, owners, RACI, approval flow, change rules, glossary, state dictionary, assumptions, risk register | team/process constraints | routing, ownership, conflict resolution |
491
+ | 1. Research | market, competitor, user interviews, VOC, analytics, legal/regulatory, technical feasibility, cost, vendor, accessibility research | raw discovery | strategy, risks, sources |
492
+ | 2. Strategy | service overview, vision, problem, target users, personas, jobs-to-be-done, value offer, positioning, business model, KPI/OKR, success/stop criteria, roadmap, MVP, non-goals | research | PRD, roadmap, scope |
493
+ | 3. Product | PRD, user stories, use cases, priorities, backlog rules, acceptance criteria, feature spec, exceptions, state definitions, notification rules, search/filter/sort rules, admin requirements | strategy and policy | UX, API, data, QA |
494
+ | 4. Policy | operations policy, auth/account, permissions, pricing, payment/refund, coupon/credit, content moderation, notifications, retention/deletion, abuse response, support, EOL | business model, law, risks | feature constraints, API rules, CS/ops |
495
+ | 5. UX and Content | IA, sitemap, user flow, task flow, screen list, wireframes, screen specs, content model, UX writing, empty/error states, help/FAQ, localization, SEO, accessibility criteria | product and policy | design, frontend, QA |
496
+ | 6. Design | brand guide, design principles, design system, component spec, responsive rules, interaction spec, prototype, design QA | UX and brand | UI implementation, design QA |
497
+ | 7. Data and Analytics | ERD, data dictionary, classification, ownership/stewardship, event taxonomy, metric definitions, funnels, data quality, lineage, export/import, anonymization, dashboards | product, policy, KPI | schemas, events, reports |
498
+ | 8. Engineering | architecture, technology decisions, API/OpenAPI, integrations, webhooks/idempotency, state machines, jobs/cron, error codes, env vars, secrets, local dev, conventions, branch/release strategy, CI/CD, feature flags, dependencies, migrations, performance, scalability, FinOps | product, UX, data, policy | implementation and verification |
499
+ | 9. Security/Legal | security requirements, threat model, privacy rules, privacy impact, terms, privacy policy, audit logs, permission history, internal access controls, key rotation, vulnerability response, licenses, vendor and DPA documents | data, architecture, law | controls, tests, release gates |
500
+ | 10. QA | test strategy, QA scenarios, test cases, regression checklist, UAT, browser/device matrix, accessibility tests, performance tests, security tests, data quality tests, design QA, quality gates | requirements, design, engineering | release approval |
501
+ | 11. Release/Ops | release plan, deployment procedure, rollback, release notes, operator manual, runbooks, monitoring, observability, SLO/SLA, on-call/escalation, incident response, backup/restore, DR/BCP, recurring checks | QA and infrastructure | stable operation |
502
+ | 12. Business Ops | CS macros, support policy, training, sales/adoption guide, CRM rules, onboarding playbook, churn/offboarding, admin operations, communication templates, revenue recognition, tax/invoice, partner operations | policy, release, sales motion | customer-facing operation |
503
+ | 13. Improvement/EOL | VOC summary, retrospectives, experiments, experiment results, cohort/retention analysis, feature deprecation, migration/data transfer, service end-of-life | operations and analytics | next PRD, policy, roadmap |
504
+
505
+ ## Writing Rule
506
+
507
+ When a new note arrives:
508
+
509
+ 1. Identify the lifecycle area and storage location.
510
+ 2. Update an existing focused page when the topic already has a canonical home.
511
+ 3. Create a new focused page only when the topic is durable, likely to be read independently, or too large for its current page.
512
+ 4. Add an \`index.md\` route when the page becomes durable.
513
+ 5. Add upstream/downstream links in prose or tables when one artifact derives from another.
514
+ 6. Record decision rationale separately when the change explains why the project chose one option over another.
515
+
516
+ ## Page Shape
517
+
518
+ Use this shape for focused canonical pages unless a domain-specific shape is clearly better:
519
+
520
+ \`\`\`md
521
+ ---
522
+ status: active
523
+ updated: YYYY-MM-DD
524
+ scope: project-canonical
525
+ read_budget: short|medium|on-demand
526
+ decision_ref: none|wiki/decisions/...
527
+ review_trigger: what should cause this page to change
528
+ ---
529
+
530
+ # <Topic>
531
+
532
+ ## TL;DR
533
+
534
+ - Current agreement in one to five bullets.
535
+
536
+ ## Current Truth
537
+
538
+ ## Upstream Inputs
539
+
540
+ ## Downstream Artifacts
541
+
542
+ ## Open Questions
543
+ \`\`\`
544
+ `;
374
545
  exports.starterFiles = {
375
546
  "wiki/README.md": `${(0, exports.metadata)("wiki-entry", "short", "wiki/meta/wiki-ops-v1-decisions.md", "top-level wiki structure changes")}
376
547
  # Project Wiki
@@ -381,10 +552,7 @@ This directory is the durable project-planning source of truth. Keep product dir
381
552
 
382
553
  - [[startup]]
383
554
  - [[index]]
384
- - [[canonical/project-brief]]
385
- - [[canonical/open-questions]]
386
- - [[canonical/assumptions]]
387
- - [[canonical/risks]]
555
+ - [[meta/document-taxonomy]]
388
556
  `,
389
557
  "wiki/canonical/project-brief.md": `${(0, exports.metadata)("project-canonical", "medium", "none", "product direction, audience, scope, success criteria, or language choice changes")}
390
558
  # Project Brief
@@ -502,17 +670,18 @@ No project decisions yet.
502
670
  ## TL;DR
503
671
 
504
672
  - This Decision Pack records accepted wiki operating choices for project-librarian.
505
- - It covers wiki structure, startup hook scope, metadata, language policy, git hook behavior, migration review, and inbox handling.
673
+ - It covers wiki structure, document taxonomy, startup hook scope, metadata, language policy, git hook behavior, migration review, and inbox handling.
506
674
  - Project product decisions belong in \`wiki/decisions/\`, while these operating decisions stay in \`wiki/meta/\`.
507
675
 
508
676
  Status: accepted
509
677
  Scope: wiki operation
510
- Canonical: [[meta/operating-model]], [[meta/decision-policy]]
678
+ Canonical: [[meta/operating-model]], [[meta/decision-policy]], [[meta/document-taxonomy]]
511
679
 
512
680
  | Date | Decision | Rationale | Rejected Alternative | Revisit Trigger | Canonical Link |
513
681
  | --- | --- | --- | --- | --- | --- |
514
682
  | ${workspace_1.today} | Keep the wiki root at \`./wiki\`. | Planning docs live with the project. | External docs only. | Another tool cannot read \`./wiki\` or the team needs another path. | [[meta/operating-model]] |
515
683
  | ${workspace_1.today} | Split \`canonical/\` and \`decisions/\`. | Current truth and decision history are easier to scan when separated. | A single mixed docs directory. | The structure proves too heavy for small projects. | [[meta/decision-policy]] |
684
+ | ${workspace_1.today} | Classify new wiki content through a service-lifecycle document taxonomy before writing or consolidating it. | Agents need a structural contract for deciding whether content is strategy, product, policy, UX, design, data, engineering, security/legal, QA, release/ops, business ops, or improvement/EOL truth. | Let agents append new content to whichever existing page is nearby. | The taxonomy becomes too heavy for small projects or repeatedly misroutes content. | [[meta/document-taxonomy]], [[meta/operating-model]] |
516
685
  | ${workspace_1.today} | Inject only \`startup.md\` and \`index.md\` through Codex, Claude Code, Cursor, and Gemini CLI startup hooks; route detailed files Read On Demand. | Full canonical and decision bodies waste startup tokens. | Always read detailed canonical and decision files first. | Important context is repeatedly missed at startup. | [[startup]], [[index]] |
517
686
  | ${workspace_1.today} | Use metadata headers on wiki knowledge pages. | Agents and humans can quickly judge status, scope, budget, and review triggers. | Body-only conventions. | Header maintenance costs more than it saves. | [[meta/operating-model]] |
518
687
  | ${workspace_1.today} | Keep wiki operating docs in \`wiki/meta/\`. | Project truth stays focused on product/project content. | Store operating docs in \`canonical/\` or \`decisions/\`. | Meta docs become hard to discover. | [[meta/operating-model]] |
@@ -523,6 +692,7 @@ Canonical: [[meta/operating-model]], [[meta/decision-policy]]
523
692
  | ${workspace_1.today} | Migration may mark rows \`needs-human-review\`. | Ambiguous, risky, or high-impact legacy content should not be closed automatically. | Force every migrated row into adopted/rejected/resolved. | Human review queues become too large. | [[meta/operating-model]] |
524
693
  | ${workspace_1.today} | Capture stores candidates in \`wiki/inbox/\`. | Useful ideas are not lost, but unreviewed content does not become canonical truth. | Save all conversation content directly into canonical docs. | Inbox content is frequently abandoned. | [[meta/operating-model]] |
525
694
  `,
695
+ "wiki/meta/document-taxonomy.md": exports.documentTaxonomy,
526
696
  "wiki/decisions/decision-pack-template.md": `${(0, exports.metadata)("project-decision-template", "short", "wiki/meta/decision-policy.md", "decision pack format changes", "template")}
527
697
  # <Topic> v<N> Decisions
528
698
 
@@ -571,3 +741,11 @@ Checked: ${workspace_1.today}
571
741
  - \`wiki/meta/\` stores wiki operating rules and operating decisions.
572
742
  `,
573
743
  };
744
+ exports.defaultStarterFilePaths = new Set([
745
+ "wiki/README.md",
746
+ "wiki/decisions/README.md",
747
+ "wiki/decisions/log.md",
748
+ "wiki/decisions/recent.md",
749
+ "wiki/meta/document-taxonomy.md",
750
+ "wiki/sources/karpathy-llm-wiki.md",
751
+ ]);
@@ -46,10 +46,9 @@ exports.extractWikiLinks = extractWikiLinks;
46
46
  exports.wikiTitleForFile = wikiTitleForFile;
47
47
  exports.metadataSummary = metadataSummary;
48
48
  exports.stripMarkedSection = stripMarkedSection;
49
- exports.extractMarkedSection = extractMarkedSection;
50
- exports.withPreservedMarkedSections = withPreservedMarkedSections;
51
49
  exports.hasGlossaryNeedSignal = hasGlossaryNeedSignal;
52
50
  exports.hasGlossaryTable = hasGlossaryTable;
51
+ exports.firstTldrBullet = firstTldrBullet;
53
52
  exports.canonicalBodyForLint = canonicalBodyForLint;
54
53
  const fs = __importStar(require("node:fs"));
55
54
  const path = __importStar(require("node:path"));
@@ -75,24 +74,22 @@ exports.standardWikiFiles = new Set([
75
74
  "wiki/index.md",
76
75
  "wiki/inbox/project-candidates.md",
77
76
  "wiki/migration/inventory.md",
77
+ "wiki/migration/unit-map.md",
78
+ "wiki/migration/split-plan.md",
78
79
  "wiki/migration/coverage.md",
79
80
  "wiki/migration/plan.md",
80
81
  "wiki/migration/review.md",
81
82
  "wiki/migration/verification.md",
82
- "wiki/canonical/project-brief.md",
83
+ "wiki/migration/bulk-review.md",
83
84
  "wiki/canonical/glossary.md",
84
- "wiki/canonical/open-questions.md",
85
- "wiki/canonical/assumptions.md",
86
- "wiki/canonical/risks.md",
87
85
  "wiki/canonical/migration-inbox.md",
88
86
  "wiki/decisions/README.md",
89
87
  "wiki/decisions/log.md",
90
88
  "wiki/decisions/recent.md",
91
- "wiki/decisions/decision-pack-template.md",
92
- "wiki/decisions/full-adr-template.md",
93
89
  "wiki/decisions/migration-inbox.md",
94
90
  "wiki/meta/operating-model.md",
95
91
  "wiki/meta/decision-policy.md",
92
+ "wiki/meta/document-taxonomy.md",
96
93
  "wiki/meta/wiki-ops-v1-decisions.md",
97
94
  "wiki/sources/karpathy-llm-wiki.md",
98
95
  "wiki/sources/migration-inbox.md",
@@ -264,25 +261,6 @@ function stripMarkedSection(text, startMarker, endMarker) {
264
261
  return text;
265
262
  return `${text.slice(0, start).trimEnd()}\n\n${text.slice(end + endMarker.length).trimStart()}`.trim() + "\n";
266
263
  }
267
- function extractMarkedSection(text, startMarker, endMarker) {
268
- const start = text.indexOf(startMarker);
269
- const end = text.indexOf(endMarker);
270
- if (start < 0 || end <= start)
271
- return "";
272
- return text.slice(start, end + endMarker.length).trim();
273
- }
274
- function withPreservedMarkedSections(relativePath, base, markerPairs) {
275
- if (!(0, workspace_1.exists)(relativePath))
276
- return base;
277
- const current = (0, workspace_1.read)(relativePath);
278
- const preserved = markerPairs
279
- .map(([startMarker, endMarker]) => extractMarkedSection(current, startMarker, endMarker))
280
- .filter(Boolean)
281
- .filter((section) => !base.includes(section));
282
- if (preserved.length === 0)
283
- return base;
284
- return `${base.trimEnd()}\n\n${preserved.join("\n\n")}\n`;
285
- }
286
264
  function hasGlossaryNeedSignal(text) {
287
265
  return /(^|\n)##\s+(Glossary|Terms|Roles|Entities|Data Model|State Model|Permissions|Events|용어|역할|엔티티|상태 모델|권한|이벤트)(\s|$)|`[^`]+`\s*(term|role|state|permission|event|entity|API|DB|UI|용어|역할|상태|권한|이벤트|엔티티)/i.test(text);
288
266
  }
@@ -290,6 +268,16 @@ function hasGlossaryTable(text) {
290
268
  const body = (0, workspace_1.stripMetadataHeader)(text);
291
269
  return /\|\s*Term\s*\|\s*Definition\s*\|\s*Avoid\s*\|\s*Related Canonical Doc\s*\|\s*Status\s*\|/.test(body);
292
270
  }
271
+ // First "## TL;DR" bullet for answer-shaped query envelopes: gives an agent the
272
+ // page's one-line summary without opening the page. Pages without a TL;DR section
273
+ // return "" and the envelope simply omits the line — quality-check separately
274
+ // flags the missing TL;DR, so this is optional enrichment, not a fallback path.
275
+ function firstTldrBullet(text) {
276
+ const body = (0, workspace_1.stripMetadataHeader)(text);
277
+ const match = body.match(/^##\s+TL;DR[^\n]*\n([\s\S]*?)(?=\n##\s|(?![\s\S]))/m);
278
+ const bullet = match?.[1]?.split(/\r?\n/).find((line) => /^\s*-\s+\S/.test(line));
279
+ return bullet ? bullet.replace(/^\s*-\s*/, "").trim().slice(0, 160) : "";
280
+ }
293
281
  function canonicalBodyForLint() {
294
282
  return (0, workspace_1.walkFilesUnder)("wiki/canonical", (file) => /\.(md|mdx)$/i.test(file) && file !== "wiki/canonical/glossary.md")
295
283
  .map((file) => (0, workspace_1.stripMetadataHeader)((0, workspace_1.read)(file)))
@@ -0,0 +1,141 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.wikiAnswerTruncationNotice = exports.wikiAnswerCharCap = exports.wikiRouterExemptPages = exports.wikiRouterDepthBudget = exports.wikiRouterRoot = void 0;
4
+ exports.finalizeWikiAnswer = finalizeWikiAnswer;
5
+ exports.buildWikiGraph = buildWikiGraph;
6
+ exports.wikiRouterDepths = wikiRouterDepths;
7
+ exports.wikiImpactAnswer = wikiImpactAnswer;
8
+ const wiki_files_1 = require("./wiki-files");
9
+ const workspace_1 = require("./workspace");
10
+ // Router reachability budget. The benchmark fixture A1 assert guarantees
11
+ // startup -> index -> answer page within two hops; real wikis add one hop for
12
+ // generated scoped routers (startup -> index -> wiki/indexes/auto-*.md -> page),
13
+ // so the real-wiki budget is three hops from wiki/startup.md.
14
+ exports.wikiRouterRoot = "wiki/startup.md";
15
+ exports.wikiRouterDepthBudget = 3;
16
+ // startup is the BFS root; README is a human entry document that is deliberately
17
+ // unrouted (the same exemption the orphan-page rule uses).
18
+ exports.wikiRouterExemptPages = new Set([exports.wikiRouterRoot, "wiki/README.md"]);
19
+ // Answer-shape discipline for wiki-side query/impact output: answer-first text,
20
+ // hard char cap, explicit truncation notice (never silent). Mirrors the MCP
21
+ // server constants (src/mcp-server.ts MAX_RESPONSE_CHARS / TRUNCATION_NOTICE);
22
+ // kept separate so the MCP server module and its node:sqlite loading path stay
23
+ // out of the bootstrap/diagnostics path.
24
+ exports.wikiAnswerCharCap = 4000;
25
+ exports.wikiAnswerTruncationNotice = "[truncated — refine the query]";
26
+ function finalizeWikiAnswer(body) {
27
+ if (body.length <= exports.wikiAnswerCharCap)
28
+ return body;
29
+ const budget = exports.wikiAnswerCharCap - exports.wikiAnswerTruncationNotice.length - 1;
30
+ return `${body.slice(0, budget > 0 ? budget : 0).trimEnd()}\n${exports.wikiAnswerTruncationNotice}`;
31
+ }
32
+ // decision_ref is frontmatter, not a wiki link, so the link extractor never sees
33
+ // it; normalize it here into a page edge. "none"/"-" are the documented empty
34
+ // markers in generated metadata headers.
35
+ function normalizedDecisionRef(file, value) {
36
+ const trimmed = value.trim();
37
+ if (!trimmed || trimmed === "none" || trimmed === "-")
38
+ return "";
39
+ return (0, wiki_files_1.normalizeWikiLinkTarget)(file, trimmed.replace(/^\[\[|\]\]$/g, ""));
40
+ }
41
+ function buildWikiGraph(pages) {
42
+ const files = new Set(pages.map((page) => page.file));
43
+ const links = [];
44
+ const incomingLinks = new Map();
45
+ const outgoingLinks = new Map();
46
+ const incomingDecisionRefs = new Map();
47
+ const outgoingDecisionRef = new Map();
48
+ for (const page of pages) {
49
+ for (const link of (0, wiki_files_1.extractWikiLinks)(page.file, page.text)) {
50
+ links.push(link);
51
+ outgoingLinks.set(page.file, [...(outgoingLinks.get(page.file) ?? []), link]);
52
+ incomingLinks.set(link.normalizedTarget, [...(incomingLinks.get(link.normalizedTarget) ?? []), link]);
53
+ }
54
+ const ref = normalizedDecisionRef(page.file, (0, workspace_1.metadataValue)(page.text, "decision_ref"));
55
+ if (ref && files.has(ref)) {
56
+ outgoingDecisionRef.set(page.file, ref);
57
+ incomingDecisionRefs.set(ref, [...(incomingDecisionRefs.get(ref) ?? []), page.file]);
58
+ }
59
+ }
60
+ return { files, links, incomingLinks, outgoingLinks, incomingDecisionRefs, outgoingDecisionRef };
61
+ }
62
+ // BFS depths over existing pages from wiki/startup.md (depth 0). Pages absent
63
+ // from the result are unreachable through the router chain. Only links whose
64
+ // target exists are traversed; broken links are the broken-link rule's job.
65
+ function wikiRouterDepths(graph) {
66
+ const depths = new Map();
67
+ if (!graph.files.has(exports.wikiRouterRoot))
68
+ return depths;
69
+ depths.set(exports.wikiRouterRoot, 0);
70
+ const queue = [exports.wikiRouterRoot];
71
+ while (queue.length > 0) {
72
+ const current = queue.shift();
73
+ const depth = depths.get(current) ?? 0;
74
+ for (const link of graph.outgoingLinks.get(current) ?? []) {
75
+ const target = link.normalizedTarget;
76
+ if (!graph.files.has(target) || depths.has(target))
77
+ continue;
78
+ depths.set(target, depth + 1);
79
+ queue.push(target);
80
+ }
81
+ }
82
+ return depths;
83
+ }
84
+ // Wiki impact: the --code-impact envelope shape applied to wiki maintenance.
85
+ // Given a page or term, report which pages link to it (review candidates when it
86
+ // changes), which pages cite it as decision_ref, what it depends on, and how the
87
+ // router reaches it. Bounded by sampling plus the shared answer cap.
88
+ const impactMatchCap = 5;
89
+ const impactListCap = 12;
90
+ function sampled(items, cap) {
91
+ if (items.length === 0)
92
+ return "none";
93
+ const shown = items.slice(0, cap).join(", ");
94
+ return items.length > cap ? `${shown}, …+${items.length - cap} more` : shown;
95
+ }
96
+ function uniqueSorted(values) {
97
+ return Array.from(new Set(values)).sort();
98
+ }
99
+ function plural(count, noun) {
100
+ return `${count} ${noun}${count === 1 ? "" : "s"}`;
101
+ }
102
+ function wikiImpactAnswer(pages, term) {
103
+ const graph = buildWikiGraph(pages);
104
+ const depths = wikiRouterDepths(graph);
105
+ const textByFile = new Map(pages.map((page) => [page.file, page.text]));
106
+ const lowered = term.toLowerCase();
107
+ const exactTarget = (0, wiki_files_1.normalizeWikiLinkTarget)("wiki/index.md", term.replace(/^\[\[|\]\]$/g, ""));
108
+ const matches = pages
109
+ .map((page) => ({ file: page.file, title: (0, wiki_files_1.wikiTitleForFile)(page.file, page.text) }))
110
+ .filter((page) => page.file === exactTarget || page.file.toLowerCase().includes(lowered) || page.title.toLowerCase().includes(lowered))
111
+ .sort((a, b) => Number(b.file === exactTarget) - Number(a.file === exactTarget) || a.file.localeCompare(b.file));
112
+ if (matches.length === 0)
113
+ return `Wiki impact "${term}": no matching wiki pages.`;
114
+ const shown = matches.slice(0, impactMatchCap);
115
+ // Headline counts are unions across the shown matches, not sums: a page that
116
+ // links two matched targets is one review candidate, not two.
117
+ const incomingUnion = new Set(shown.flatMap((match) => (graph.incomingLinks.get(match.file) ?? []).map((link) => link.file)));
118
+ const refUnion = new Set(shown.flatMap((match) => graph.incomingDecisionRefs.get(match.file) ?? []));
119
+ const lines = [
120
+ `Wiki impact "${term}": ${plural(matches.length, "matching page")}${matches.length > impactMatchCap ? ` (top ${impactMatchCap} shown)` : ""}; review the ${plural(incomingUnion.size, "linking page")} and ${plural(refUnion.size, "decision_ref citation")} below when ${matches.length === 1 ? "this page changes" : "these pages change"}.`,
121
+ ];
122
+ for (const match of shown) {
123
+ const text = textByFile.get(match.file) ?? "";
124
+ const incoming = uniqueSorted((graph.incomingLinks.get(match.file) ?? []).map((link) => link.file));
125
+ const refs = uniqueSorted(graph.incomingDecisionRefs.get(match.file) ?? []);
126
+ const outgoing = uniqueSorted((graph.outgoingLinks.get(match.file) ?? []).map((link) => link.normalizedTarget));
127
+ const depth = depths.get(match.file);
128
+ const trigger = (0, workspace_1.metadataValue)(text, "review_trigger");
129
+ lines.push("");
130
+ lines.push(`${match.file} — ${match.title}`);
131
+ if (trigger)
132
+ lines.push(` review_trigger: ${trigger}`);
133
+ lines.push(` incoming links (${incoming.length}): ${sampled(incoming, impactListCap)}`);
134
+ lines.push(` decision_ref from (${refs.length}): ${sampled(refs, impactListCap)}`);
135
+ lines.push(` outgoing links (${outgoing.length}): ${sampled(outgoing, impactListCap)}`);
136
+ lines.push(depth === undefined
137
+ ? ` router: unreachable from ${exports.wikiRouterRoot}`
138
+ : ` router: reachable at depth ${depth} (budget ${exports.wikiRouterDepthBudget})`);
139
+ }
140
+ return finalizeWikiAnswer(lines.join("\n"));
141
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "project-librarian",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "Create and maintain compact project context for humans and LLM coding agents.",
5
5
  "license": "MIT",
6
6
  "type": "commonjs",
@@ -28,8 +28,6 @@
28
28
  "LICENSE",
29
29
  "README.md",
30
30
  "README.ko.md",
31
- "README.ja.md",
32
- "README.zh.md",
33
31
  "SKILL.md"
34
32
  ],
35
33
  "bin": {
@@ -45,6 +43,8 @@
45
43
  "benchmark:llm": "npm run build && node benchmarks/codex-llm-metrics.js",
46
44
  "benchmark:llm:dry-run": "npm run build && node benchmarks/codex-llm-metrics.js --dry-run",
47
45
  "benchmark:llm:parse-smoke": "node tests/validators/codex-llm-benchmark-smoke.js",
46
+ "benchmark:injection-sentinel": "node benchmarks/tools/injection-sentinel.js",
47
+ "benchmark:real-corpus:demo": "npm run build && node benchmarks/tools/real-corpus-offline-demo.js",
48
48
  "benchmark:release": "npm run benchmark:llm -- --full-matrix --runs 3 --warmup-runs 1 --min-runs-for-claim 3 --require-clean --require-claimable --out benchmarks/reports/llm/current.json --markdown benchmarks/reports/llm/current.md",
49
49
  "build": "tsc && chmod +x dist/init-project-wiki.js",
50
50
  "typecheck": "tsc --noEmit",