@justyork/repo-mind 0.8.0 → 0.9.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/README.md +29 -2
- package/dist/ask/ask.d.ts +7 -0
- package/dist/ask/ask.js +68 -0
- package/dist/ask/citations.d.ts +6 -0
- package/dist/ask/citations.js +40 -0
- package/dist/ask/config.d.ts +9 -0
- package/dist/ask/config.js +19 -0
- package/dist/ask/llm.d.ts +8 -0
- package/dist/ask/llm.js +110 -0
- package/dist/ask/prompt.d.ts +5 -0
- package/dist/ask/prompt.js +39 -0
- package/dist/ask/retrieve.d.ts +4 -0
- package/dist/ask/retrieve.js +38 -0
- package/dist/ask/search.d.ts +7 -0
- package/dist/ask/search.js +232 -0
- package/dist/ask/small-talk.d.ts +2 -0
- package/dist/ask/small-talk.js +35 -0
- package/dist/ask/suggestions.d.ts +12 -0
- package/dist/ask/suggestions.js +145 -0
- package/dist/ask/types.d.ts +27 -0
- package/dist/ask/types.js +1 -0
- package/dist/cli.js +6 -1
- package/dist/commands/export.d.ts +2 -0
- package/dist/commands/export.js +6 -4
- package/dist/commands/ui.js +6 -0
- package/dist/env/load-repomind-env.d.ts +10 -0
- package/dist/env/load-repomind-env.js +96 -0
- package/dist/ui/api-handlers.d.ts +1 -0
- package/dist/ui/api-handlers.js +84 -0
- package/dist/ui/draft-api.js +2 -2
- package/dist/ui/server.js +6 -1
- package/package.json +2 -1
- package/ui/dist/assets/{arc-D8hdO1rw.js → arc-BN7_EkMD.js} +1 -1
- package/ui/dist/assets/{architectureDiagram-3BPJPVTR-DD8y2xkc.js → architectureDiagram-3BPJPVTR-B8enQ17Y.js} +1 -1
- package/ui/dist/assets/{blockDiagram-GPEHLZMM-CC_b0LTp.js → blockDiagram-GPEHLZMM-BnmnlgNm.js} +1 -1
- package/ui/dist/assets/{c4Diagram-AAUBKEIU-D0xWINWL.js → c4Diagram-AAUBKEIU-DoP4UJAJ.js} +1 -1
- package/ui/dist/assets/channel-DqyPrNMh.js +1 -0
- package/ui/dist/assets/{chunk-2J33WTMH-BU6b81sJ.js → chunk-2J33WTMH-Bls2Xdkl.js} +1 -1
- package/ui/dist/assets/{chunk-4BX2VUAB-DZAlHkZW.js → chunk-4BX2VUAB-DCIvYWrM.js} +1 -1
- package/ui/dist/assets/{chunk-55IACEB6-AiC0yewf.js → chunk-55IACEB6-BZCrHrSY.js} +1 -1
- package/ui/dist/assets/{chunk-727SXJPM-DThb082L.js → chunk-727SXJPM-CyTz7y8e.js} +1 -1
- package/ui/dist/assets/{chunk-AQP2D5EJ-BPfxgjqM.js → chunk-AQP2D5EJ-kOonrdhC.js} +1 -1
- package/ui/dist/assets/{chunk-FMBD7UC4-BPN8p0X8.js → chunk-FMBD7UC4-BVCWYiIY.js} +1 -1
- package/ui/dist/assets/{chunk-ND2GUHAM-Fg3EXATp.js → chunk-ND2GUHAM-CiV-9tNZ.js} +1 -1
- package/ui/dist/assets/{chunk-QZHKN3VN-BfNeOxQX.js → chunk-QZHKN3VN-DAimc2AE.js} +1 -1
- package/ui/dist/assets/classDiagram-4FO5ZUOK-CwifX_Y1.js +1 -0
- package/ui/dist/assets/classDiagram-v2-Q7XG4LA2-CwifX_Y1.js +1 -0
- package/ui/dist/assets/{cose-bilkent-S5V4N54A-BajfXpNb.js → cose-bilkent-S5V4N54A-B4yubD-y.js} +1 -1
- package/ui/dist/assets/{dagre-BM42HDAG-BOr2m0CZ.js → dagre-BM42HDAG-EoElLLGl.js} +1 -1
- package/ui/dist/assets/{diagram-2AECGRRQ-0NMPI4vh.js → diagram-2AECGRRQ-Df9wv121.js} +1 -1
- package/ui/dist/assets/{diagram-5GNKFQAL-CTuUvbHc.js → diagram-5GNKFQAL-Ck5dW8ww.js} +1 -1
- package/ui/dist/assets/{diagram-KO2AKTUF-BQMPycKH.js → diagram-KO2AKTUF-DQqRKGqm.js} +1 -1
- package/ui/dist/assets/{diagram-LMA3HP47-0EqSTR8-.js → diagram-LMA3HP47-Dua6RRw8.js} +1 -1
- package/ui/dist/assets/{diagram-OG6HWLK6-Cn7vgxcz.js → diagram-OG6HWLK6-B3Ogt3zv.js} +1 -1
- package/ui/dist/assets/{erDiagram-TEJ5UH35-DCHJaPi9.js → erDiagram-TEJ5UH35-BYdkXgPm.js} +1 -1
- package/ui/dist/assets/{flowDiagram-I6XJVG4X-GcCMnaHc.js → flowDiagram-I6XJVG4X-DxwAsXVj.js} +1 -1
- package/ui/dist/assets/{ganttDiagram-6RSMTGT7-4NzDSonj.js → ganttDiagram-6RSMTGT7-CkvGosG3.js} +1 -1
- package/ui/dist/assets/{gitGraphDiagram-PVQCEYII-BXLfDhC_.js → gitGraphDiagram-PVQCEYII-xeD8b-c2.js} +1 -1
- package/ui/dist/assets/{graph-CxZcS9dt.js → graph-D1eDjihg.js} +1 -1
- package/ui/dist/assets/{infoDiagram-5YYISTIA-DLHHvQ1b.js → infoDiagram-5YYISTIA-CZX2wTOb.js} +1 -1
- package/ui/dist/assets/{ishikawaDiagram-YF4QCWOH-BtqY11Uk.js → ishikawaDiagram-YF4QCWOH-B97H92r3.js} +1 -1
- package/ui/dist/assets/{journeyDiagram-JHISSGLW-lBwhxW7E.js → journeyDiagram-JHISSGLW-Djcx9Sr4.js} +1 -1
- package/ui/dist/assets/{kanban-definition-UN3LZRKU-C4Q8frPQ.js → kanban-definition-UN3LZRKU-DGmylaB2.js} +1 -1
- package/ui/dist/assets/main-B3H8-mmv.js +365 -0
- package/ui/dist/assets/{mermaid.core-DdiilVI8.js → mermaid.core-CkbXj8L9.js} +4 -4
- package/ui/dist/assets/{mindmap-definition-RKZ34NQL-CzSxcpUz.js → mindmap-definition-RKZ34NQL-BxiFVhcQ.js} +1 -1
- package/ui/dist/assets/{pieDiagram-4H26LBE5-VQ1Y2aMu.js → pieDiagram-4H26LBE5-CIAMX7m1.js} +1 -1
- package/ui/dist/assets/{quadrantDiagram-W4KKPZXB-DHxOr_Bb.js → quadrantDiagram-W4KKPZXB-BjcX-J56.js} +1 -1
- package/ui/dist/assets/{requirementDiagram-4Y6WPE33-IruBFJXP.js → requirementDiagram-4Y6WPE33-DDYZmURd.js} +1 -1
- package/ui/dist/assets/{sankeyDiagram-5OEKKPKP-J4usxNW5.js → sankeyDiagram-5OEKKPKP-Dp3WtUXa.js} +1 -1
- package/ui/dist/assets/{sequenceDiagram-3UESZ5HK-C6C-kFaI.js → sequenceDiagram-3UESZ5HK-DQALrsRY.js} +1 -1
- package/ui/dist/assets/{stateDiagram-AJRCARHV-CJsdQK0K.js → stateDiagram-AJRCARHV-Bi2QeigX.js} +1 -1
- package/ui/dist/assets/stateDiagram-v2-BHNVJYJU-pbZREyTr.js +1 -0
- package/ui/dist/assets/theme-CROa1dkI.js +21 -0
- package/ui/dist/assets/theme-CiCs9Mn_.css +1 -0
- package/ui/dist/assets/{timeline-definition-PNZ67QCA-DR-akfNv.js → timeline-definition-PNZ67QCA-C8K5LzbR.js} +1 -1
- package/ui/dist/assets/{vennDiagram-CIIHVFJN-HohIp6U7.js → vennDiagram-CIIHVFJN-Bf0WkuCL.js} +1 -1
- package/ui/dist/assets/visual-editor-B6GUowO4.js +177 -0
- package/ui/dist/assets/{wardley-L42UT6IY-DI0ohKT0.js → wardley-L42UT6IY-R45K8ohg.js} +1 -1
- package/ui/dist/assets/{wardleyDiagram-YWT4CUSO-C1s7Fzl1.js → wardleyDiagram-YWT4CUSO-CZNd6CHT.js} +1 -1
- package/ui/dist/assets/{xychartDiagram-2RQKCTM6-BbPEPR4c.js → xychartDiagram-2RQKCTM6-ClCu9u0t.js} +1 -1
- package/ui/dist/graph.html +14 -3
- package/ui/dist/index.html +14 -3
- package/ui/dist/theme-boot.js +16 -0
- package/ui/dist/assets/channel-BlZV_pp1.js +0 -1
- package/ui/dist/assets/classDiagram-4FO5ZUOK-DeRilEwX.js +0 -1
- package/ui/dist/assets/classDiagram-v2-Q7XG4LA2-DeRilEwX.js +0 -1
- package/ui/dist/assets/main-BuVawP8Y.js +0 -269
- package/ui/dist/assets/stateDiagram-v2-BHNVJYJU-CnDT2Btz.js +0 -1
- package/ui/dist/assets/theme-BoL8lq0C.css +0 -1
- package/ui/dist/assets/theme-PLJ1jPdr.js +0 -21
- package/ui/dist/assets/visual-editor-DeCtedTT.js +0 -220
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
const SMALL_TALK_TERMS = new Set([
|
|
2
|
+
'привет',
|
|
3
|
+
'здравствуй',
|
|
4
|
+
'здравствуйте',
|
|
5
|
+
'приветствую',
|
|
6
|
+
'hello',
|
|
7
|
+
'hi',
|
|
8
|
+
'hey',
|
|
9
|
+
'yo',
|
|
10
|
+
'thanks',
|
|
11
|
+
'thank',
|
|
12
|
+
'thx',
|
|
13
|
+
'спасибо',
|
|
14
|
+
'благодарю',
|
|
15
|
+
'пока',
|
|
16
|
+
'bye',
|
|
17
|
+
'goodbye',
|
|
18
|
+
]);
|
|
19
|
+
/** Detects short greetings or small talk that should not trigger doc-not-found. */
|
|
20
|
+
export function isAskSmallTalk(question) {
|
|
21
|
+
const normalized = question
|
|
22
|
+
.trim()
|
|
23
|
+
.toLowerCase()
|
|
24
|
+
.replace(/[!?.…,]+/g, ' ')
|
|
25
|
+
.replace(/\s+/g, ' ')
|
|
26
|
+
.trim();
|
|
27
|
+
if (!normalized) {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
const words = normalized.split(' ').filter(Boolean);
|
|
31
|
+
if (words.length === 0 || words.length > 4) {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
return words.every((word) => SMALL_TALK_TERMS.has(word));
|
|
35
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { DocIndex } from '../index/doc-index.js';
|
|
2
|
+
export interface AskSuggestion {
|
|
3
|
+
slug: string;
|
|
4
|
+
title: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function formatSuggestionLink(suggestion: AskSuggestion): string;
|
|
7
|
+
/** Picks onboarding pages (readme, roadmap, wiki) from the live index. */
|
|
8
|
+
export declare function pickStarterPages(index: DocIndex): AskSuggestion[];
|
|
9
|
+
/** Suggests pages when retrieval found nothing — looser than ask search. */
|
|
10
|
+
export declare function suggestAlternativePages(index: DocIndex, question: string): AskSuggestion[];
|
|
11
|
+
export declare function buildAskGreetingReply(index: DocIndex, question: string): string;
|
|
12
|
+
export declare function buildAskNotFoundReply(index: DocIndex, question: string): string;
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { listDocs } from '../tools/list-docs.js';
|
|
2
|
+
import { extractAskSearchTerms, searchDocsWithOrTerms } from './search.js';
|
|
3
|
+
const MAX_STARTERS = 5;
|
|
4
|
+
const MAX_ALTERNATIVES = 4;
|
|
5
|
+
function isCyrillic(text) {
|
|
6
|
+
return /[\u0400-\u04ff]/.test(text);
|
|
7
|
+
}
|
|
8
|
+
export function formatSuggestionLink(suggestion) {
|
|
9
|
+
return `[${suggestion.title}](?slug=${encodeURIComponent(suggestion.slug)})`;
|
|
10
|
+
}
|
|
11
|
+
/** Picks onboarding pages (readme, roadmap, wiki) from the live index. */
|
|
12
|
+
export function pickStarterPages(index) {
|
|
13
|
+
const docs = listDocs(index);
|
|
14
|
+
const scored = [];
|
|
15
|
+
for (const doc of docs) {
|
|
16
|
+
let score = 0;
|
|
17
|
+
const slugLower = doc.slug.toLowerCase();
|
|
18
|
+
const titleLower = doc.title.toLowerCase();
|
|
19
|
+
const pathLower = doc.relativePath.toLowerCase();
|
|
20
|
+
if (slugLower === 'readme' || pathLower === 'readme.md') {
|
|
21
|
+
score += 100;
|
|
22
|
+
}
|
|
23
|
+
if (pathLower.endsWith('/readme.md') || slugLower.endsWith('-readme')) {
|
|
24
|
+
score += 80;
|
|
25
|
+
}
|
|
26
|
+
if (slugLower.includes('roadmap') || titleLower.includes('roadmap')) {
|
|
27
|
+
score += 70;
|
|
28
|
+
}
|
|
29
|
+
if (doc.type === 'wiki-page') {
|
|
30
|
+
score += 20;
|
|
31
|
+
}
|
|
32
|
+
if (slugLower.includes('wiki') || titleLower.includes('wiki')) {
|
|
33
|
+
score += 15;
|
|
34
|
+
}
|
|
35
|
+
if (doc.domain === 'product') {
|
|
36
|
+
score += 10;
|
|
37
|
+
}
|
|
38
|
+
if (score > 0) {
|
|
39
|
+
scored.push({ slug: doc.slug, title: doc.title, score });
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return scored
|
|
43
|
+
.sort((a, b) => b.score - a.score || a.title.localeCompare(b.title))
|
|
44
|
+
.slice(0, MAX_STARTERS)
|
|
45
|
+
.map(({ slug, title }) => ({ slug, title }));
|
|
46
|
+
}
|
|
47
|
+
function scorePartialTermMatch(slugLower, titleLower, term) {
|
|
48
|
+
if (term.length < 3) {
|
|
49
|
+
return 0;
|
|
50
|
+
}
|
|
51
|
+
let score = 0;
|
|
52
|
+
if (slugLower.includes(term)) {
|
|
53
|
+
score += 3;
|
|
54
|
+
}
|
|
55
|
+
if (titleLower.includes(term)) {
|
|
56
|
+
score += 2;
|
|
57
|
+
}
|
|
58
|
+
const words = [...slugLower.split(/[-_/]/), ...titleLower.split(/\s+/)].filter(Boolean);
|
|
59
|
+
if (words.some((word) => word.startsWith(term) || (word.length >= 3 && term.startsWith(word)))) {
|
|
60
|
+
score += 1;
|
|
61
|
+
}
|
|
62
|
+
return score;
|
|
63
|
+
}
|
|
64
|
+
/** Suggests pages when retrieval found nothing — looser than ask search. */
|
|
65
|
+
export function suggestAlternativePages(index, question) {
|
|
66
|
+
const terms = extractAskSearchTerms(question);
|
|
67
|
+
const bySlug = new Map();
|
|
68
|
+
function add(slug, title, score) {
|
|
69
|
+
const existing = bySlug.get(slug);
|
|
70
|
+
if (!existing || score > existing.score) {
|
|
71
|
+
bySlug.set(slug, { slug, title, score });
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
for (const term of terms) {
|
|
75
|
+
for (const hit of searchDocsWithOrTerms(index, [term])) {
|
|
76
|
+
add(hit.slug, hit.title, hit.score);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
for (const doc of listDocs(index)) {
|
|
80
|
+
const slugLower = doc.slug.toLowerCase();
|
|
81
|
+
const titleLower = doc.title.toLowerCase();
|
|
82
|
+
for (const term of terms) {
|
|
83
|
+
const score = scorePartialTermMatch(slugLower, titleLower, term);
|
|
84
|
+
if (score > 0) {
|
|
85
|
+
add(doc.slug, doc.title, score);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return [...bySlug.values()]
|
|
90
|
+
.sort((a, b) => b.score - a.score || a.title.localeCompare(b.title))
|
|
91
|
+
.slice(0, MAX_ALTERNATIVES)
|
|
92
|
+
.map(({ slug, title }) => ({ slug, title }));
|
|
93
|
+
}
|
|
94
|
+
function formatStarterSection(starters, ru) {
|
|
95
|
+
if (starters.length === 0) {
|
|
96
|
+
return [];
|
|
97
|
+
}
|
|
98
|
+
return [
|
|
99
|
+
ru ? 'С чего начать:' : 'Where to start:',
|
|
100
|
+
...starters.map((page) => `- ${formatSuggestionLink(page)}`),
|
|
101
|
+
'',
|
|
102
|
+
];
|
|
103
|
+
}
|
|
104
|
+
function formatExamplePrompt(exampleTitle, ru) {
|
|
105
|
+
if (exampleTitle) {
|
|
106
|
+
return ru
|
|
107
|
+
? `Спросите что-то конкретное — например: «что в ${exampleTitle.toLowerCase()}?»`
|
|
108
|
+
: `Ask something specific — for example: "what is in ${exampleTitle.toLowerCase()}?"`;
|
|
109
|
+
}
|
|
110
|
+
return ru
|
|
111
|
+
? 'Спросите что-то конкретное — укажите ключевое слово из названия страницы или тему.'
|
|
112
|
+
: 'Ask something specific — use a keyword from a page title or a topic you care about.';
|
|
113
|
+
}
|
|
114
|
+
export function buildAskGreetingReply(index, question) {
|
|
115
|
+
const ru = isCyrillic(question);
|
|
116
|
+
const starters = pickStarterPages(index);
|
|
117
|
+
const exampleTitle = starters[0]?.title;
|
|
118
|
+
const lines = ru
|
|
119
|
+
? ['Привет! Я помощник по документации RepoMind.', '']
|
|
120
|
+
: ['Hello! I am the RepoMind documentation assistant.', ''];
|
|
121
|
+
lines.push(...formatStarterSection(starters, ru));
|
|
122
|
+
lines.push(formatExamplePrompt(exampleTitle, ru));
|
|
123
|
+
return lines.join('\n');
|
|
124
|
+
}
|
|
125
|
+
export function buildAskNotFoundReply(index, question) {
|
|
126
|
+
const ru = isCyrillic(question);
|
|
127
|
+
const alternatives = suggestAlternativePages(index, question);
|
|
128
|
+
const starters = pickStarterPages(index);
|
|
129
|
+
const trimmed = question.trim();
|
|
130
|
+
const lines = ru
|
|
131
|
+
? [`Не нашёл страниц по запросу «${trimmed}».`, '']
|
|
132
|
+
: [`No pages matched your question "${trimmed}".`, ''];
|
|
133
|
+
if (alternatives.length > 0) {
|
|
134
|
+
lines.push(ru ? 'Возможно, вы искали:' : 'Perhaps you were looking for:');
|
|
135
|
+
for (const page of alternatives) {
|
|
136
|
+
lines.push(`- ${formatSuggestionLink(page)}`);
|
|
137
|
+
}
|
|
138
|
+
lines.push('');
|
|
139
|
+
}
|
|
140
|
+
else if (starters.length > 0) {
|
|
141
|
+
lines.push(...formatStarterSection(starters, ru));
|
|
142
|
+
}
|
|
143
|
+
lines.push(formatExamplePrompt(alternatives[0]?.title ?? starters[0]?.title, ru));
|
|
144
|
+
return lines.join('\n');
|
|
145
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export type LlmProvider = 'openai' | 'anthropic';
|
|
2
|
+
export interface AskSource {
|
|
3
|
+
slug: string;
|
|
4
|
+
title: string;
|
|
5
|
+
excerpt: string;
|
|
6
|
+
}
|
|
7
|
+
export interface AskRequest {
|
|
8
|
+
question: string;
|
|
9
|
+
provider?: LlmProvider;
|
|
10
|
+
model?: string;
|
|
11
|
+
apiKey?: string;
|
|
12
|
+
history?: AskChatTurn[];
|
|
13
|
+
}
|
|
14
|
+
export interface AskChatTurn {
|
|
15
|
+
role: 'user' | 'assistant';
|
|
16
|
+
content: string;
|
|
17
|
+
}
|
|
18
|
+
export interface AskResponse {
|
|
19
|
+
answer: string;
|
|
20
|
+
sources: AskSource[];
|
|
21
|
+
notFound: boolean;
|
|
22
|
+
}
|
|
23
|
+
export interface LlmMessage {
|
|
24
|
+
role: 'system' | 'user' | 'assistant';
|
|
25
|
+
content: string;
|
|
26
|
+
}
|
|
27
|
+
export type CompleteAskFn = (provider: LlmProvider, apiKey: string, model: string, messages: LlmMessage[]) => Promise<string>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/cli.js
CHANGED
|
@@ -28,13 +28,18 @@ Usage:
|
|
|
28
28
|
|
|
29
29
|
Environment:
|
|
30
30
|
REPOMIND_UI_PASSWORD When set, protects the UI and API with password login
|
|
31
|
+
REPOMIND_ASK_API_KEY Optional default API key for the Ask assistant (BYOK)
|
|
32
|
+
REPOMIND_ASK_PROVIDER Ask LLM provider: openai (default) or anthropic
|
|
33
|
+
REPOMIND_ASK_MODEL Ask LLM model override
|
|
34
|
+
|
|
35
|
+
repo-mind ui loads REPOMIND_* variables from <cwd>/.env (does not override shell env).
|
|
31
36
|
|
|
32
37
|
Commands:
|
|
33
38
|
init Scaffold docs/ with example structured pages
|
|
34
39
|
setup Configure Cursor/Claude MCP and CLAUDE.md snippet
|
|
35
40
|
install-skill Copy repomind-docs Cursor skill into .cursor/skills/
|
|
36
41
|
check Validate frontmatter schema and related links
|
|
37
|
-
export Write agents.md
|
|
42
|
+
export Write agents-export.md flat dump to repo root
|
|
38
43
|
prepare Add RepoMind frontmatter to markdown files (--all for batch)
|
|
39
44
|
sync-links Convert markdown links to wikilinks and sync related frontmatter
|
|
40
45
|
publish Publish active drafts to docs/; --pr opens a GitHub pull request
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { type DocType } from '../index/types.js';
|
|
2
|
+
/** Generated flat export — distinct from Cursor's `AGENTS.md` (case-insensitive FS clash). */
|
|
3
|
+
export declare const EXPORT_FILENAME = "agents-export.md";
|
|
2
4
|
export interface ExportOptions {
|
|
3
5
|
cwd?: string;
|
|
4
6
|
force?: boolean;
|
package/dist/commands/export.js
CHANGED
|
@@ -2,6 +2,8 @@ import fs from 'node:fs';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { DocIndex } from '../index/doc-index.js';
|
|
4
4
|
import { DOC_TYPES, TYPE_TO_DIR } from '../index/types.js';
|
|
5
|
+
/** Generated flat export — distinct from Cursor's `AGENTS.md` (case-insensitive FS clash). */
|
|
6
|
+
export const EXPORT_FILENAME = 'agents-export.md';
|
|
5
7
|
const TYPE_SECTIONS = {
|
|
6
8
|
adr: 'ADRs',
|
|
7
9
|
'feature-spec': 'Feature Specs',
|
|
@@ -19,9 +21,9 @@ export function runExport(options = {}) {
|
|
|
19
21
|
return 1;
|
|
20
22
|
}
|
|
21
23
|
const repoRoot = path.dirname(knowledgeRoot);
|
|
22
|
-
const outputPath = path.join(repoRoot,
|
|
24
|
+
const outputPath = path.join(repoRoot, EXPORT_FILENAME);
|
|
23
25
|
if (fs.existsSync(outputPath) && !options.force) {
|
|
24
|
-
console.error(
|
|
26
|
+
console.error(`${EXPORT_FILENAME} already exists — pass --force to overwrite`);
|
|
25
27
|
return 1;
|
|
26
28
|
}
|
|
27
29
|
const docs = index.refresh();
|
|
@@ -71,9 +73,9 @@ export function resolveExportPath(cwd) {
|
|
|
71
73
|
const index = new DocIndex(cwd);
|
|
72
74
|
const knowledgeRoot = index.getKnowledgeRoot();
|
|
73
75
|
if (!knowledgeRoot) {
|
|
74
|
-
return path.join(cwd,
|
|
76
|
+
return path.join(cwd, EXPORT_FILENAME);
|
|
75
77
|
}
|
|
76
|
-
return path.join(path.dirname(knowledgeRoot),
|
|
78
|
+
return path.join(path.dirname(knowledgeRoot), EXPORT_FILENAME);
|
|
77
79
|
}
|
|
78
80
|
export function typeDirFor(type) {
|
|
79
81
|
return TYPE_TO_DIR[type];
|
package/dist/commands/ui.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { DocIndex } from '../index/doc-index.js';
|
|
4
|
+
import { loadRepomindEnv } from '../env/load-repomind-env.js';
|
|
4
5
|
import { closeAllDocsEventStreams } from '../ui/api-handlers.js';
|
|
5
6
|
import { DocsWatcher } from '../ui/docs-watcher.js';
|
|
6
7
|
import { openDraftsDb } from '../ui/db/drafts-db.js';
|
|
@@ -14,6 +15,11 @@ export async function runUi(options = {}) {
|
|
|
14
15
|
console.error('no docs/ found — run `repo-mind init` or create a docs/ directory');
|
|
15
16
|
return 1;
|
|
16
17
|
}
|
|
18
|
+
const projectRoot = path.dirname(index.getKnowledgeRoot());
|
|
19
|
+
const envLoad = loadRepomindEnv(cwd, projectRoot);
|
|
20
|
+
if (envLoad.loaded.length > 0 && envLoad.envPath) {
|
|
21
|
+
console.log(`loaded ${envLoad.loaded.length} REPOMIND_* variable(s) from ${envLoad.envPath}`);
|
|
22
|
+
}
|
|
17
23
|
const staticDir = resolveUiStaticDir();
|
|
18
24
|
const indexHtml = path.join(staticDir, 'index.html');
|
|
19
25
|
if (!fs.existsSync(indexHtml)) {
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface LoadRepomindEnvResult {
|
|
2
|
+
loaded: string[];
|
|
3
|
+
envPath: string | null;
|
|
4
|
+
}
|
|
5
|
+
/** Finds the nearest `.env` walking up from `startDir`. */
|
|
6
|
+
export declare function findRepomindEnvFile(startDir: string): string | null;
|
|
7
|
+
/** Loads `REPOMIND_*` variables from the nearest `.env` without overriding shell env. */
|
|
8
|
+
export declare function loadRepomindEnvFromFile(cwd: string): string[];
|
|
9
|
+
/** Loads `REPOMIND_*` from nearest `.env` near cwd and optional project root. */
|
|
10
|
+
export declare function loadRepomindEnv(cwd: string, projectRoot?: string): LoadRepomindEnvResult;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
function unquoteEnvValue(raw) {
|
|
4
|
+
const value = raw.trim();
|
|
5
|
+
if ((value.startsWith('"') && value.endsWith('"')) ||
|
|
6
|
+
(value.startsWith("'") && value.endsWith("'"))) {
|
|
7
|
+
return value.slice(1, -1);
|
|
8
|
+
}
|
|
9
|
+
return value;
|
|
10
|
+
}
|
|
11
|
+
function parseEnvLine(line) {
|
|
12
|
+
const trimmed = line.trim();
|
|
13
|
+
if (!trimmed || trimmed.startsWith('#')) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
if (trimmed.startsWith('export ')) {
|
|
17
|
+
return parseEnvLine(trimmed.slice('export '.length));
|
|
18
|
+
}
|
|
19
|
+
const separator = trimmed.indexOf('=');
|
|
20
|
+
if (separator <= 0) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
const key = trimmed.slice(0, separator).trim();
|
|
24
|
+
const rawValue = trimmed.slice(separator + 1).trim();
|
|
25
|
+
if (!key) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
return { key, value: unquoteEnvValue(rawValue) };
|
|
29
|
+
}
|
|
30
|
+
function envIsUnset(key) {
|
|
31
|
+
const value = process.env[key];
|
|
32
|
+
return value === undefined || value.trim() === '';
|
|
33
|
+
}
|
|
34
|
+
/** Finds the nearest `.env` walking up from `startDir`. */
|
|
35
|
+
export function findRepomindEnvFile(startDir) {
|
|
36
|
+
let current = path.resolve(startDir);
|
|
37
|
+
while (true) {
|
|
38
|
+
const envPath = path.join(current, '.env');
|
|
39
|
+
if (fs.existsSync(envPath) && fs.statSync(envPath).isFile()) {
|
|
40
|
+
return envPath;
|
|
41
|
+
}
|
|
42
|
+
const parent = path.dirname(current);
|
|
43
|
+
if (parent === current) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
current = parent;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function loadRepomindEnvAtPath(envPath) {
|
|
50
|
+
const loaded = [];
|
|
51
|
+
const content = fs.readFileSync(envPath, 'utf8');
|
|
52
|
+
for (const line of content.split(/\r?\n/)) {
|
|
53
|
+
const parsed = parseEnvLine(line);
|
|
54
|
+
if (!parsed || !parsed.key.startsWith('REPOMIND_')) {
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (!envIsUnset(parsed.key)) {
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
process.env[parsed.key] = parsed.value;
|
|
61
|
+
loaded.push(parsed.key);
|
|
62
|
+
}
|
|
63
|
+
return loaded;
|
|
64
|
+
}
|
|
65
|
+
/** Loads `REPOMIND_*` variables from the nearest `.env` without overriding shell env. */
|
|
66
|
+
export function loadRepomindEnvFromFile(cwd) {
|
|
67
|
+
return loadRepomindEnv(cwd).loaded;
|
|
68
|
+
}
|
|
69
|
+
/** Loads `REPOMIND_*` from nearest `.env` near cwd and optional project root. */
|
|
70
|
+
export function loadRepomindEnv(cwd, projectRoot) {
|
|
71
|
+
const candidates = new Set([path.resolve(cwd)]);
|
|
72
|
+
if (projectRoot) {
|
|
73
|
+
candidates.add(path.resolve(projectRoot));
|
|
74
|
+
}
|
|
75
|
+
const envPaths = [];
|
|
76
|
+
for (const candidate of candidates) {
|
|
77
|
+
const envPath = findRepomindEnvFile(candidate);
|
|
78
|
+
if (envPath && !envPaths.includes(envPath)) {
|
|
79
|
+
envPaths.push(envPath);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
const loaded = [];
|
|
83
|
+
let envPath = null;
|
|
84
|
+
for (const candidatePath of envPaths) {
|
|
85
|
+
const keys = loadRepomindEnvAtPath(candidatePath);
|
|
86
|
+
if (keys.length > 0) {
|
|
87
|
+
envPath = candidatePath;
|
|
88
|
+
for (const key of keys) {
|
|
89
|
+
if (!loaded.includes(key)) {
|
|
90
|
+
loaded.push(key);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return { loaded, envPath };
|
|
96
|
+
}
|
|
@@ -5,6 +5,7 @@ export interface ApiResponse {
|
|
|
5
5
|
status: number;
|
|
6
6
|
body: unknown;
|
|
7
7
|
}
|
|
8
|
+
export declare function handleAskApi(index: DocIndex, method: string, pathname: string, bodyRaw: string): Promise<ApiResponse | null>;
|
|
8
9
|
export declare function handleApiRequest(index: DocIndex, req: IncomingMessage, pathname: string, searchParams: URLSearchParams): ApiResponse;
|
|
9
10
|
export declare function routeApi(index: DocIndex, req: IncomingMessage): ApiResponse | null;
|
|
10
11
|
/** Closes live SSE streams so `server.close()` can finish during shutdown. */
|
package/dist/ui/api-handlers.js
CHANGED
|
@@ -15,14 +15,98 @@ import { buildDocsTree } from './fs-tree.js';
|
|
|
15
15
|
import { readCatalogMeta } from './catalog-meta.js';
|
|
16
16
|
import { computeKnowledgeStats } from './stats.js';
|
|
17
17
|
import { listPageTemplates } from './templates.js';
|
|
18
|
+
import { askDocs } from '../ask/ask.js';
|
|
19
|
+
import { getAskServerConfig } from '../ask/config.js';
|
|
18
20
|
function jsonError(status, message) {
|
|
19
21
|
return { status, body: { error: message } };
|
|
20
22
|
}
|
|
23
|
+
function parseJsonBody(raw) {
|
|
24
|
+
if (!raw.trim()) {
|
|
25
|
+
return {};
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
const parsed = JSON.parse(raw);
|
|
29
|
+
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
return parsed;
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function isLlmProvider(value) {
|
|
39
|
+
return value === 'openai' || value === 'anthropic';
|
|
40
|
+
}
|
|
41
|
+
export async function handleAskApi(index, method, pathname, bodyRaw) {
|
|
42
|
+
if (pathname !== '/api/ask' || method !== 'POST') {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
const body = parseJsonBody(bodyRaw);
|
|
46
|
+
if (!body) {
|
|
47
|
+
return jsonError(400, 'invalid JSON body');
|
|
48
|
+
}
|
|
49
|
+
const question = typeof body.question === 'string' ? body.question : '';
|
|
50
|
+
const providerRaw = typeof body.provider === 'string' ? body.provider : undefined;
|
|
51
|
+
if (providerRaw && !isLlmProvider(providerRaw)) {
|
|
52
|
+
return jsonError(400, 'invalid provider — use openai or anthropic');
|
|
53
|
+
}
|
|
54
|
+
const provider = providerRaw && isLlmProvider(providerRaw) ? providerRaw : undefined;
|
|
55
|
+
const model = typeof body.model === 'string' ? body.model : undefined;
|
|
56
|
+
const apiKey = typeof body.apiKey === 'string' ? body.apiKey : undefined;
|
|
57
|
+
const history = parseAskHistory(body.history);
|
|
58
|
+
try {
|
|
59
|
+
const result = await askDocs(index, {
|
|
60
|
+
question,
|
|
61
|
+
provider,
|
|
62
|
+
model,
|
|
63
|
+
apiKey,
|
|
64
|
+
history,
|
|
65
|
+
});
|
|
66
|
+
return { status: 200, body: result };
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
const message = error instanceof Error ? error.message : 'ask failed';
|
|
70
|
+
if (message.includes('API key is required')) {
|
|
71
|
+
return jsonError(400, message);
|
|
72
|
+
}
|
|
73
|
+
if (message.includes('question is required')) {
|
|
74
|
+
return jsonError(400, message);
|
|
75
|
+
}
|
|
76
|
+
return jsonError(502, message);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function parseAskHistory(value) {
|
|
80
|
+
if (!Array.isArray(value)) {
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
83
|
+
const turns = value
|
|
84
|
+
.map((item) => {
|
|
85
|
+
if (typeof item !== 'object' || item === null) {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
const role = item.role;
|
|
89
|
+
const content = item.content;
|
|
90
|
+
if ((role !== 'user' && role !== 'assistant') || typeof content !== 'string') {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
const trimmed = content.trim();
|
|
94
|
+
if (!trimmed) {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
return { role, content: trimmed };
|
|
98
|
+
})
|
|
99
|
+
.filter((item) => item !== null);
|
|
100
|
+
return turns.length > 0 ? turns : undefined;
|
|
101
|
+
}
|
|
21
102
|
function parseRequestUrl(req, baseUrl) {
|
|
22
103
|
const host = req.headers.host ?? '127.0.0.1';
|
|
23
104
|
return new URL(req.url ?? '/', `http://${host}${baseUrl}`);
|
|
24
105
|
}
|
|
25
106
|
export function handleApiRequest(index, req, pathname, searchParams) {
|
|
107
|
+
if (pathname === '/api/ask/config') {
|
|
108
|
+
return { status: 200, body: getAskServerConfig() };
|
|
109
|
+
}
|
|
26
110
|
if (pathname === '/api/health') {
|
|
27
111
|
const docs = index.refresh();
|
|
28
112
|
return {
|
package/dist/ui/draft-api.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { runExport } from '../commands/export.js';
|
|
1
|
+
import { EXPORT_FILENAME, runExport } from '../commands/export.js';
|
|
2
2
|
import { isValidSlug } from '../index/slug.js';
|
|
3
3
|
import { DOC_TYPES, isDocStatus, isDocType } from '../index/types.js';
|
|
4
4
|
import { writeCatalogEmoji } from './catalog-meta.js';
|
|
@@ -38,7 +38,7 @@ export function handleDraftApi(index, db, method, pathname, bodyRaw) {
|
|
|
38
38
|
if (code !== 0) {
|
|
39
39
|
return jsonError(500, 'export failed');
|
|
40
40
|
}
|
|
41
|
-
return { status: 200, body: { ok: true, path:
|
|
41
|
+
return { status: 200, body: { ok: true, path: EXPORT_FILENAME } };
|
|
42
42
|
}
|
|
43
43
|
if (pathname === '/api/catalog-meta' && method === 'PUT') {
|
|
44
44
|
const body = parseJsonBody(bodyRaw);
|
package/dist/ui/server.js
CHANGED
|
@@ -2,7 +2,7 @@ import fs from 'node:fs';
|
|
|
2
2
|
import http from 'node:http';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
|
-
import { routeApi, handleDocsEvents } from './api-handlers.js';
|
|
5
|
+
import { routeApi, handleDocsEvents, handleAskApi } from './api-handlers.js';
|
|
6
6
|
import { handleAuthApi } from './auth.js';
|
|
7
7
|
import { handleDraftApi } from './draft-api.js';
|
|
8
8
|
import { serveKnowledgeAsset } from './serve-asset.js';
|
|
@@ -214,6 +214,11 @@ export function createUiServer(options) {
|
|
|
214
214
|
sendJson(res, draftResponse.status, draftResponse.body);
|
|
215
215
|
return;
|
|
216
216
|
}
|
|
217
|
+
const askResponse = await handleAskApi(index, method, urlPath, bodyRaw);
|
|
218
|
+
if (askResponse) {
|
|
219
|
+
sendJson(res, askResponse.status, askResponse.body);
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
217
222
|
if (urlPath.startsWith('/api/drafts')) {
|
|
218
223
|
sendJson(res, 503, { error: 'drafts database unavailable' });
|
|
219
224
|
return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@justyork/repo-mind",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Unified docs/ workspace for humans (UI) and AI agents (MCP) — Confluence in your repo",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
"build:ui": "vite build --config ui/vite.config.ts",
|
|
18
18
|
"test": "vitest run",
|
|
19
19
|
"test:watch": "vitest",
|
|
20
|
+
"ui": "node dist/cli.js ui",
|
|
20
21
|
"ab-demo": "npm run build --silent && node dist/ab-demo/run-ab.js --dry-run",
|
|
21
22
|
"ab-demo:run": "npm run build --silent && node dist/ab-demo/run-ab.js",
|
|
22
23
|
"ab-eval:live": "npm run build --silent && node dist/cli.js ab-eval",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{$ as ln,a0 as an,a1 as G,a2 as q,a3 as z,a4 as un,a5 as y,a6 as tn,a7 as K,a8 as _,a9 as rn,aa as o,ab as on,ac as sn,ad as fn}from"./mermaid.core-
|
|
1
|
+
import{$ as ln,a0 as an,a1 as G,a2 as q,a3 as z,a4 as un,a5 as y,a6 as tn,a7 as K,a8 as _,a9 as rn,aa as o,ab as on,ac as sn,ad as fn}from"./mermaid.core-CkbXj8L9.js";function cn(l){return l.innerRadius}function yn(l){return l.outerRadius}function gn(l){return l.startAngle}function dn(l){return l.endAngle}function mn(l){return l&&l.padAngle}function pn(l,h,I,D,v,A,B,a){var O=I-l,i=D-h,n=B-v,d=a-A,u=d*O-n*i;if(!(u*u<y))return u=(n*(h-A)-d*(l-v))/u,[l+u*O,h+u*i]}function V(l,h,I,D,v,A,B){var a=l-I,O=h-D,i=(B?A:-A)/K(a*a+O*O),n=i*O,d=-i*a,u=l+n,s=h+d,f=I+n,c=D+d,C=(u+f)/2,t=(s+c)/2,m=f-u,g=c-s,R=m*m+g*g,T=v-A,P=u*c-f*s,S=(g<0?-1:1)*K(on(0,T*T*R-P*P)),$=(P*g-m*S)/R,j=(-P*m-g*S)/R,w=(P*g+m*S)/R,p=(-P*m+g*S)/R,x=$-C,e=j-t,r=w-C,F=p-t;return x*x+e*e>r*r+F*F&&($=w,j=p),{cx:$,cy:j,x01:-n,y01:-d,x11:$*(v/T-1),y11:j*(v/T-1)}}function hn(){var l=cn,h=yn,I=z(0),D=null,v=gn,A=dn,B=mn,a=null,O=ln(i);function i(){var n,d,u=+l.apply(this,arguments),s=+h.apply(this,arguments),f=v.apply(this,arguments)-un,c=A.apply(this,arguments)-un,C=rn(c-f),t=c>f;if(a||(a=n=O()),s<u&&(d=s,s=u,u=d),!(s>y))a.moveTo(0,0);else if(C>tn-y)a.moveTo(s*G(f),s*q(f)),a.arc(0,0,s,f,c,!t),u>y&&(a.moveTo(u*G(c),u*q(c)),a.arc(0,0,u,c,f,t));else{var m=f,g=c,R=f,T=c,P=C,S=C,$=B.apply(this,arguments)/2,j=$>y&&(D?+D.apply(this,arguments):K(u*u+s*s)),w=_(rn(s-u)/2,+I.apply(this,arguments)),p=w,x=w,e,r;if(j>y){var F=sn(j/u*q($)),L=sn(j/s*q($));(P-=F*2)>y?(F*=t?1:-1,R+=F,T-=F):(P=0,R=T=(f+c)/2),(S-=L*2)>y?(L*=t?1:-1,m+=L,g-=L):(S=0,m=g=(f+c)/2)}var H=s*G(m),J=s*q(m),M=u*G(T),N=u*q(T);if(w>y){var Q=s*G(g),U=s*q(g),W=u*G(R),X=u*q(R),E;if(C<an)if(E=pn(H,J,W,X,Q,U,M,N)){var Y=H-E[0],Z=J-E[1],b=Q-E[0],k=U-E[1],nn=1/q(fn((Y*b+Z*k)/(K(Y*Y+Z*Z)*K(b*b+k*k)))/2),en=K(E[0]*E[0]+E[1]*E[1]);p=_(w,(u-en)/(nn-1)),x=_(w,(s-en)/(nn+1))}else p=x=0}S>y?x>y?(e=V(W,X,H,J,s,x,t),r=V(Q,U,M,N,s,x,t),a.moveTo(e.cx+e.x01,e.cy+e.y01),x<w?a.arc(e.cx,e.cy,x,o(e.y01,e.x01),o(r.y01,r.x01),!t):(a.arc(e.cx,e.cy,x,o(e.y01,e.x01),o(e.y11,e.x11),!t),a.arc(0,0,s,o(e.cy+e.y11,e.cx+e.x11),o(r.cy+r.y11,r.cx+r.x11),!t),a.arc(r.cx,r.cy,x,o(r.y11,r.x11),o(r.y01,r.x01),!t))):(a.moveTo(H,J),a.arc(0,0,s,m,g,!t)):a.moveTo(H,J),!(u>y)||!(P>y)?a.lineTo(M,N):p>y?(e=V(M,N,Q,U,u,-p,t),r=V(H,J,W,X,u,-p,t),a.lineTo(e.cx+e.x01,e.cy+e.y01),p<w?a.arc(e.cx,e.cy,p,o(e.y01,e.x01),o(r.y01,r.x01),!t):(a.arc(e.cx,e.cy,p,o(e.y01,e.x01),o(e.y11,e.x11),!t),a.arc(0,0,u,o(e.cy+e.y11,e.cx+e.x11),o(r.cy+r.y11,r.cx+r.x11),t),a.arc(r.cx,r.cy,p,o(r.y11,r.x11),o(r.y01,r.x01),!t))):a.arc(0,0,u,T,R,t)}if(a.closePath(),n)return a=null,n+""||null}return i.centroid=function(){var n=(+l.apply(this,arguments)+ +h.apply(this,arguments))/2,d=(+v.apply(this,arguments)+ +A.apply(this,arguments))/2-an/2;return[G(d)*n,q(d)*n]},i.innerRadius=function(n){return arguments.length?(l=typeof n=="function"?n:z(+n),i):l},i.outerRadius=function(n){return arguments.length?(h=typeof n=="function"?n:z(+n),i):h},i.cornerRadius=function(n){return arguments.length?(I=typeof n=="function"?n:z(+n),i):I},i.padRadius=function(n){return arguments.length?(D=n==null?null:typeof n=="function"?n:z(+n),i):D},i.startAngle=function(n){return arguments.length?(v=typeof n=="function"?n:z(+n),i):v},i.endAngle=function(n){return arguments.length?(A=typeof n=="function"?n:z(+n),i):A},i.padAngle=function(n){return arguments.length?(B=typeof n=="function"?n:z(+n),i):B},i.context=function(n){return arguments.length?(a=n??null,i):a},i}export{hn as d};
|