@nzpr/kb 0.1.1 → 0.1.3

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 CHANGED
@@ -23,6 +23,8 @@ The public surface of `@nzpr/kb` is the `kb` CLI.
23
23
 
24
24
  - `kb init-repo`
25
25
  Initialize a knowledge base workspace.
26
+ Use `kb init-repo --interactive` for a guided setup flow.
27
+ Use `--layout repo-root` when this repository is itself the KB.
26
28
 
27
29
  - `kb create --title TEXT --text TEXT`
28
30
  Propose a new knowledge document or a substantial update.
@@ -95,6 +97,19 @@ Bootstrap that repo with:
95
97
  kb init-repo --dir /path/to/knowledge-repo
96
98
  ```
97
99
 
100
+ If you want the CLI to walk you through setup and tell you what to do next:
101
+
102
+ ```bash
103
+ kb init-repo --interactive
104
+ ```
105
+
106
+ Layout options:
107
+
108
+ - `--layout repo-root` puts documents in `docs/`
109
+ - `--layout nested-kb` puts documents in `kb/docs/`
110
+
111
+ For a dedicated knowledge repository, prefer `--layout repo-root`.
112
+
98
113
  Or bootstrap and configure it in one step:
99
114
 
100
115
  ```bash
@@ -113,15 +128,24 @@ Inside that knowledge repo, the normal flow is:
113
128
  1. Run `kb init-repo` once to scaffold the repo. If you provide `--repo`, `--database-url`, and `GITHUB_TOKEN`, it also configures the target repo and preflights the database.
114
129
  2. Open or update a knowledge proposal issue.
115
130
  3. Review and approve it there.
116
- 4. Materialize it into `kb/docs/`.
117
- 5. After merge, that repo's CI runs `kb publish --docs-root ./kb/docs`.
131
+ 4. Materialize it into the configured docs directory.
132
+ 5. After merge, that repo's CI runs `kb publish` against that docs directory.
118
133
 
119
134
  `kb init-repo` is safe to rerun. It reports bootstrap status for:
120
135
 
121
136
  - local scaffold creation
137
+ - knowledge document layout selection
122
138
  - database preflight and schema initialization
123
139
  - GitHub repo labels, variables, and secrets
124
140
 
141
+ In interactive mode it also:
142
+
143
+ - asks which repo and directory you want to initialize
144
+ - asks whether to wire GitHub setup now
145
+ - asks whether to verify the database now
146
+ - collects embedding settings if you want remote embeddings
147
+ - prints the next actions after initialization
148
+
125
149
  If one remote step fails, the scaffold still stays in place and the command tells you what to rerun.
126
150
 
127
151
  ## Runtime
@@ -170,7 +194,7 @@ export KB_DATABASE_URL=postgresql://kb:kb@localhost:5432/kb
170
194
  export KB_GITHUB_REPO=owner/repo
171
195
  export GITHUB_TOKEN=...
172
196
  docker compose -f docker-compose.pgvector.yml up -d
173
- kb publish --docs-root ./kb/docs
197
+ kb publish --docs-root ./docs
174
198
  kb catalog --json
175
199
  kb search "deployment rule"
176
200
  ```
@@ -200,13 +224,13 @@ The package is published as `@nzpr/kb`.
200
224
 
201
225
  ## Using From A Knowledge Repo
202
226
 
203
- The knowledge-authority repo should install this package in CI and use it to sync `kb/docs/` into the vector database:
227
+ The knowledge-authority repo should install this package in CI and use it to sync its configured docs directory into the vector database. For a dedicated KB repo with `--layout repo-root`, that directory is `docs/`:
204
228
 
205
229
  ```bash
206
230
  npm install -g @nzpr/kb
207
231
  export KB_GITHUB_REPO=owner/repo
208
232
  export GITHUB_TOKEN=...
209
- kb publish --docs-root ./kb/docs
233
+ kb publish --docs-root ./docs
210
234
  ```
211
235
 
212
236
  Or scaffold that repo first:
package/lib/cli-common.js CHANGED
@@ -8,7 +8,7 @@ export function parseFlags(args) {
8
8
  continue;
9
9
  }
10
10
  const key = arg.slice(2);
11
- if (key === "json" || key === "include-drafts") {
11
+ if (key === "json" || key === "include-drafts" || key === "interactive") {
12
12
  flags[key] = true;
13
13
  continue;
14
14
  }
package/lib/cli.js CHANGED
@@ -9,6 +9,7 @@ import {
9
9
  } from "./config.js";
10
10
  import { connect, initDb } from "./db.js";
11
11
  import { ingestDocuments } from "./index.js";
12
+ import { collectInitRepoInteractiveOptions } from "./init-repo-interactive.js";
12
13
  import { createGitHubIssueFromText } from "./kb-proposals.js";
13
14
  import { bootstrapKnowledgeRepo } from "./repo-init.js";
14
15
  import { askIndex, doctor, knowledgeCatalog, listDocuments, searchIndex, snippet } from "./search.js";
@@ -60,21 +61,37 @@ export async function main(argv) {
60
61
  return 0;
61
62
  }
62
63
  case "init-repo": {
64
+ const initRepoOptions = flags.interactive
65
+ ? await collectInitRepoInteractiveOptions({ flags })
66
+ : {
67
+ targetDir: flags.dir ?? process.cwd(),
68
+ layout: flags.layout ?? "nested-kb",
69
+ repo: flags.repo ?? resolveGitHubRepository(),
70
+ databaseUrl: flags["database-url"] ?? process.env.KB_DATABASE_URL ?? null,
71
+ embeddingMode: flags["embedding-mode"] ?? process.env.KB_EMBEDDING_MODE ?? null,
72
+ embeddingApiUrl: flags["embedding-api-url"] ?? process.env.KB_EMBEDDING_API_URL ?? null,
73
+ embeddingModel: flags["embedding-model"] ?? process.env.KB_EMBEDDING_MODEL ?? null,
74
+ embeddingApiKey: flags["embedding-api-key"] ?? process.env.KB_EMBEDDING_API_KEY ?? null,
75
+ dbConnectTimeoutMs:
76
+ flags["db-connect-timeout-ms"] ?? process.env.KB_DB_CONNECT_TIMEOUT_MS ?? null,
77
+ repoAutomationToken:
78
+ flags["repo-automation-token"] ?? process.env.KB_REPO_AUTOMATION_TOKEN ?? null
79
+ };
63
80
  const result = await bootstrapKnowledgeRepo({
64
- targetDir: flags.dir ?? process.cwd(),
65
- repo: flags.repo ?? resolveGitHubRepository(),
81
+ targetDir: initRepoOptions.targetDir,
82
+ layout: initRepoOptions.layout,
83
+ repo: initRepoOptions.repo,
66
84
  githubToken: process.env.GITHUB_TOKEN ?? null,
67
- databaseUrl: flags["database-url"] ?? process.env.KB_DATABASE_URL ?? null,
68
- embeddingMode: flags["embedding-mode"] ?? process.env.KB_EMBEDDING_MODE ?? null,
69
- embeddingApiUrl: flags["embedding-api-url"] ?? process.env.KB_EMBEDDING_API_URL ?? null,
70
- embeddingModel: flags["embedding-model"] ?? process.env.KB_EMBEDDING_MODEL ?? null,
71
- embeddingApiKey: flags["embedding-api-key"] ?? process.env.KB_EMBEDDING_API_KEY ?? null,
72
- dbConnectTimeoutMs:
73
- flags["db-connect-timeout-ms"] ?? process.env.KB_DB_CONNECT_TIMEOUT_MS ?? null,
74
- repoAutomationToken:
75
- flags["repo-automation-token"] ?? process.env.KB_REPO_AUTOMATION_TOKEN ?? null
85
+ databaseUrl: initRepoOptions.databaseUrl,
86
+ embeddingMode: initRepoOptions.embeddingMode,
87
+ embeddingApiUrl: initRepoOptions.embeddingApiUrl,
88
+ embeddingModel: initRepoOptions.embeddingModel,
89
+ embeddingApiKey: initRepoOptions.embeddingApiKey,
90
+ dbConnectTimeoutMs: initRepoOptions.dbConnectTimeoutMs,
91
+ repoAutomationToken: initRepoOptions.repoAutomationToken
76
92
  });
77
93
  console.log(`initialized knowledge repo scaffold in ${result.root}`);
94
+ console.log(`knowledge documents will live in ${result.docsRootRelative}/`);
78
95
  for (const relativePath of result.created) {
79
96
  console.log(`created ${relativePath}`);
80
97
  }
@@ -87,6 +104,7 @@ export async function main(argv) {
87
104
  printInitRepoStatus(result);
88
105
  printRepoConfiguration(result.configuration);
89
106
  printInitRepoNextStep(result);
107
+ printInitRepoChecklist(result);
90
108
  return result.ok ? 0 : 1;
91
109
  }
92
110
  case "search": {
@@ -231,7 +249,7 @@ function printHelp() {
231
249
  console.log(`usage: kb <command> [options]
232
250
 
233
251
  commands:
234
- init-repo [--dir PATH] [--repo OWNER/REPO]
252
+ init-repo [--interactive] [--layout repo-root|nested-kb] [--dir PATH] [--repo OWNER/REPO]
235
253
  create --title TEXT --text TEXT [--path RELATIVE_PATH]
236
254
  search <query>
237
255
  ask <question>
@@ -256,7 +274,7 @@ search options:
256
274
  function printCommandHelp(command) {
257
275
  const commandHelp = {
258
276
  "init-repo":
259
- "usage: kb init-repo [--dir PATH] [--repo OWNER/REPO] [--database-url URL] [--embedding-mode MODE] [--embedding-api-url URL] [--embedding-model NAME] [--embedding-api-key KEY] [--db-connect-timeout-ms N] [--repo-automation-token TOKEN]\n\nScaffold a knowledge-authority repository, optionally configure its GitHub settings, and preflight the target database.",
277
+ "usage: kb init-repo [--interactive] [--layout repo-root|nested-kb] [--dir PATH] [--repo OWNER/REPO] [--database-url URL] [--embedding-mode MODE] [--embedding-api-url URL] [--embedding-model NAME] [--embedding-api-key KEY] [--db-connect-timeout-ms N] [--repo-automation-token TOKEN]\n\nScaffold a knowledge-authority repository, optionally configure its GitHub settings, and preflight the target database. Use --interactive for a guided terminal walkthrough.",
260
278
  create: `usage: kb create --title TEXT --text TEXT [--path RELATIVE_PATH] [--repo OWNER/REPO]\n\n${githubCreationHelp()}`,
261
279
  search: `usage: kb search <query> [options]\n\n${databaseHelp()}\n --limit N`,
262
280
  ask: `usage: kb ask <question> [options]\n\n${databaseHelp()}\n --limit N`,
@@ -389,3 +407,19 @@ function printInitRepoNextStep(result) {
389
407
  "next: rerun kb init-repo with the missing repo or database inputs when you are ready, or commit the scaffold now and finish remote setup later"
390
408
  );
391
409
  }
410
+
411
+ function printInitRepoChecklist(result) {
412
+ console.log("");
413
+ console.log("what to do next:");
414
+ if (result.created.length || result.skipped.length) {
415
+ console.log(` 1. commit and push the scaffolded knowledge repo files, including ${result.docsRootRelative}/`);
416
+ }
417
+ if (result.github.status !== "configured") {
418
+ console.log(" 2. make sure the target repo exists and rerun with --repo once GitHub setup is ready");
419
+ }
420
+ if (result.database.status !== "verified") {
421
+ console.log(" 3. rerun with --database-url once the target database is ready");
422
+ }
423
+ console.log(" 4. use kb create to open a proposal issue for the first knowledge entry");
424
+ console.log(" 5. review the issue, add kb-approved, merge the generated PR, and let publish sync the live KB");
425
+ }
@@ -0,0 +1,253 @@
1
+ import path from "node:path";
2
+ import fs from "node:fs";
3
+ import readline from "node:readline/promises";
4
+ import { stdin as defaultStdin, stdout as defaultStdout } from "node:process";
5
+ import { resolveGitHubRepository } from "./config.js";
6
+
7
+ export async function collectInitRepoInteractiveOptions({
8
+ flags,
9
+ env = process.env,
10
+ cwd = process.cwd(),
11
+ stdin = defaultStdin,
12
+ stdout = defaultStdout,
13
+ prompter = null
14
+ }) {
15
+ if (!prompter && (!stdin.isTTY || !stdout.isTTY)) {
16
+ throw new Error("interactive init-repo requires a TTY; rerun in a terminal or pass flags directly");
17
+ }
18
+
19
+ const prompt = prompter ?? createReadlinePrompter({ stdin, stdout });
20
+
21
+ try {
22
+ const defaults = buildInitRepoDefaults({ flags, env, cwd });
23
+
24
+ stdout.write("interactive kb init-repo\n");
25
+ stdout.write("this wizard scaffolds the knowledge repo and can also wire remote setup now.\n\n");
26
+
27
+ const targetDir = path.resolve(
28
+ cwd,
29
+ await prompt.askText("knowledge repo directory", defaults.targetDir, {
30
+ validate: requireValue
31
+ })
32
+ );
33
+ const layout = await askKnowledgeLayout(prompt, targetDir, defaults.layout);
34
+
35
+ const configureRepo = await prompt.askConfirm(
36
+ "configure the GitHub repo now",
37
+ Boolean(defaults.repo || env.GITHUB_TOKEN)
38
+ );
39
+
40
+ let repo = null;
41
+ if (configureRepo) {
42
+ repo = await prompt.askText("target GitHub repo (OWNER/REPO)", defaults.repo, {
43
+ validate: requireValue
44
+ });
45
+ }
46
+
47
+ const verifyDatabase = await prompt.askConfirm(
48
+ "verify the database and initialize schema now",
49
+ Boolean(defaults.databaseUrl)
50
+ );
51
+
52
+ let databaseUrl = null;
53
+ if (verifyDatabase) {
54
+ databaseUrl = await prompt.askText("database URL", defaults.databaseUrl, {
55
+ validate: requireValue
56
+ });
57
+ }
58
+
59
+ let embeddingMode = null;
60
+ let embeddingApiUrl = null;
61
+ let embeddingModel = null;
62
+ let embeddingApiKey = null;
63
+ let dbConnectTimeoutMs = null;
64
+
65
+ if (verifyDatabase) {
66
+ const useRemoteEmbeddings = await prompt.askConfirm(
67
+ "configure remote embeddings now",
68
+ defaults.embeddingMode === "bge-m3-openai"
69
+ );
70
+
71
+ if (useRemoteEmbeddings) {
72
+ embeddingMode = "bge-m3-openai";
73
+ embeddingApiUrl = await prompt.askText(
74
+ "embedding API URL",
75
+ defaults.embeddingApiUrl || "https://your-embeddings-host/v1/embeddings",
76
+ { validate: requireValue }
77
+ );
78
+ embeddingModel = await prompt.askText(
79
+ "embedding model",
80
+ defaults.embeddingModel || "BAAI/bge-m3",
81
+ { validate: requireValue }
82
+ );
83
+ embeddingApiKey = await prompt.askText(
84
+ "embedding API key (optional)",
85
+ defaults.embeddingApiKey
86
+ );
87
+ dbConnectTimeoutMs = await prompt.askText(
88
+ "database connect timeout ms",
89
+ defaults.dbConnectTimeoutMs || "20000",
90
+ { validate: requirePositiveNumber }
91
+ );
92
+ }
93
+ }
94
+
95
+ let repoAutomationToken = null;
96
+ if (configureRepo) {
97
+ const useAutomationToken = await prompt.askConfirm(
98
+ "set a dedicated repo automation token secret",
99
+ Boolean(defaults.repoAutomationToken)
100
+ );
101
+ if (useAutomationToken) {
102
+ repoAutomationToken = await prompt.askText(
103
+ "repo automation token",
104
+ defaults.repoAutomationToken
105
+ );
106
+ }
107
+ }
108
+
109
+ stdout.write("\nplan:\n");
110
+ stdout.write(` local scaffold: ${targetDir}\n`);
111
+ stdout.write(` document layout: ${layout === "repo-root" ? "docs/" : "kb/docs/"}\n`);
112
+ stdout.write(` github repo setup: ${repo ? repo : "skip for now"}\n`);
113
+ stdout.write(` database preflight: ${databaseUrl ? "yes" : "skip for now"}\n`);
114
+ stdout.write(
115
+ ` embeddings config: ${embeddingMode === "bge-m3-openai" ? `${embeddingMode}/${embeddingModel}` : "skip for now"}\n`
116
+ );
117
+
118
+ const proceed = await prompt.askConfirm("run init-repo with this plan", true);
119
+ if (!proceed) {
120
+ throw new Error("interactive init-repo cancelled");
121
+ }
122
+
123
+ return {
124
+ targetDir,
125
+ layout,
126
+ repo,
127
+ databaseUrl,
128
+ embeddingMode,
129
+ embeddingApiUrl,
130
+ embeddingModel,
131
+ embeddingApiKey: emptyToNull(embeddingApiKey),
132
+ dbConnectTimeoutMs: emptyToNull(dbConnectTimeoutMs),
133
+ repoAutomationToken: emptyToNull(repoAutomationToken)
134
+ };
135
+ } finally {
136
+ await prompt.close();
137
+ }
138
+ }
139
+
140
+ export function buildInitRepoDefaults({ flags = {}, env = process.env, cwd = process.cwd() }) {
141
+ return {
142
+ targetDir: flags.dir ?? cwd,
143
+ layout:
144
+ flags.layout ?? inferDefaultKnowledgeLayout(flags.dir ? path.resolve(cwd, flags.dir) : cwd),
145
+ repo: flags.repo ?? resolveGitHubRepository(env) ?? "",
146
+ databaseUrl: flags["database-url"] ?? env.KB_DATABASE_URL ?? "",
147
+ embeddingMode: flags["embedding-mode"] ?? env.KB_EMBEDDING_MODE ?? "",
148
+ embeddingApiUrl: flags["embedding-api-url"] ?? env.KB_EMBEDDING_API_URL ?? "",
149
+ embeddingModel: flags["embedding-model"] ?? env.KB_EMBEDDING_MODEL ?? "",
150
+ embeddingApiKey: flags["embedding-api-key"] ?? env.KB_EMBEDDING_API_KEY ?? "",
151
+ dbConnectTimeoutMs:
152
+ flags["db-connect-timeout-ms"] ?? env.KB_DB_CONNECT_TIMEOUT_MS ?? "",
153
+ repoAutomationToken:
154
+ flags["repo-automation-token"] ?? env.KB_REPO_AUTOMATION_TOKEN ?? ""
155
+ };
156
+ }
157
+
158
+ async function askKnowledgeLayout(prompt, targetDir, defaultLayout) {
159
+ const useRepoRoot = await prompt.askConfirm(
160
+ "store documents at repo root docs/ (recommended when this repo is the knowledge base)",
161
+ defaultLayout === "repo-root"
162
+ );
163
+ if (useRepoRoot) {
164
+ return "repo-root";
165
+ }
166
+ if (!fs.existsSync(targetDir) || fs.readdirSync(targetDir).length === 0) {
167
+ return "nested-kb";
168
+ }
169
+ return "nested-kb";
170
+ }
171
+
172
+ function inferDefaultKnowledgeLayout(targetDir) {
173
+ const root = path.resolve(targetDir);
174
+ if (fs.existsSync(path.join(root, "docs"))) {
175
+ return "repo-root";
176
+ }
177
+ if (fs.existsSync(path.join(root, "kb", "docs"))) {
178
+ return "nested-kb";
179
+ }
180
+ return "repo-root";
181
+ }
182
+
183
+ function createReadlinePrompter({ stdin, stdout }) {
184
+ const rl = readline.createInterface({
185
+ input: stdin,
186
+ output: stdout
187
+ });
188
+ return {
189
+ askText(label, defaultValue = "", options = {}) {
190
+ return askText(rl, label, defaultValue, options);
191
+ },
192
+ askConfirm(label, defaultValue = false) {
193
+ return askConfirm(rl, label, defaultValue);
194
+ },
195
+ async close() {
196
+ rl.close();
197
+ }
198
+ };
199
+ }
200
+
201
+ async function askText(rl, label, defaultValue = "", { validate = null } = {}) {
202
+ while (true) {
203
+ const suffix = defaultValue ? ` [${defaultValue}]` : "";
204
+ const answer = (await rl.question(`${label}${suffix}: `)).trim();
205
+ const value = answer || String(defaultValue ?? "").trim();
206
+
207
+ if (!validate) {
208
+ return value;
209
+ }
210
+ const error = validate(value);
211
+ if (!error) {
212
+ return value;
213
+ }
214
+ rl.output.write(`${error}\n`);
215
+ }
216
+ }
217
+
218
+ async function askConfirm(rl, label, defaultValue = false) {
219
+ const hint = defaultValue ? "Y/n" : "y/N";
220
+ while (true) {
221
+ const answer = (await rl.question(`${label}? [${hint}]: `)).trim().toLowerCase();
222
+ if (!answer) {
223
+ return defaultValue;
224
+ }
225
+ if (["y", "yes"].includes(answer)) {
226
+ return true;
227
+ }
228
+ if (["n", "no"].includes(answer)) {
229
+ return false;
230
+ }
231
+ rl.output.write("please answer yes or no\n");
232
+ }
233
+ }
234
+
235
+ function requireValue(value) {
236
+ return String(value ?? "").trim() ? null : "a value is required";
237
+ }
238
+
239
+ function requirePositiveNumber(value) {
240
+ if (!String(value ?? "").trim()) {
241
+ return "a value is required";
242
+ }
243
+ const number = Number(value);
244
+ if (!Number.isFinite(number) || number <= 0) {
245
+ return "enter a positive number";
246
+ }
247
+ return null;
248
+ }
249
+
250
+ function emptyToNull(value) {
251
+ const normalized = String(value ?? "").trim();
252
+ return normalized ? normalized : null;
253
+ }
package/lib/repo-init.js CHANGED
@@ -5,6 +5,8 @@ import { connect, initDb } from "./db.js";
5
5
  import { maskConnection } from "./cli-common.js";
6
6
 
7
7
  const PACKAGE_NAME = "@nzpr/kb";
8
+ const REPO_ROOT_LAYOUT = "repo-root";
9
+ const NESTED_KB_LAYOUT = "nested-kb";
8
10
  const LABELS = Object.freeze([
9
11
  {
10
12
  name: "kb-entry",
@@ -18,14 +20,19 @@ const LABELS = Object.freeze([
18
20
  }
19
21
  ]);
20
22
 
21
- export function initializeKnowledgeRepo({ targetDir = process.cwd() } = {}) {
23
+ export function initializeKnowledgeRepo({
24
+ targetDir = process.cwd(),
25
+ layout = NESTED_KB_LAYOUT
26
+ } = {}) {
22
27
  const root = path.resolve(targetDir);
28
+ const normalizedLayout = normalizeKnowledgeLayout(layout);
29
+ const docsRootRelative = docsRootForLayout(normalizedLayout);
23
30
  const files = new Map([
24
31
  [".github/ISSUE_TEMPLATE/config.yml", renderIssueConfig()],
25
- [".github/ISSUE_TEMPLATE/knowledge-document.md", renderIssueTemplate()],
26
- [".github/workflows/kb-issue-to-pr.yml", renderIssueToPrWorkflow()],
27
- [".github/workflows/kb-publish.yml", renderPublishWorkflow()],
28
- ["kb/docs/.gitkeep", ""]
32
+ [".github/ISSUE_TEMPLATE/knowledge-document.md", renderIssueTemplate({ docsRootRelative })],
33
+ [".github/workflows/kb-issue-to-pr.yml", renderIssueToPrWorkflow({ docsRootRelative })],
34
+ [".github/workflows/kb-publish.yml", renderPublishWorkflow({ docsRootRelative })],
35
+ [`${docsRootRelative}/.gitkeep`, ""]
29
36
  ]);
30
37
 
31
38
  const created = [];
@@ -44,6 +51,8 @@ export function initializeKnowledgeRepo({ targetDir = process.cwd() } = {}) {
44
51
 
45
52
  return {
46
53
  root,
54
+ layout: normalizedLayout,
55
+ docsRootRelative,
47
56
  created,
48
57
  skipped,
49
58
  configuration: buildConfigurationGuide()
@@ -52,6 +61,7 @@ export function initializeKnowledgeRepo({ targetDir = process.cwd() } = {}) {
52
61
 
53
62
  export async function bootstrapKnowledgeRepo({
54
63
  targetDir = process.cwd(),
64
+ layout = NESTED_KB_LAYOUT,
55
65
  repo = null,
56
66
  githubToken = null,
57
67
  databaseUrl = null,
@@ -64,7 +74,7 @@ export async function bootstrapKnowledgeRepo({
64
74
  runGitHubCommand = defaultRunGitHubCommand,
65
75
  verifyDatabaseReady = defaultVerifyDatabaseReady
66
76
  } = {}) {
67
- const scaffold = initializeKnowledgeRepo({ targetDir });
77
+ const scaffold = initializeKnowledgeRepo({ targetDir, layout });
68
78
  const result = {
69
79
  ...scaffold,
70
80
  ok: true,
@@ -284,7 +294,7 @@ function renderIssueConfig() {
284
294
  return ["blank_issues_enabled: false", ""].join("\n");
285
295
  }
286
296
 
287
- function renderIssueTemplate() {
297
+ function renderIssueTemplate({ docsRootRelative }) {
288
298
  return [
289
299
  "---",
290
300
  "name: Knowledge Base Document",
@@ -313,12 +323,12 @@ function renderIssueTemplate() {
313
323
  "",
314
324
  "1. Open the issue with this template.",
315
325
  "2. Review and edit the issue until the title and text are ready.",
316
- "3. Add the `kb-approved` label to generate a PR that writes the Markdown file into `kb/docs/`.",
326
+ `3. Add the \`kb-approved\` label to generate a PR that writes the Markdown file into \`${docsRootRelative}/\`.`,
317
327
  ""
318
328
  ].join("\n");
319
329
  }
320
330
 
321
- function renderIssueToPrWorkflow() {
331
+ function renderIssueToPrWorkflow({ docsRootRelative }) {
322
332
  return [
323
333
  "name: kb-issue-to-pr",
324
334
  "",
@@ -353,7 +363,7 @@ function renderIssueToPrWorkflow() {
353
363
  "",
354
364
  " - name: Materialize approved issue",
355
365
  " id: materialize",
356
- ' run: kb-admin issue-to-doc --issue-event "$GITHUB_EVENT_PATH" --docs-root ./kb/docs',
366
+ ` run: kb-admin issue-to-doc --issue-event "$GITHUB_EVENT_PATH" --docs-root ./${docsRootRelative}`,
357
367
  "",
358
368
  " - name: Create pull request",
359
369
  " id: cpr",
@@ -382,7 +392,7 @@ function renderIssueToPrWorkflow() {
382
392
  ].join("\n");
383
393
  }
384
394
 
385
- function renderPublishWorkflow() {
395
+ function renderPublishWorkflow({ docsRootRelative }) {
386
396
  return [
387
397
  "name: kb-publish",
388
398
  "",
@@ -391,7 +401,7 @@ function renderPublishWorkflow() {
391
401
  " branches:",
392
402
  " - main",
393
403
  " paths:",
394
- ' - "kb/docs/**"',
404
+ ` - "${docsRootRelative}/**"`,
395
405
  ' - ".github/workflows/**"',
396
406
  " workflow_dispatch:",
397
407
  "",
@@ -432,7 +442,18 @@ function renderPublishWorkflow() {
432
442
  ` run: npm install -g ${PACKAGE_NAME}`,
433
443
  "",
434
444
  " - name: Publish knowledge",
435
- " run: kb publish --docs-root ./kb/docs",
445
+ ` run: kb publish --docs-root ./${docsRootRelative}`,
436
446
  ""
437
447
  ].join("\n");
438
448
  }
449
+
450
+ function normalizeKnowledgeLayout(layout) {
451
+ if (layout === REPO_ROOT_LAYOUT || layout === NESTED_KB_LAYOUT) {
452
+ return layout;
453
+ }
454
+ throw new Error(`unsupported init-repo layout: ${layout}`);
455
+ }
456
+
457
+ function docsRootForLayout(layout) {
458
+ return layout === REPO_ROOT_LAYOUT ? "docs" : "kb/docs";
459
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nzpr/kb",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Knowledge base CLI for proposing, publishing, and querying curated agent knowledge.",
5
5
  "repository": {
6
6
  "type": "git",