@nzpr/kb 0.1.2 → 0.1.4

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
@@ -24,6 +24,7 @@ The public surface of `@nzpr/kb` is the `kb` CLI.
24
24
  - `kb init-repo`
25
25
  Initialize a knowledge base workspace.
26
26
  Use `kb init-repo --interactive` for a guided setup flow.
27
+ Use `--layout repo-root` when this repository is itself the KB.
27
28
 
28
29
  - `kb create --title TEXT --text TEXT`
29
30
  Propose a new knowledge document or a substantial update.
@@ -102,6 +103,13 @@ If you want the CLI to walk you through setup and tell you what to do next:
102
103
  kb init-repo --interactive
103
104
  ```
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
+
105
113
  Or bootstrap and configure it in one step:
106
114
 
107
115
  ```bash
@@ -120,12 +128,13 @@ Inside that knowledge repo, the normal flow is:
120
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.
121
129
  2. Open or update a knowledge proposal issue.
122
130
  3. Review and approve it there.
123
- 4. Materialize it into `kb/docs/`.
124
- 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.
125
133
 
126
134
  `kb init-repo` is safe to rerun. It reports bootstrap status for:
127
135
 
128
136
  - local scaffold creation
137
+ - knowledge document layout selection
129
138
  - database preflight and schema initialization
130
139
  - GitHub repo labels, variables, and secrets
131
140
 
@@ -185,7 +194,7 @@ export KB_DATABASE_URL=postgresql://kb:kb@localhost:5432/kb
185
194
  export KB_GITHUB_REPO=owner/repo
186
195
  export GITHUB_TOKEN=...
187
196
  docker compose -f docker-compose.pgvector.yml up -d
188
- kb publish --docs-root ./kb/docs
197
+ kb publish --docs-root ./docs
189
198
  kb catalog --json
190
199
  kb search "deployment rule"
191
200
  ```
@@ -215,13 +224,13 @@ The package is published as `@nzpr/kb`.
215
224
 
216
225
  ## Using From A Knowledge Repo
217
226
 
218
- 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/`:
219
228
 
220
229
  ```bash
221
230
  npm install -g @nzpr/kb
222
231
  export KB_GITHUB_REPO=owner/repo
223
232
  export GITHUB_TOKEN=...
224
- kb publish --docs-root ./kb/docs
233
+ kb publish --docs-root ./docs
225
234
  ```
226
235
 
227
236
  Or scaffold that repo first:
package/lib/cli.js CHANGED
@@ -65,6 +65,7 @@ export async function main(argv) {
65
65
  ? await collectInitRepoInteractiveOptions({ flags })
66
66
  : {
67
67
  targetDir: flags.dir ?? process.cwd(),
68
+ layout: flags.layout ?? "nested-kb",
68
69
  repo: flags.repo ?? resolveGitHubRepository(),
69
70
  databaseUrl: flags["database-url"] ?? process.env.KB_DATABASE_URL ?? null,
70
71
  embeddingMode: flags["embedding-mode"] ?? process.env.KB_EMBEDDING_MODE ?? null,
@@ -78,8 +79,9 @@ export async function main(argv) {
78
79
  };
79
80
  const result = await bootstrapKnowledgeRepo({
80
81
  targetDir: initRepoOptions.targetDir,
82
+ layout: initRepoOptions.layout,
81
83
  repo: initRepoOptions.repo,
82
- githubToken: process.env.GITHUB_TOKEN ?? null,
84
+ githubToken: initRepoOptions.githubToken ?? process.env.GITHUB_TOKEN ?? null,
83
85
  databaseUrl: initRepoOptions.databaseUrl,
84
86
  embeddingMode: initRepoOptions.embeddingMode,
85
87
  embeddingApiUrl: initRepoOptions.embeddingApiUrl,
@@ -89,6 +91,7 @@ export async function main(argv) {
89
91
  repoAutomationToken: initRepoOptions.repoAutomationToken
90
92
  });
91
93
  console.log(`initialized knowledge repo scaffold in ${result.root}`);
94
+ console.log(`knowledge documents will live in ${result.docsRootRelative}/`);
92
95
  for (const relativePath of result.created) {
93
96
  console.log(`created ${relativePath}`);
94
97
  }
@@ -246,7 +249,7 @@ function printHelp() {
246
249
  console.log(`usage: kb <command> [options]
247
250
 
248
251
  commands:
249
- init-repo [--interactive] [--dir PATH] [--repo OWNER/REPO]
252
+ init-repo [--interactive] [--layout repo-root|nested-kb] [--dir PATH] [--repo OWNER/REPO]
250
253
  create --title TEXT --text TEXT [--path RELATIVE_PATH]
251
254
  search <query>
252
255
  ask <question>
@@ -271,7 +274,7 @@ search options:
271
274
  function printCommandHelp(command) {
272
275
  const commandHelp = {
273
276
  "init-repo":
274
- "usage: kb init-repo [--interactive] [--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.",
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.",
275
278
  create: `usage: kb create --title TEXT --text TEXT [--path RELATIVE_PATH] [--repo OWNER/REPO]\n\n${githubCreationHelp()}`,
276
279
  search: `usage: kb search <query> [options]\n\n${databaseHelp()}\n --limit N`,
277
280
  ask: `usage: kb ask <question> [options]\n\n${databaseHelp()}\n --limit N`,
@@ -327,7 +330,7 @@ async function requirePublishAccess({ repo, token, apiBaseUrl }) {
327
330
 
328
331
  function printRepoConfiguration(configuration) {
329
332
  console.log("");
330
- console.log("configure the knowledge repo with these GitHub settings:");
333
+ console.log("configuration reference:");
331
334
  console.log("");
332
335
  console.log("required secrets:");
333
336
  for (const entry of configuration.requiredSecrets) {
@@ -351,7 +354,10 @@ function printRepoConfiguration(configuration) {
351
354
 
352
355
  function printInitRepoStatus(result) {
353
356
  console.log("");
354
- console.log("bootstrap status:");
357
+ console.log("summary:");
358
+ console.log(` docs layout: ${result.docsRootRelative}/`);
359
+ console.log(` files created: ${result.created.length}`);
360
+ console.log(` files kept: ${result.skipped.length}`);
355
361
  printInitRepoDatabaseStatus(result.database);
356
362
  printInitRepoGitHubStatus(result.github);
357
363
  }
@@ -373,6 +379,11 @@ function printInitRepoDatabaseStatus(database) {
373
379
  function printInitRepoGitHubStatus(github) {
374
380
  if (github.status === "configured") {
375
381
  console.log(` github: configured ${github.repo}`);
382
+ if (github.actions) {
383
+ console.log(
384
+ ` actions: enabled=${github.actions.enabled} workflow_permissions=${github.actions.defaultWorkflowPermissions} pr_creation=${github.actions.canApprovePullRequestReviews}`
385
+ );
386
+ }
376
387
  console.log(` labels: ${github.labels.join(", ")}`);
377
388
  if (github.secrets.length) {
378
389
  console.log(` secrets: ${github.secrets.join(", ")}`);
@@ -407,9 +418,9 @@ function printInitRepoNextStep(result) {
407
418
 
408
419
  function printInitRepoChecklist(result) {
409
420
  console.log("");
410
- console.log("what to do next:");
421
+ console.log("next steps:");
411
422
  if (result.created.length || result.skipped.length) {
412
- console.log(" 1. commit and push the scaffolded knowledge repo files");
423
+ console.log(` 1. commit and push the scaffolded knowledge repo files, including ${result.docsRootRelative}/`);
413
424
  }
414
425
  if (result.github.status !== "configured") {
415
426
  console.log(" 2. make sure the target repo exists and rerun with --repo once GitHub setup is ready");
@@ -419,4 +430,14 @@ function printInitRepoChecklist(result) {
419
430
  }
420
431
  console.log(" 4. use kb create to open a proposal issue for the first knowledge entry");
421
432
  console.log(" 5. review the issue, add kb-approved, merge the generated PR, and let publish sync the live KB");
433
+ console.log("");
434
+ console.log("done in this run:");
435
+ if (result.created.length) {
436
+ console.log(` created: ${result.created.join(", ")}`);
437
+ } else {
438
+ console.log(" created: no new files");
439
+ }
440
+ if (result.skipped.length) {
441
+ console.log(` kept: ${result.skipped.join(", ")}`);
442
+ }
422
443
  }
@@ -1,4 +1,6 @@
1
1
  import path from "node:path";
2
+ import fs from "node:fs";
3
+ import { execFileSync } from "node:child_process";
2
4
  import readline from "node:readline/promises";
3
5
  import { stdin as defaultStdin, stdout as defaultStdout } from "node:process";
4
6
  import { resolveGitHubRepository } from "./config.js";
@@ -9,7 +11,8 @@ export async function collectInitRepoInteractiveOptions({
9
11
  cwd = process.cwd(),
10
12
  stdin = defaultStdin,
11
13
  stdout = defaultStdout,
12
- prompter = null
14
+ prompter = null,
15
+ auth = defaultInteractiveGitHubAuth
13
16
  }) {
14
17
  if (!prompter && (!stdin.isTTY || !stdout.isTTY)) {
15
18
  throw new Error("interactive init-repo requires a TTY; rerun in a terminal or pass flags directly");
@@ -20,8 +23,8 @@ export async function collectInitRepoInteractiveOptions({
20
23
  try {
21
24
  const defaults = buildInitRepoDefaults({ flags, env, cwd });
22
25
 
23
- stdout.write("interactive kb init-repo\n");
24
- stdout.write("this wizard scaffolds the knowledge repo and can also wire remote setup now.\n\n");
26
+ stdout.write("KB Init Wizard\n");
27
+ stdout.write("This walkthrough prepares the knowledge repo and can wire GitHub and database setup now.\n\n");
25
28
 
26
29
  const targetDir = path.resolve(
27
30
  cwd,
@@ -29,6 +32,7 @@ export async function collectInitRepoInteractiveOptions({
29
32
  validate: requireValue
30
33
  })
31
34
  );
35
+ const layout = await askKnowledgeLayout(prompt, targetDir, defaults.layout);
32
36
 
33
37
  const configureRepo = await prompt.askConfirm(
34
38
  "configure the GitHub repo now",
@@ -40,6 +44,15 @@ export async function collectInitRepoInteractiveOptions({
40
44
  repo = await prompt.askText("target GitHub repo (OWNER/REPO)", defaults.repo, {
41
45
  validate: requireValue
42
46
  });
47
+ if (!defaults.githubToken) {
48
+ const authorized = await ensureInteractiveGitHubAccess({ prompt, stdout, auth });
49
+ if (authorized) {
50
+ defaults.githubToken = authorized;
51
+ } else {
52
+ stdout.write("skipping GitHub repo setup for now because GitHub auth is not ready.\n");
53
+ repo = null;
54
+ }
55
+ }
43
56
  }
44
57
 
45
58
  const verifyDatabase = await prompt.askConfirm(
@@ -93,7 +106,7 @@ export async function collectInitRepoInteractiveOptions({
93
106
  let repoAutomationToken = null;
94
107
  if (configureRepo) {
95
108
  const useAutomationToken = await prompt.askConfirm(
96
- "set a dedicated repo automation token secret",
109
+ "use a custom automation token instead of the default GitHub Actions token",
97
110
  Boolean(defaults.repoAutomationToken)
98
111
  );
99
112
  if (useAutomationToken) {
@@ -104,9 +117,10 @@ export async function collectInitRepoInteractiveOptions({
104
117
  }
105
118
  }
106
119
 
107
- stdout.write("\nplan:\n");
108
- stdout.write(` local scaffold: ${targetDir}\n`);
109
- stdout.write(` github repo setup: ${repo ? repo : "skip for now"}\n`);
120
+ stdout.write("\nPlan\n");
121
+ stdout.write(` target directory: ${targetDir}\n`);
122
+ stdout.write(` document layout: ${layout === "repo-root" ? "docs/" : "kb/docs/"}\n`);
123
+ stdout.write(` GitHub repo setup: ${repo ? repo : "skip for now"}\n`);
110
124
  stdout.write(` database preflight: ${databaseUrl ? "yes" : "skip for now"}\n`);
111
125
  stdout.write(
112
126
  ` embeddings config: ${embeddingMode === "bge-m3-openai" ? `${embeddingMode}/${embeddingModel}` : "skip for now"}\n`
@@ -119,7 +133,9 @@ export async function collectInitRepoInteractiveOptions({
119
133
 
120
134
  return {
121
135
  targetDir,
136
+ layout,
122
137
  repo,
138
+ githubToken: defaults.githubToken || null,
123
139
  databaseUrl,
124
140
  embeddingMode,
125
141
  embeddingApiUrl,
@@ -136,6 +152,9 @@ export async function collectInitRepoInteractiveOptions({
136
152
  export function buildInitRepoDefaults({ flags = {}, env = process.env, cwd = process.cwd() }) {
137
153
  return {
138
154
  targetDir: flags.dir ?? cwd,
155
+ layout:
156
+ flags.layout ?? inferDefaultKnowledgeLayout(flags.dir ? path.resolve(cwd, flags.dir) : cwd),
157
+ githubToken: env.GITHUB_TOKEN ?? "",
139
158
  repo: flags.repo ?? resolveGitHubRepository(env) ?? "",
140
159
  databaseUrl: flags["database-url"] ?? env.KB_DATABASE_URL ?? "",
141
160
  embeddingMode: flags["embedding-mode"] ?? env.KB_EMBEDDING_MODE ?? "",
@@ -149,6 +168,31 @@ export function buildInitRepoDefaults({ flags = {}, env = process.env, cwd = pro
149
168
  };
150
169
  }
151
170
 
171
+ async function askKnowledgeLayout(prompt, targetDir, defaultLayout) {
172
+ const useRepoRoot = await prompt.askConfirm(
173
+ "store documents at repo root docs/ (recommended when this repo is the knowledge base)",
174
+ defaultLayout === "repo-root"
175
+ );
176
+ if (useRepoRoot) {
177
+ return "repo-root";
178
+ }
179
+ if (!fs.existsSync(targetDir) || fs.readdirSync(targetDir).length === 0) {
180
+ return "nested-kb";
181
+ }
182
+ return "nested-kb";
183
+ }
184
+
185
+ function inferDefaultKnowledgeLayout(targetDir) {
186
+ const root = path.resolve(targetDir);
187
+ if (fs.existsSync(path.join(root, "docs"))) {
188
+ return "repo-root";
189
+ }
190
+ if (fs.existsSync(path.join(root, "kb", "docs"))) {
191
+ return "nested-kb";
192
+ }
193
+ return "repo-root";
194
+ }
195
+
152
196
  function createReadlinePrompter({ stdin, stdout }) {
153
197
  const rl = readline.createInterface({
154
198
  input: stdin,
@@ -220,3 +264,45 @@ function emptyToNull(value) {
220
264
  const normalized = String(value ?? "").trim();
221
265
  return normalized ? normalized : null;
222
266
  }
267
+
268
+ async function ensureInteractiveGitHubAccess({ prompt, stdout, auth }) {
269
+ const existingToken = auth.getToken();
270
+ if (existingToken) {
271
+ stdout.write("using existing GitHub CLI authentication for repo setup.\n");
272
+ return existingToken;
273
+ }
274
+
275
+ stdout.write("\nGitHub repo setup needs GitHub authentication.\n");
276
+ stdout.write("The wizard can open GitHub CLI login in your browser and then continue.\n");
277
+ const loginNow = await prompt.askConfirm("authorize GitHub CLI now", true);
278
+ if (!loginNow) {
279
+ return null;
280
+ }
281
+
282
+ auth.login();
283
+ const token = auth.getToken();
284
+ if (token) {
285
+ stdout.write("GitHub CLI authentication is ready.\n");
286
+ return token;
287
+ }
288
+
289
+ throw new Error("GitHub authentication did not complete; rerun gh auth login or set GITHUB_TOKEN");
290
+ }
291
+
292
+ const defaultInteractiveGitHubAuth = {
293
+ getToken() {
294
+ try {
295
+ return execFileSync("gh", ["auth", "token"], {
296
+ encoding: "utf8",
297
+ stdio: ["ignore", "pipe", "ignore"]
298
+ }).trim();
299
+ } catch {
300
+ return null;
301
+ }
302
+ },
303
+ login() {
304
+ execFileSync("gh", ["auth", "login", "--web", "--git-protocol", "https"], {
305
+ stdio: "inherit"
306
+ });
307
+ }
308
+ };
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,
@@ -102,7 +112,8 @@ export async function bootstrapKnowledgeRepo({
102
112
  result.github = {
103
113
  status: "failed",
104
114
  repo,
105
- error: "GITHUB_TOKEN is required when configuring a knowledge repo"
115
+ error:
116
+ "GITHUB_TOKEN is required when configuring a knowledge repo; set GITHUB_TOKEN or run gh auth login before rerunning"
106
117
  };
107
118
  return result;
108
119
  }
@@ -216,6 +227,11 @@ async function configureKnowledgeRepo({
216
227
  runGitHubCommand
217
228
  }) {
218
229
  await runGitHubCommand(["repo", "edit", repo, "--enable-issues"], { githubToken });
230
+ const actions = await ensureGitHubActionsPermissions({
231
+ repo,
232
+ githubToken,
233
+ runGitHubCommand
234
+ });
219
235
 
220
236
  for (const label of LABELS) {
221
237
  await runGitHubCommand(
@@ -249,12 +265,72 @@ async function configureKnowledgeRepo({
249
265
 
250
266
  return {
251
267
  repo,
268
+ actions,
252
269
  labels: LABELS.map((label) => label.name),
253
270
  secrets: [...secrets.keys()],
254
271
  variables: [...variables.keys()]
255
272
  };
256
273
  }
257
274
 
275
+ async function ensureGitHubActionsPermissions({ repo, githubToken, runGitHubCommand }) {
276
+ const actionsPermissions = await runGitHubJson(
277
+ ["api", `repos/${repo}/actions/permissions`],
278
+ { githubToken, runGitHubCommand }
279
+ );
280
+
281
+ if (actionsPermissions.enabled === false) {
282
+ await runGitHubCommand(
283
+ [
284
+ "api",
285
+ "--method",
286
+ "PUT",
287
+ `repos/${repo}/actions/permissions`,
288
+ "-F",
289
+ "enabled=true",
290
+ "-f",
291
+ `allowed_actions=${actionsPermissions.allowed_actions ?? "all"}`
292
+ ],
293
+ { githubToken }
294
+ );
295
+ }
296
+
297
+ const workflowPermissions = await runGitHubJson(
298
+ ["api", `repos/${repo}/actions/permissions/workflow`],
299
+ { githubToken, runGitHubCommand }
300
+ );
301
+
302
+ if (
303
+ workflowPermissions.default_workflow_permissions !== "write" ||
304
+ workflowPermissions.can_approve_pull_request_reviews !== true
305
+ ) {
306
+ await runGitHubCommand(
307
+ [
308
+ "api",
309
+ "--method",
310
+ "PUT",
311
+ `repos/${repo}/actions/permissions/workflow`,
312
+ "-f",
313
+ "default_workflow_permissions=write",
314
+ "-F",
315
+ "can_approve_pull_request_reviews=true"
316
+ ],
317
+ { githubToken }
318
+ );
319
+ }
320
+
321
+ return {
322
+ enabled: true,
323
+ allowedActions: actionsPermissions.allowed_actions ?? "all",
324
+ defaultWorkflowPermissions: "write",
325
+ canApprovePullRequestReviews: true
326
+ };
327
+ }
328
+
329
+ async function runGitHubJson(args, { githubToken, runGitHubCommand }) {
330
+ const output = await runGitHubCommand(args, { githubToken });
331
+ return JSON.parse(output);
332
+ }
333
+
258
334
  async function defaultVerifyDatabaseReady({ databaseUrl }) {
259
335
  const client = await connect(databaseUrl);
260
336
  try {
@@ -284,7 +360,7 @@ function renderIssueConfig() {
284
360
  return ["blank_issues_enabled: false", ""].join("\n");
285
361
  }
286
362
 
287
- function renderIssueTemplate() {
363
+ function renderIssueTemplate({ docsRootRelative }) {
288
364
  return [
289
365
  "---",
290
366
  "name: Knowledge Base Document",
@@ -313,12 +389,12 @@ function renderIssueTemplate() {
313
389
  "",
314
390
  "1. Open the issue with this template.",
315
391
  "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/`.",
392
+ `3. Add the \`kb-approved\` label to generate a PR that writes the Markdown file into \`${docsRootRelative}/\`.`,
317
393
  ""
318
394
  ].join("\n");
319
395
  }
320
396
 
321
- function renderIssueToPrWorkflow() {
397
+ function renderIssueToPrWorkflow({ docsRootRelative }) {
322
398
  return [
323
399
  "name: kb-issue-to-pr",
324
400
  "",
@@ -353,7 +429,7 @@ function renderIssueToPrWorkflow() {
353
429
  "",
354
430
  " - name: Materialize approved issue",
355
431
  " id: materialize",
356
- ' run: kb-admin issue-to-doc --issue-event "$GITHUB_EVENT_PATH" --docs-root ./kb/docs',
432
+ ` run: kb-admin issue-to-doc --issue-event "$GITHUB_EVENT_PATH" --docs-root ./${docsRootRelative}`,
357
433
  "",
358
434
  " - name: Create pull request",
359
435
  " id: cpr",
@@ -382,7 +458,7 @@ function renderIssueToPrWorkflow() {
382
458
  ].join("\n");
383
459
  }
384
460
 
385
- function renderPublishWorkflow() {
461
+ function renderPublishWorkflow({ docsRootRelative }) {
386
462
  return [
387
463
  "name: kb-publish",
388
464
  "",
@@ -391,7 +467,7 @@ function renderPublishWorkflow() {
391
467
  " branches:",
392
468
  " - main",
393
469
  " paths:",
394
- ' - "kb/docs/**"',
470
+ ` - "${docsRootRelative}/**"`,
395
471
  ' - ".github/workflows/**"',
396
472
  " workflow_dispatch:",
397
473
  "",
@@ -432,7 +508,18 @@ function renderPublishWorkflow() {
432
508
  ` run: npm install -g ${PACKAGE_NAME}`,
433
509
  "",
434
510
  " - name: Publish knowledge",
435
- " run: kb publish --docs-root ./kb/docs",
511
+ ` run: kb publish --docs-root ./${docsRootRelative}`,
436
512
  ""
437
513
  ].join("\n");
438
514
  }
515
+
516
+ function normalizeKnowledgeLayout(layout) {
517
+ if (layout === REPO_ROOT_LAYOUT || layout === NESTED_KB_LAYOUT) {
518
+ return layout;
519
+ }
520
+ throw new Error(`unsupported init-repo layout: ${layout}`);
521
+ }
522
+
523
+ function docsRootForLayout(layout) {
524
+ return layout === REPO_ROOT_LAYOUT ? "docs" : "kb/docs";
525
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nzpr/kb",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Knowledge base CLI for proposing, publishing, and querying curated agent knowledge.",
5
5
  "repository": {
6
6
  "type": "git",