@codecell-germany/company-agent-wiki-skill 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -59,6 +59,12 @@ The private knowledge workspace lives outside this public code repository. It ma
59
59
  The SQLite index is local and derived. It is rebuilt by the CLI and must not be treated as the source of truth.
60
60
  It lives inside the private workspace under `.company-agent-wiki/index.sqlite`, but it is intentionally kept out of Git by default because it is rebuildable, binary and noisy in diffs.
61
61
 
62
+ The workspace path can also be stored globally for other agents. Phase 1 now keeps a per-user workspace registry:
63
+
64
+ - macOS: `~/Library/Application Support/company-agent-wiki/workspaces.json`
65
+ - Windows: `%APPDATA%\\company-agent-wiki\\workspaces.json`
66
+ - Linux: `${XDG_CONFIG_HOME:-~/.config}/company-agent-wiki/workspaces.json`
67
+
62
68
  ## Install
63
69
 
64
70
  ```bash
@@ -107,6 +113,8 @@ company-agent-wiki-cli setup workspace \
107
113
  --git-remote git@github.com:your-org/private-company-knowledge.git
108
114
  ```
109
115
 
116
+ This now also registers the workspace globally and marks it as the default for later agents.
117
+
110
118
  3. Inspect the local state:
111
119
 
112
120
  ```bash
@@ -138,6 +146,16 @@ company-agent-wiki-cli serve --workspace /absolute/path/to/private-company-knowl
138
146
  The read-only web view is served by the installed CLI process. The private workspace itself contains Markdown, metadata and the local derived index, but no standalone frontend application.
139
147
 
140
148
  If the current shell is already inside a private workspace, runtime commands such as `doctor`, `verify`, `search`, `route`, `read`, `history`, `diff` and `serve` may omit `--workspace`.
149
+ If not, the CLI can now also fall back to the globally registered default workspace.
150
+
151
+ Useful discovery commands:
152
+
153
+ ```bash
154
+ company-agent-wiki-cli workspace current --json
155
+ company-agent-wiki-cli workspace list --json
156
+ company-agent-wiki-cli workspace register --workspace /absolute/path/to/private-company-knowledge --default --json
157
+ company-agent-wiki-cli workspace use --workspace /absolute/path/to/private-company-knowledge --json
158
+ ```
141
159
 
142
160
  By default `setup workspace` also creates starter Markdown documents such as `wiki-start-here.md`, `company-profile.md`, `organisation-und-rollen.md`, `systeme-und-tools.md`, `kernprozesse.md`, `projekte-und-roadmap.md` and `glossar.md`. Use `--no-starter-docs` only if you intentionally want an almost empty scaffold.
143
161
 
@@ -169,15 +187,16 @@ This repository is publishable code only. It must never contain:
169
187
 
170
188
  The actual knowledge workspace is separate and private. The human must provide:
171
189
 
172
- - the private workspace path
190
+ - the private workspace path at least once
173
191
  - if desired, the private Git remote URL
174
192
  - access rights to that remote
175
193
 
176
- The agent can handle local scaffolding, root registration and index rebuilds, but it should not invent remotes or inject private data into this repository.
194
+ The agent can handle local scaffolding, root registration, global workspace registration and index rebuilds, but it should not invent remotes or inject private data into this repository.
177
195
 
178
196
  ## Phase 1 Commands
179
197
 
180
198
  - `setup workspace`: scaffold a private workspace and optionally initialize Git
199
+ - `workspace current|list|register|use`: inspect or manage the global workspace registry for other agents
181
200
  - `doctor`: inspect the local runtime and workspace state
182
201
  - `verify`: check whether the current roots still match the indexed snapshot
183
202
  - `roots add`: register another local Markdown root
@@ -247,7 +266,7 @@ Then:
247
266
 
248
267
  ## Concurrency Note
249
268
 
250
- The SQLite index is intentionally local and rebuildable. For the same workspace, avoid running multiple `search`, `route`, `read`, `history` and `diff` calls in parallel when possible. The CLI now uses a busy timeout and a lock-specific error, but agent-side serialization is still the safer Phase-1 operating mode.
269
+ The SQLite index is intentionally local and rebuildable. Parallel reads such as `search`, `route`, `read`, `history` and `diff` are now an explicit Phase-1 goal and should work across multiple agents. Write paths such as `index rebuild` and onboarding apply are serialized per workspace through a local write lock, so concurrent writes queue behind the active writer instead of colliding.
251
270
 
252
271
  ## What Phase 1 Does Not Do
253
272
 
package/dist/index.js CHANGED
@@ -1189,8 +1189,8 @@ var require_command = __commonJS({
1189
1189
  "node_modules/commander/lib/command.js"(exports2) {
1190
1190
  var EventEmitter = require("node:events").EventEmitter;
1191
1191
  var childProcess = require("node:child_process");
1192
- var path8 = require("node:path");
1193
- var fs8 = require("node:fs");
1192
+ var path9 = require("node:path");
1193
+ var fs9 = require("node:fs");
1194
1194
  var process2 = require("node:process");
1195
1195
  var { Argument: Argument2, humanReadableArgName } = require_argument();
1196
1196
  var { CommanderError: CommanderError2 } = require_error();
@@ -2184,7 +2184,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
2184
2184
  * @param {string} subcommandName
2185
2185
  */
2186
2186
  _checkForMissingExecutable(executableFile, executableDir, subcommandName) {
2187
- if (fs8.existsSync(executableFile)) return;
2187
+ if (fs9.existsSync(executableFile)) return;
2188
2188
  const executableDirMessage = executableDir ? `searched for local subcommand relative to directory '${executableDir}'` : "no directory for search for local subcommand, use .executableDir() to supply a custom directory";
2189
2189
  const executableMissing = `'${executableFile}' does not exist
2190
2190
  - if '${subcommandName}' is not meant to be an executable command, remove description parameter from '.command()' and use '.description()' instead
@@ -2202,11 +2202,11 @@ Expecting one of '${allowedValues.join("', '")}'`);
2202
2202
  let launchWithNode = false;
2203
2203
  const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
2204
2204
  function findFile(baseDir, baseName) {
2205
- const localBin = path8.resolve(baseDir, baseName);
2206
- if (fs8.existsSync(localBin)) return localBin;
2207
- if (sourceExt.includes(path8.extname(baseName))) return void 0;
2205
+ const localBin = path9.resolve(baseDir, baseName);
2206
+ if (fs9.existsSync(localBin)) return localBin;
2207
+ if (sourceExt.includes(path9.extname(baseName))) return void 0;
2208
2208
  const foundExt = sourceExt.find(
2209
- (ext) => fs8.existsSync(`${localBin}${ext}`)
2209
+ (ext) => fs9.existsSync(`${localBin}${ext}`)
2210
2210
  );
2211
2211
  if (foundExt) return `${localBin}${foundExt}`;
2212
2212
  return void 0;
@@ -2218,21 +2218,21 @@ Expecting one of '${allowedValues.join("', '")}'`);
2218
2218
  if (this._scriptPath) {
2219
2219
  let resolvedScriptPath;
2220
2220
  try {
2221
- resolvedScriptPath = fs8.realpathSync(this._scriptPath);
2221
+ resolvedScriptPath = fs9.realpathSync(this._scriptPath);
2222
2222
  } catch {
2223
2223
  resolvedScriptPath = this._scriptPath;
2224
2224
  }
2225
- executableDir = path8.resolve(
2226
- path8.dirname(resolvedScriptPath),
2225
+ executableDir = path9.resolve(
2226
+ path9.dirname(resolvedScriptPath),
2227
2227
  executableDir
2228
2228
  );
2229
2229
  }
2230
2230
  if (executableDir) {
2231
2231
  let localFile = findFile(executableDir, executableFile);
2232
2232
  if (!localFile && !subcommand._executableFile && this._scriptPath) {
2233
- const legacyName = path8.basename(
2233
+ const legacyName = path9.basename(
2234
2234
  this._scriptPath,
2235
- path8.extname(this._scriptPath)
2235
+ path9.extname(this._scriptPath)
2236
2236
  );
2237
2237
  if (legacyName !== this._name) {
2238
2238
  localFile = findFile(
@@ -2243,7 +2243,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
2243
2243
  }
2244
2244
  executableFile = localFile || executableFile;
2245
2245
  }
2246
- launchWithNode = sourceExt.includes(path8.extname(executableFile));
2246
+ launchWithNode = sourceExt.includes(path9.extname(executableFile));
2247
2247
  let proc;
2248
2248
  if (process2.platform !== "win32") {
2249
2249
  if (launchWithNode) {
@@ -3158,7 +3158,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
3158
3158
  * @return {Command}
3159
3159
  */
3160
3160
  nameFromFilename(filename) {
3161
- this._name = path8.basename(filename, path8.extname(filename));
3161
+ this._name = path9.basename(filename, path9.extname(filename));
3162
3162
  return this;
3163
3163
  }
3164
3164
  /**
@@ -3172,9 +3172,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
3172
3172
  * @param {string} [path]
3173
3173
  * @return {(string|null|Command)}
3174
3174
  */
3175
- executableDir(path9) {
3176
- if (path9 === void 0) return this._executableDir;
3177
- this._executableDir = path9;
3175
+ executableDir(path10) {
3176
+ if (path10 === void 0) return this._executableDir;
3177
+ this._executableDir = path10;
3178
3178
  return this;
3179
3179
  }
3180
3180
  /**
@@ -6823,7 +6823,7 @@ var require_parse = __commonJS({
6823
6823
  var require_gray_matter = __commonJS({
6824
6824
  "node_modules/gray-matter/index.js"(exports2, module2) {
6825
6825
  "use strict";
6826
- var fs8 = require("fs");
6826
+ var fs9 = require("fs");
6827
6827
  var sections = require_section_matter();
6828
6828
  var defaults = require_defaults();
6829
6829
  var stringify = require_stringify();
@@ -6907,7 +6907,7 @@ var require_gray_matter = __commonJS({
6907
6907
  return stringify(file, data, options3);
6908
6908
  };
6909
6909
  matter2.read = function(filepath, options3) {
6910
- const str2 = fs8.readFileSync(filepath, "utf8");
6910
+ const str2 = fs9.readFileSync(filepath, "utf8");
6911
6911
  const file = matter2(str2, options3);
6912
6912
  file.path = filepath;
6913
6913
  return file;
@@ -6936,8 +6936,8 @@ var require_gray_matter = __commonJS({
6936
6936
  });
6937
6937
 
6938
6938
  // src/index.ts
6939
- var import_node_fs7 = __toESM(require("node:fs"));
6940
- var import_node_path7 = __toESM(require("node:path"));
6939
+ var import_node_fs8 = __toESM(require("node:fs"));
6940
+ var import_node_path8 = __toESM(require("node:path"));
6941
6941
 
6942
6942
  // node_modules/commander/esm.mjs
6943
6943
  var import_index = __toESM(require_commander(), 1);
@@ -6963,8 +6963,10 @@ var WORKSPACE_INTERNAL_DIR = ".company-agent-wiki";
6963
6963
  var WORKSPACE_CONFIG_FILE = "workspace.json";
6964
6964
  var INDEX_DB_FILE = "index.sqlite";
6965
6965
  var INDEX_MANIFEST_FILE = "index-manifest.json";
6966
+ var GLOBAL_REGISTRY_DIR_NAME = "company-agent-wiki";
6967
+ var GLOBAL_REGISTRY_FILE = "workspaces.json";
6966
6968
  var WORKSPACE_LAYOUT_VERSION = 1;
6967
- var CLI_SCHEMA_VERSION = "2026-04-12";
6969
+ var CLI_SCHEMA_VERSION = "2026-04-13";
6968
6970
  var INDEX_SCHEMA_VERSION = 1;
6969
6971
  var DEFAULT_MANAGED_ROOT_ID = "canonical";
6970
6972
  var DEFAULT_MANAGED_ROOT_PATH = "knowledge/canonical";
@@ -6979,6 +6981,7 @@ var EXIT_CODES = {
6979
6981
  notFound: 6,
6980
6982
  git: 7,
6981
6983
  sqliteLocked: 8,
6984
+ workspaceBusy: 9,
6982
6985
  runtime: 10
6983
6986
  };
6984
6987
 
@@ -7139,8 +7142,8 @@ function getGitDiff(filePath, baseRef, compareRef) {
7139
7142
  }
7140
7143
 
7141
7144
  // src/lib/indexer.ts
7142
- var import_node_fs4 = __toESM(require("node:fs"));
7143
- var import_node_path5 = __toESM(require("node:path"));
7145
+ var import_node_fs5 = __toESM(require("node:fs"));
7146
+ var import_node_path6 = __toESM(require("node:path"));
7144
7147
  var import_better_sqlite3 = __toESM(require("better-sqlite3"));
7145
7148
 
7146
7149
  // src/lib/fs-utils.ts
@@ -7340,8 +7343,29 @@ var import_node_path4 = __toESM(require("node:path"));
7340
7343
  function getDefaultCodexHome() {
7341
7344
  return process.env.CODEX_HOME || import_node_path4.default.join(import_node_os.default.homedir(), ".codex");
7342
7345
  }
7346
+ function getGlobalRegistryDir() {
7347
+ const explicit = process.env.COMPANY_AGENT_WIKI_CONFIG_HOME;
7348
+ if (explicit?.trim()) {
7349
+ return import_node_path4.default.resolve(explicit);
7350
+ }
7351
+ if (process.env.VITEST || process.env.NODE_ENV === "test") {
7352
+ return import_node_path4.default.join(import_node_os.default.tmpdir(), GLOBAL_REGISTRY_DIR_NAME, "vitest");
7353
+ }
7354
+ if (process.platform === "darwin") {
7355
+ return import_node_path4.default.join(import_node_os.default.homedir(), "Library", "Application Support", GLOBAL_REGISTRY_DIR_NAME);
7356
+ }
7357
+ if (process.platform === "win32") {
7358
+ const roaming = process.env.APPDATA || import_node_path4.default.join(import_node_os.default.homedir(), "AppData", "Roaming");
7359
+ return import_node_path4.default.join(roaming, GLOBAL_REGISTRY_DIR_NAME);
7360
+ }
7361
+ const xdgConfig = process.env.XDG_CONFIG_HOME || import_node_path4.default.join(import_node_os.default.homedir(), ".config");
7362
+ return import_node_path4.default.join(xdgConfig, GLOBAL_REGISTRY_DIR_NAME);
7363
+ }
7364
+ function getGlobalRegistryPath() {
7365
+ return import_node_path4.default.join(getGlobalRegistryDir(), GLOBAL_REGISTRY_FILE);
7366
+ }
7343
7367
  function resolveWorkspacePaths(workspaceRoot) {
7344
- const absoluteRoot = import_node_path4.default.resolve(workspaceRoot);
7368
+ const absoluteRoot = normalizeWorkspaceRootPath(workspaceRoot);
7345
7369
  const internalDir = import_node_path4.default.join(absoluteRoot, WORKSPACE_INTERNAL_DIR);
7346
7370
  return {
7347
7371
  workspaceRoot: absoluteRoot,
@@ -7367,6 +7391,160 @@ function detectWorkspaceRoot(startDir = process.cwd()) {
7367
7391
  current = parent;
7368
7392
  }
7369
7393
  }
7394
+ function createDefaultGlobalRegistry() {
7395
+ return {
7396
+ schemaVersion: WORKSPACE_LAYOUT_VERSION,
7397
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
7398
+ workspaces: []
7399
+ };
7400
+ }
7401
+ function normalizeWorkspaceRootPath(candidatePath) {
7402
+ const resolved = import_node_path4.default.resolve(candidatePath);
7403
+ try {
7404
+ return import_node_fs3.default.realpathSync.native ? import_node_fs3.default.realpathSync.native(resolved) : import_node_fs3.default.realpathSync(resolved);
7405
+ } catch {
7406
+ return resolved;
7407
+ }
7408
+ }
7409
+ function loadGlobalWorkspaceRegistry() {
7410
+ const registryPath = getGlobalRegistryPath();
7411
+ if (!fileExists(registryPath)) {
7412
+ return createDefaultGlobalRegistry();
7413
+ }
7414
+ const raw = readJsonFile(registryPath);
7415
+ const registry = createDefaultGlobalRegistry();
7416
+ registry.schemaVersion = raw.schemaVersion || registry.schemaVersion;
7417
+ registry.updatedAt = raw.updatedAt || registry.updatedAt;
7418
+ const deduped = /* @__PURE__ */ new Map();
7419
+ for (const entry of raw.workspaces || []) {
7420
+ const normalizedPath = normalizeWorkspaceRootPath(entry.path);
7421
+ if (!fileExists(resolveWorkspacePaths(normalizedPath).configPath)) {
7422
+ continue;
7423
+ }
7424
+ const normalizedEntry = {
7425
+ workspaceId: entry.workspaceId,
7426
+ path: normalizedPath,
7427
+ label: entry.label || import_node_path4.default.basename(normalizedPath),
7428
+ registeredAt: entry.registeredAt,
7429
+ lastUsedAt: entry.lastUsedAt,
7430
+ source: entry.source
7431
+ };
7432
+ const existing = deduped.get(normalizedPath);
7433
+ if (!existing || existing.lastUsedAt < normalizedEntry.lastUsedAt) {
7434
+ deduped.set(normalizedPath, normalizedEntry);
7435
+ }
7436
+ }
7437
+ registry.workspaces = Array.from(deduped.values()).sort((left, right) => left.label.localeCompare(right.label));
7438
+ if (raw.defaultWorkspace) {
7439
+ const normalizedDefault = normalizeWorkspaceRootPath(raw.defaultWorkspace);
7440
+ if (deduped.has(normalizedDefault)) {
7441
+ registry.defaultWorkspace = normalizedDefault;
7442
+ }
7443
+ }
7444
+ const serializedRaw = JSON.stringify(raw);
7445
+ const serializedNormalized = JSON.stringify(registry);
7446
+ if (serializedRaw !== serializedNormalized) {
7447
+ saveGlobalWorkspaceRegistry(registry);
7448
+ }
7449
+ return registry;
7450
+ }
7451
+ function saveGlobalWorkspaceRegistry(registry) {
7452
+ writeJsonAtomic(getGlobalRegistryPath(), registry);
7453
+ }
7454
+ function buildRegisteredWorkspace(workspaceRoot, source) {
7455
+ const resolvedRoot = normalizeWorkspaceRootPath(workspaceRoot);
7456
+ const config = loadWorkspaceConfig(resolvedRoot);
7457
+ const now = (/* @__PURE__ */ new Date()).toISOString();
7458
+ return {
7459
+ workspaceId: config.workspaceId,
7460
+ path: resolvedRoot,
7461
+ label: import_node_path4.default.basename(resolvedRoot),
7462
+ registeredAt: now,
7463
+ lastUsedAt: now,
7464
+ source
7465
+ };
7466
+ }
7467
+ function registerWorkspaceGlobally(workspaceRoot, options3) {
7468
+ const resolvedRoot = import_node_path4.default.resolve(workspaceRoot);
7469
+ const nextEntry = buildRegisteredWorkspace(resolvedRoot, options3?.source || "manual");
7470
+ const registry = loadGlobalWorkspaceRegistry();
7471
+ const existing = registry.workspaces.find((item) => item.path === resolvedRoot);
7472
+ if (existing) {
7473
+ existing.workspaceId = nextEntry.workspaceId;
7474
+ existing.label = nextEntry.label;
7475
+ existing.lastUsedAt = nextEntry.lastUsedAt;
7476
+ existing.source = nextEntry.source;
7477
+ } else {
7478
+ registry.workspaces.push(nextEntry);
7479
+ }
7480
+ if (options3?.setDefault || !registry.defaultWorkspace) {
7481
+ registry.defaultWorkspace = resolvedRoot;
7482
+ }
7483
+ registry.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
7484
+ saveGlobalWorkspaceRegistry(registry);
7485
+ return registry.workspaces.find((item) => item.path === resolvedRoot) || nextEntry;
7486
+ }
7487
+ function rememberWorkspaceGlobally(workspaceRoot, options3) {
7488
+ const configPath = resolveWorkspacePaths(workspaceRoot).configPath;
7489
+ if (!fileExists(configPath)) {
7490
+ return void 0;
7491
+ }
7492
+ try {
7493
+ return registerWorkspaceGlobally(workspaceRoot, options3);
7494
+ } catch {
7495
+ return void 0;
7496
+ }
7497
+ }
7498
+ function listRegisteredWorkspaces() {
7499
+ const registry = loadGlobalWorkspaceRegistry();
7500
+ return {
7501
+ registryPath: getGlobalRegistryPath(),
7502
+ defaultWorkspace: registry.defaultWorkspace,
7503
+ workspaces: registry.workspaces.map((item) => ({
7504
+ ...item,
7505
+ exists: fileExists(resolveWorkspacePaths(item.path).configPath)
7506
+ }))
7507
+ };
7508
+ }
7509
+ function resolveWorkspaceSelection(startDir = process.cwd()) {
7510
+ const registryPath = getGlobalRegistryPath();
7511
+ const cwdWorkspace = detectWorkspaceRoot(startDir);
7512
+ if (cwdWorkspace) {
7513
+ rememberWorkspaceGlobally(cwdWorkspace, { setDefault: true, source: "detected" });
7514
+ return {
7515
+ workspaceRoot: cwdWorkspace,
7516
+ source: "cwd",
7517
+ registryPath,
7518
+ defaultWorkspace: loadGlobalWorkspaceRegistry().defaultWorkspace
7519
+ };
7520
+ }
7521
+ const registry = loadGlobalWorkspaceRegistry();
7522
+ const existingWorkspaces = registry.workspaces.filter((item) => fileExists(resolveWorkspacePaths(item.path).configPath));
7523
+ const defaultWorkspace = registry.defaultWorkspace;
7524
+ if (defaultWorkspace && fileExists(resolveWorkspacePaths(defaultWorkspace).configPath)) {
7525
+ rememberWorkspaceGlobally(defaultWorkspace, { setDefault: true, source: "runtime" });
7526
+ return {
7527
+ workspaceRoot: defaultWorkspace,
7528
+ source: "global-default",
7529
+ registryPath,
7530
+ defaultWorkspace
7531
+ };
7532
+ }
7533
+ if (existingWorkspaces.length === 1) {
7534
+ const onlyWorkspace = existingWorkspaces[0].path;
7535
+ rememberWorkspaceGlobally(onlyWorkspace, { setDefault: true, source: "runtime" });
7536
+ return {
7537
+ workspaceRoot: onlyWorkspace,
7538
+ source: "single-registered",
7539
+ registryPath,
7540
+ defaultWorkspace: onlyWorkspace
7541
+ };
7542
+ }
7543
+ return {
7544
+ registryPath,
7545
+ defaultWorkspace
7546
+ };
7547
+ }
7370
7548
  function templateWorkspaceReadme() {
7371
7549
  return `# Private Company Knowledge Workspace
7372
7550
 
@@ -7734,6 +7912,7 @@ function setupWorkspace(options3) {
7734
7912
  }
7735
7913
  }
7736
7914
  }
7915
+ registerWorkspaceGlobally(paths.workspaceRoot, { setDefault: true, source: "setup" });
7737
7916
  return {
7738
7917
  workspaceRoot: paths.workspaceRoot,
7739
7918
  configPath: paths.configPath,
@@ -7743,7 +7922,8 @@ function setupWorkspace(options3) {
7743
7922
  `company-agent-wiki-cli doctor --workspace ${paths.workspaceRoot} --json`,
7744
7923
  `company-agent-wiki-cli index rebuild --workspace ${paths.workspaceRoot} --json`,
7745
7924
  `company-agent-wiki-cli verify --workspace ${paths.workspaceRoot} --json`,
7746
- `company-agent-wiki-cli serve --workspace ${paths.workspaceRoot} --port 4187`
7925
+ `company-agent-wiki-cli serve --workspace ${paths.workspaceRoot} --port 4187`,
7926
+ "Other agents can now discover this workspace automatically via the global workspace registry."
7747
7927
  ]
7748
7928
  };
7749
7929
  }
@@ -7776,6 +7956,7 @@ function addRoot(workspaceRoot, rootDefinition) {
7776
7956
  };
7777
7957
  config.roots.push(root);
7778
7958
  saveWorkspaceConfig(workspaceRoot, config);
7959
+ rememberWorkspaceGlobally(workspaceRoot, { setDefault: true, source: "runtime" });
7779
7960
  return root;
7780
7961
  }
7781
7962
  function listRoots(workspaceRoot) {
@@ -7797,6 +7978,8 @@ function doctor(workspaceRoot) {
7797
7978
  const codexBinDir = import_node_path4.default.join(codexHome, "bin");
7798
7979
  const codexShimPath = import_node_path4.default.join(codexBinDir, CLI_NAME);
7799
7980
  const pathEntries = (process.env.PATH || "").split(import_node_path4.default.delimiter).filter(Boolean);
7981
+ const registryPath = getGlobalRegistryPath();
7982
+ const registry = loadGlobalWorkspaceRegistry();
7800
7983
  checks.push({
7801
7984
  name: "workspace-root",
7802
7985
  ok: isDirectory(paths.workspaceRoot),
@@ -7822,6 +8005,16 @@ function doctor(workspaceRoot) {
7822
8005
  ok: pathEntries.includes(codexBinDir),
7823
8006
  message: pathEntries.includes(codexBinDir) ? `Codex bin directory is available in PATH: ${codexBinDir}` : `Codex bin directory is not in PATH: ${codexBinDir}`
7824
8007
  });
8008
+ checks.push({
8009
+ name: "global-workspace-registry",
8010
+ ok: fileExists(registryPath),
8011
+ message: fileExists(registryPath) ? `Global workspace registry found: ${registryPath}` : `Global workspace registry missing: ${registryPath}`
8012
+ });
8013
+ checks.push({
8014
+ name: "global-default-workspace",
8015
+ ok: typeof registry.defaultWorkspace === "string" && fileExists(resolveWorkspacePaths(registry.defaultWorkspace).configPath),
8016
+ message: typeof registry.defaultWorkspace === "string" ? `Global default workspace: ${registry.defaultWorkspace}` : "No global default workspace registered yet."
8017
+ });
7825
8018
  if (fileExists(paths.configPath)) {
7826
8019
  const config = loadWorkspaceConfig(workspaceRoot);
7827
8020
  for (const root of config.roots) {
@@ -7849,6 +8042,121 @@ function doctor(workspaceRoot) {
7849
8042
  };
7850
8043
  }
7851
8044
 
8045
+ // src/lib/write-lock.ts
8046
+ var import_node_fs4 = __toESM(require("node:fs"));
8047
+ var import_node_path5 = __toESM(require("node:path"));
8048
+ var LOCK_FILE_NAME = "write.lock";
8049
+ var LOCK_WAIT_TIMEOUT_MS = 6e4;
8050
+ var LOCK_POLL_INTERVAL_MS = 125;
8051
+ var LOCK_STALE_AFTER_MS = 10 * 6e4;
8052
+ function sleepMs(durationMs) {
8053
+ const shared = new SharedArrayBuffer(4);
8054
+ const array = new Int32Array(shared);
8055
+ Atomics.wait(array, 0, 0, durationMs);
8056
+ }
8057
+ function getLockPath(workspaceRoot) {
8058
+ return import_node_path5.default.join(import_node_path5.default.resolve(workspaceRoot), WORKSPACE_INTERNAL_DIR, LOCK_FILE_NAME);
8059
+ }
8060
+ function readLockPayload(lockPath) {
8061
+ if (!fileExists(lockPath)) {
8062
+ return void 0;
8063
+ }
8064
+ try {
8065
+ return readJsonFile(lockPath);
8066
+ } catch {
8067
+ return void 0;
8068
+ }
8069
+ }
8070
+ function isProcessAlive(pid) {
8071
+ if (!pid || pid <= 0) {
8072
+ return false;
8073
+ }
8074
+ try {
8075
+ process.kill(pid, 0);
8076
+ return true;
8077
+ } catch (error) {
8078
+ const code = error.code;
8079
+ return code !== "ESRCH";
8080
+ }
8081
+ }
8082
+ function canBreakLock(payload) {
8083
+ if (!payload) {
8084
+ return true;
8085
+ }
8086
+ const acquiredAt = Date.parse(payload.acquiredAt);
8087
+ const ageMs = Number.isNaN(acquiredAt) ? Number.POSITIVE_INFINITY : Date.now() - acquiredAt;
8088
+ if (ageMs > LOCK_STALE_AFTER_MS) {
8089
+ return true;
8090
+ }
8091
+ return !isProcessAlive(payload.pid);
8092
+ }
8093
+ function writeLockPayload(lockPath, payload) {
8094
+ const tempPath = `${lockPath}.${payload.token}.tmp`;
8095
+ writeJsonAtomic(tempPath, payload);
8096
+ import_node_fs4.default.renameSync(tempPath, lockPath);
8097
+ }
8098
+ function createLockPayload(workspaceRoot, reason) {
8099
+ return {
8100
+ token: newBuildId(),
8101
+ pid: process.pid,
8102
+ reason,
8103
+ workspaceRoot: import_node_path5.default.resolve(workspaceRoot),
8104
+ acquiredAt: (/* @__PURE__ */ new Date()).toISOString()
8105
+ };
8106
+ }
8107
+ function withWorkspaceWriteLock(workspaceRoot, reason, callback, options3) {
8108
+ const lockPath = getLockPath(workspaceRoot);
8109
+ const timeoutMs = options3?.timeoutMs ?? LOCK_WAIT_TIMEOUT_MS;
8110
+ const deadline = Date.now() + timeoutMs;
8111
+ const payload = createLockPayload(workspaceRoot, reason);
8112
+ ensureDir(import_node_path5.default.dirname(lockPath));
8113
+ while (true) {
8114
+ try {
8115
+ const fileDescriptor = import_node_fs4.default.openSync(lockPath, "wx");
8116
+ import_node_fs4.default.closeSync(fileDescriptor);
8117
+ writeLockPayload(lockPath, payload);
8118
+ break;
8119
+ } catch (error) {
8120
+ const code = error.code;
8121
+ if (code !== "EEXIST") {
8122
+ throw error;
8123
+ }
8124
+ const existing = readLockPayload(lockPath);
8125
+ if (canBreakLock(existing)) {
8126
+ import_node_fs4.default.rmSync(lockPath, { force: true });
8127
+ continue;
8128
+ }
8129
+ if (Date.now() >= deadline) {
8130
+ throw new CliError(
8131
+ "WORKSPACE_BUSY",
8132
+ "Another write operation is already running for this workspace.",
8133
+ EXIT_CODES.workspaceBusy,
8134
+ {
8135
+ hint: "Parallel reads should continue to work. For writes or auto-rebuilds, wait for the active write to finish and retry.",
8136
+ details: {
8137
+ lockPath,
8138
+ holder: existing
8139
+ }
8140
+ }
8141
+ );
8142
+ }
8143
+ sleepMs(LOCK_POLL_INTERVAL_MS);
8144
+ }
8145
+ }
8146
+ const artificialDelay = Number(process.env.COMPANY_AGENT_WIKI_TEST_WRITE_LOCK_DELAY_MS || "0");
8147
+ if (Number.isFinite(artificialDelay) && artificialDelay > 0) {
8148
+ sleepMs(artificialDelay);
8149
+ }
8150
+ try {
8151
+ return callback();
8152
+ } finally {
8153
+ const current = readLockPayload(lockPath);
8154
+ if (current && current.token === payload.token) {
8155
+ import_node_fs4.default.rmSync(lockPath, { force: true });
8156
+ }
8157
+ }
8158
+ }
8159
+
7852
8160
  // src/lib/indexer.ts
7853
8161
  function openDatabase(databasePath, options3) {
7854
8162
  try {
@@ -7857,6 +8165,12 @@ function openDatabase(databasePath, options3) {
7857
8165
  options3?.readonly ? { readonly: true, fileMustExist: true } : void 0
7858
8166
  );
7859
8167
  database.pragma("busy_timeout = 5000");
8168
+ if (options3?.readonly) {
8169
+ database.pragma("query_only = 1");
8170
+ } else {
8171
+ database.pragma("journal_mode = WAL");
8172
+ database.pragma("synchronous = NORMAL");
8173
+ }
7860
8174
  return database;
7861
8175
  } catch (error) {
7862
8176
  throw coerceCliError(error, {
@@ -7875,11 +8189,11 @@ function closeDatabaseQuietly(database) {
7875
8189
  }
7876
8190
  function throwKnownDatabaseError(error, workspaceRoot) {
7877
8191
  const cliError = coerceCliError(error, {
7878
- sqliteLockHint: `Retry in a moment, serialize parallel CLI reads against ${import_node_path5.default.resolve(
8192
+ sqliteLockHint: `Retry in a moment, serialize parallel CLI reads against ${import_node_path6.default.resolve(
7879
8193
  workspaceRoot
7880
8194
  )}, or rerun with --auto-rebuild after the current write finishes.`,
7881
8195
  sqliteLockDetails: {
7882
- workspaceRoot: import_node_path5.default.resolve(workspaceRoot)
8196
+ workspaceRoot: import_node_path6.default.resolve(workspaceRoot)
7883
8197
  }
7884
8198
  });
7885
8199
  if (cliError) {
@@ -7961,8 +8275,8 @@ function collectRootSnapshot(rootId, rootPath, kind) {
7961
8275
  const entries = [];
7962
8276
  let latestMtimeMs = 0;
7963
8277
  for (const filePath of markdownFiles) {
7964
- const stats = import_node_fs4.default.statSync(filePath);
7965
- const relPath = import_node_path5.default.relative(rootPath, filePath);
8278
+ const stats = import_node_fs5.default.statSync(filePath);
8279
+ const relPath = import_node_path6.default.relative(rootPath, filePath);
7966
8280
  latestMtimeMs = Math.max(latestMtimeMs, Math.trunc(stats.mtimeMs));
7967
8281
  entries.push(`${relPath}|${stats.size}|${Math.trunc(stats.mtimeMs)}`);
7968
8282
  }
@@ -8150,13 +8464,13 @@ function matchesFilters(metadata, filters) {
8150
8464
  }
8151
8465
  return true;
8152
8466
  }
8153
- function rebuildIndex(workspaceRoot) {
8467
+ function rebuildIndexUnlocked(workspaceRoot) {
8154
8468
  const config = loadWorkspaceConfig(workspaceRoot);
8155
8469
  const paths = resolveWorkspacePaths(workspaceRoot);
8156
- const tempDbPath = `${paths.indexDbPath}.next`;
8470
+ const tempDbPath = `${paths.indexDbPath}.${process.pid}.${Date.now()}.${newBuildId()}.next`;
8157
8471
  const indexedAt = (/* @__PURE__ */ new Date()).toISOString();
8158
8472
  if (fileExists(tempDbPath)) {
8159
- import_node_fs4.default.unlinkSync(tempDbPath);
8473
+ import_node_fs5.default.unlinkSync(tempDbPath);
8160
8474
  }
8161
8475
  let database;
8162
8476
  try {
@@ -8186,9 +8500,9 @@ function rebuildIndex(workspaceRoot) {
8186
8500
  insertRoot(database, snapshot, indexedAt);
8187
8501
  const markdownFiles = walkMarkdownFiles(rootPath);
8188
8502
  for (const filePath of markdownFiles) {
8189
- const rawContent = import_node_fs4.default.readFileSync(filePath, "utf8");
8190
- const stats = import_node_fs4.default.statSync(filePath);
8191
- const relPath = import_node_path5.default.relative(rootPath, filePath);
8503
+ const rawContent = import_node_fs5.default.readFileSync(filePath, "utf8");
8504
+ const stats = import_node_fs5.default.statSync(filePath);
8505
+ const relPath = import_node_path6.default.relative(rootPath, filePath);
8192
8506
  const parsed = parseMarkdownDocument(filePath, relPath, root.id, rawContent, Math.trunc(stats.mtimeMs));
8193
8507
  insertDocument(database, parsed.document);
8194
8508
  insertSections(database, parsed.document, parsed.sections);
@@ -8199,8 +8513,8 @@ function rebuildIndex(workspaceRoot) {
8199
8513
  database.exec("INSERT INTO sections_fts(sections_fts) VALUES('optimize');");
8200
8514
  closeDatabaseQuietly(database);
8201
8515
  database = void 0;
8202
- replaceFileAtomic(paths.indexDbPath, import_node_fs4.default.readFileSync(tempDbPath));
8203
- import_node_fs4.default.unlinkSync(tempDbPath);
8516
+ replaceFileAtomic(paths.indexDbPath, import_node_fs5.default.readFileSync(tempDbPath));
8517
+ import_node_fs5.default.unlinkSync(tempDbPath);
8204
8518
  const manifest = {
8205
8519
  buildId,
8206
8520
  schemaVersion: INDEX_SCHEMA_VERSION,
@@ -8215,11 +8529,14 @@ function rebuildIndex(workspaceRoot) {
8215
8529
  } catch (error) {
8216
8530
  closeDatabaseQuietly(database);
8217
8531
  if (fileExists(tempDbPath)) {
8218
- import_node_fs4.default.rmSync(tempDbPath, { force: true });
8532
+ import_node_fs5.default.rmSync(tempDbPath, { force: true });
8219
8533
  }
8220
8534
  throwKnownDatabaseError(error, workspaceRoot);
8221
8535
  }
8222
8536
  }
8537
+ function rebuildIndex(workspaceRoot) {
8538
+ return withWorkspaceWriteLock(workspaceRoot, "index-rebuild", () => rebuildIndexUnlocked(workspaceRoot));
8539
+ }
8223
8540
  function readManifest(workspaceRoot) {
8224
8541
  const paths = resolveWorkspacePaths(workspaceRoot);
8225
8542
  if (!fileExists(paths.indexManifestPath)) {
@@ -8297,7 +8614,7 @@ function requireFreshIndex(workspaceRoot, options3) {
8297
8614
  "The workspace has not been indexed yet.",
8298
8615
  EXIT_CODES.indexMissing,
8299
8616
  {
8300
- hint: verification.hint || `Run: company-agent-wiki-cli index rebuild --workspace ${import_node_path5.default.resolve(workspaceRoot)}`
8617
+ hint: verification.hint || `Run: company-agent-wiki-cli index rebuild --workspace ${import_node_path6.default.resolve(workspaceRoot)}`
8301
8618
  }
8302
8619
  );
8303
8620
  }
@@ -8310,7 +8627,7 @@ function requireFreshIndex(workspaceRoot, options3) {
8310
8627
  "The indexed snapshot no longer matches the current roots.",
8311
8628
  EXIT_CODES.indexStale,
8312
8629
  {
8313
- hint: `Run: company-agent-wiki-cli index rebuild --workspace ${import_node_path5.default.resolve(workspaceRoot)}`,
8630
+ hint: `Run: company-agent-wiki-cli index rebuild --workspace ${import_node_path6.default.resolve(workspaceRoot)}`,
8314
8631
  details: verification.roots.filter((root) => !root.ok)
8315
8632
  }
8316
8633
  );
@@ -8619,8 +8936,8 @@ function getDocumentHeadings(workspaceRoot, docId, options3) {
8619
8936
  }
8620
8937
 
8621
8938
  // src/lib/onboarding.ts
8622
- var import_node_fs5 = __toESM(require("node:fs"));
8623
- var import_node_path6 = __toESM(require("node:path"));
8939
+ var import_node_fs6 = __toESM(require("node:fs"));
8940
+ var import_node_path7 = __toESM(require("node:path"));
8624
8941
  var COMPANY_ONBOARDING_DE_V1 = {
8625
8942
  profileId: "de-company-v1",
8626
8943
  locale: "de-DE",
@@ -9212,8 +9529,8 @@ function ensureKnownAnswerKeys(payload) {
9212
9529
  }
9213
9530
  }
9214
9531
  function isPathInsideWorkspace2(workspaceRoot, candidatePath) {
9215
- const relative = import_node_path6.default.relative(import_node_path6.default.resolve(workspaceRoot), import_node_path6.default.resolve(candidatePath));
9216
- return relative === "" || !relative.startsWith("..") && !import_node_path6.default.isAbsolute(relative);
9532
+ const relative = import_node_path7.default.relative(import_node_path7.default.resolve(workspaceRoot), import_node_path7.default.resolve(candidatePath));
9533
+ return relative === "" || !relative.startsWith("..") && !import_node_path7.default.isAbsolute(relative);
9217
9534
  }
9218
9535
  function resolveManagedRoot(workspaceRoot) {
9219
9536
  const config = loadWorkspaceConfig(workspaceRoot);
@@ -9236,12 +9553,12 @@ function resolveManagedRoot(workspaceRoot) {
9236
9553
  return resolvedPath;
9237
9554
  }
9238
9555
  function createDocument(workspaceRoot, managedRoot, relPath, id, title, type, tags, body, answeredAt, answeredBy) {
9239
- const absPath = import_node_path6.default.join(managedRoot, relPath);
9556
+ const absPath = import_node_path7.default.join(managedRoot, relPath);
9240
9557
  return {
9241
9558
  docId: id,
9242
9559
  title,
9243
9560
  absPath,
9244
- relPath: import_node_path6.default.relative(workspaceRoot, absPath),
9561
+ relPath: import_node_path7.default.relative(workspaceRoot, absPath),
9245
9562
  existed: false,
9246
9563
  content: `${renderFrontmatter({ id, title, type, tags, answeredAt, answeredBy })}${body.trimEnd()}
9247
9564
  `
@@ -9440,7 +9757,7 @@ function buildCompanyDocuments(workspaceRoot, normalized) {
9440
9757
  function loadCompanyOnboardingAnswers(answerFile) {
9441
9758
  const payload = readJsonFile(answerFile);
9442
9759
  ensureKnownAnswerKeys(payload);
9443
- const stats = import_node_fs5.default.statSync(answerFile);
9760
+ const stats = import_node_fs6.default.statSync(answerFile);
9444
9761
  const normalized = normalizeAnswers(payload, stats.mtime.toISOString());
9445
9762
  if (normalized.profileId !== COMPANY_ONBOARDING_DE_V1.profileId) {
9446
9763
  throw new CliError(
@@ -9464,7 +9781,7 @@ function previewCompanyOnboarding(workspaceRoot, answerFile) {
9464
9781
  );
9465
9782
  }
9466
9783
  for (const document of materialized.documents) {
9467
- document.existed = import_node_fs5.default.existsSync(document.absPath);
9784
+ document.existed = import_node_fs6.default.existsSync(document.absPath);
9468
9785
  }
9469
9786
  return {
9470
9787
  documents: materialized.documents,
@@ -9473,39 +9790,41 @@ function previewCompanyOnboarding(workspaceRoot, answerFile) {
9473
9790
  };
9474
9791
  }
9475
9792
  function applyCompanyOnboarding(options3) {
9476
- const workspaceRoot = import_node_path6.default.resolve(options3.workspaceRoot);
9477
- const answerFile = import_node_path6.default.resolve(options3.answerFile);
9793
+ const workspaceRoot = import_node_path7.default.resolve(options3.workspaceRoot);
9794
+ const answerFile = import_node_path7.default.resolve(options3.answerFile);
9478
9795
  const preview = previewCompanyOnboarding(workspaceRoot, answerFile);
9479
9796
  const warnings = [...preview.warnings];
9480
9797
  let indexBuildId;
9481
9798
  if (options3.execute) {
9482
- const seenPaths = /* @__PURE__ */ new Set();
9483
- for (const document of preview.documents) {
9484
- if (seenPaths.has(document.absPath)) {
9485
- throw new CliError(
9486
- "ONBOARDING_TARGET_CONFLICT",
9487
- `Multiple onboarding documents resolve to the same target path: ${document.relPath}`,
9488
- EXIT_CODES.validation
9489
- );
9490
- }
9491
- seenPaths.add(document.absPath);
9492
- if (document.existed && !options3.force) {
9493
- throw new CliError(
9494
- "ONBOARDING_TARGET_EXISTS",
9495
- `Target file already exists: ${document.relPath}`,
9496
- EXIT_CODES.validation,
9497
- { hint: "Use --force to overwrite generated onboarding files." }
9498
- );
9799
+ const manifest = withWorkspaceWriteLock(workspaceRoot, "onboarding-apply", () => {
9800
+ const seenPaths = /* @__PURE__ */ new Set();
9801
+ for (const document of preview.documents) {
9802
+ if (seenPaths.has(document.absPath)) {
9803
+ throw new CliError(
9804
+ "ONBOARDING_TARGET_CONFLICT",
9805
+ `Multiple onboarding documents resolve to the same target path: ${document.relPath}`,
9806
+ EXIT_CODES.validation
9807
+ );
9808
+ }
9809
+ seenPaths.add(document.absPath);
9810
+ if (document.existed && !options3.force) {
9811
+ throw new CliError(
9812
+ "ONBOARDING_TARGET_EXISTS",
9813
+ `Target file already exists: ${document.relPath}`,
9814
+ EXIT_CODES.validation,
9815
+ { hint: "Use --force to overwrite generated onboarding files." }
9816
+ );
9817
+ }
9499
9818
  }
9500
- }
9501
- for (const document of preview.documents) {
9502
- ensureDir(import_node_path6.default.dirname(document.absPath));
9503
- if (document.existed) {
9504
- warnings.push(`Overwriting existing file: ${document.relPath}`);
9819
+ for (const document of preview.documents) {
9820
+ ensureDir(import_node_path7.default.dirname(document.absPath));
9821
+ if (document.existed) {
9822
+ warnings.push(`Overwriting existing file: ${document.relPath}`);
9823
+ }
9824
+ replaceFileAtomic(document.absPath, document.content);
9505
9825
  }
9506
- replaceFileAtomic(document.absPath, document.content);
9507
- }
9508
- const manifest = rebuildIndex(workspaceRoot);
9826
+ return rebuildIndexUnlocked(workspaceRoot);
9827
+ });
9509
9828
  indexBuildId = manifest.buildId;
9510
9829
  }
9511
9830
  return {
@@ -9568,7 +9887,7 @@ function printJson(value) {
9568
9887
  }
9569
9888
 
9570
9889
  // src/lib/server.ts
9571
- var import_node_fs6 = __toESM(require("node:fs"));
9890
+ var import_node_fs7 = __toESM(require("node:fs"));
9572
9891
  var import_node_http = __toESM(require("node:http"));
9573
9892
  var import_node_url = require("node:url");
9574
9893
 
@@ -12027,6 +12346,7 @@ function getHttpStatusCode(error) {
12027
12346
  case "INDEX_STALE":
12028
12347
  return 409;
12029
12348
  case "SQLITE_LOCKED":
12349
+ case "WORKSPACE_BUSY":
12030
12350
  return 423;
12031
12351
  default:
12032
12352
  return 500;
@@ -12133,7 +12453,7 @@ function startServer(workspaceRoot, port, options3) {
12133
12453
  return;
12134
12454
  }
12135
12455
  const resolved = resolveDocumentById(workspaceRoot, docId, { autoRebuild: options3?.autoRebuild });
12136
- const rawMarkdown = import_node_fs6.default.readFileSync(resolved.absPath, "utf8");
12456
+ const rawMarkdown = import_node_fs7.default.readFileSync(resolved.absPath, "utf8");
12137
12457
  sendJson(response, {
12138
12458
  ok: true,
12139
12459
  data: {
@@ -12189,14 +12509,16 @@ function startServer(workspaceRoot, port, options3) {
12189
12509
  // src/index.ts
12190
12510
  function assertWorkspace(workspacePath) {
12191
12511
  if (workspacePath?.trim()) {
12192
- return import_node_path7.default.resolve(workspacePath);
12512
+ const resolved = import_node_path8.default.resolve(workspacePath);
12513
+ rememberWorkspaceGlobally(resolved, { setDefault: true, source: "runtime" });
12514
+ return resolved;
12193
12515
  }
12194
- const detected = detectWorkspaceRoot(process.cwd());
12195
- if (detected) {
12196
- return detected;
12516
+ const selection = resolveWorkspaceSelection(process.cwd());
12517
+ if (selection.workspaceRoot) {
12518
+ return selection.workspaceRoot;
12197
12519
  }
12198
12520
  throw new CliError("WORKSPACE_REQUIRED", "Missing --workspace option and no workspace was detected from the current directory.", EXIT_CODES.usage, {
12199
- hint: "Pass --workspace /absolute/path or run the command from a directory inside the private workspace."
12521
+ hint: `Pass --workspace /absolute/path, run the command from a directory inside the private workspace, or register one globally in ${getGlobalRegistryPath()}.`
12200
12522
  });
12201
12523
  }
12202
12524
  function printHumanChecks(checks) {
@@ -12281,8 +12603,10 @@ program2.command("about").description("Show CLI runtime metadata and common Code
12281
12603
  cliName: CLI_NAME,
12282
12604
  schemaVersion: CLI_SCHEMA_VERSION,
12283
12605
  codexHome,
12284
- codexShimPath: import_node_path7.default.join(codexHome, "bin", CLI_NAME),
12285
- cwdWorkspace: detectWorkspaceRoot(process.cwd()) || null
12606
+ codexShimPath: import_node_path8.default.join(codexHome, "bin", CLI_NAME),
12607
+ cwdWorkspace: detectWorkspaceRoot(process.cwd()) || null,
12608
+ globalRegistryPath: getGlobalRegistryPath(),
12609
+ resolvedWorkspace: resolveWorkspaceSelection(process.cwd())
12286
12610
  };
12287
12611
  if (options3.json) {
12288
12612
  printJson(envelope("about", data));
@@ -12298,10 +12622,101 @@ program2.command("about").description("Show CLI runtime metadata and common Code
12298
12622
  process.stdout.write(` detected workspace: ${data.cwdWorkspace}
12299
12623
  `);
12300
12624
  }
12625
+ if (data.resolvedWorkspace.workspaceRoot && data.resolvedWorkspace.source !== "cwd") {
12626
+ process.stdout.write(` resolved workspace: ${data.resolvedWorkspace.workspaceRoot} (${data.resolvedWorkspace.source})
12627
+ `);
12628
+ }
12629
+ process.stdout.write(` global registry: ${data.globalRegistryPath}
12630
+ `);
12631
+ });
12632
+ var workspace = new Command("workspace").description("Manage global workspace discovery");
12633
+ workspace.command("current").description("Show the currently resolved workspace and discovery source").option("--json", "Emit JSON output", false).action((options3) => {
12634
+ const selection = resolveWorkspaceSelection(process.cwd());
12635
+ const data = {
12636
+ workspaceRoot: selection.workspaceRoot || null,
12637
+ source: selection.source || null,
12638
+ registryPath: selection.registryPath,
12639
+ defaultWorkspace: selection.defaultWorkspace || null
12640
+ };
12641
+ if (options3.json) {
12642
+ printJson(envelope("workspace current", data));
12643
+ return;
12644
+ }
12645
+ if (data.workspaceRoot) {
12646
+ process.stdout.write(`Resolved workspace: ${data.workspaceRoot}
12647
+ `);
12648
+ process.stdout.write(`Source: ${data.source}
12649
+ `);
12650
+ } else {
12651
+ process.stdout.write("No workspace resolved.\n");
12652
+ }
12653
+ process.stdout.write(`Global registry: ${data.registryPath}
12654
+ `);
12655
+ if (data.defaultWorkspace) {
12656
+ process.stdout.write(`Default workspace: ${data.defaultWorkspace}
12657
+ `);
12658
+ }
12659
+ });
12660
+ workspace.command("list").description("List globally registered workspaces").option("--json", "Emit JSON output", false).action((options3) => {
12661
+ const result = listRegisteredWorkspaces();
12662
+ if (options3.json) {
12663
+ printJson(envelope("workspace list", result));
12664
+ return;
12665
+ }
12666
+ process.stdout.write(`Global registry: ${result.registryPath}
12667
+ `);
12668
+ if (result.workspaces.length === 0) {
12669
+ process.stdout.write("No registered workspaces.\n");
12670
+ return;
12671
+ }
12672
+ for (const item of result.workspaces) {
12673
+ const defaultMarker = result.defaultWorkspace === item.path ? " [default]" : "";
12674
+ const existsMarker = item.exists ? "" : " [missing]";
12675
+ process.stdout.write(`- ${item.label}${defaultMarker}${existsMarker}
12676
+ `);
12677
+ process.stdout.write(` ${item.path}
12678
+ `);
12679
+ }
12680
+ });
12681
+ workspace.command("register").description("Register an existing workspace globally for other agents").requiredOption("--workspace <path>", "Absolute or relative workspace path").option("--default", "Also mark this workspace as the global default", false).option("--json", "Emit JSON output", false).action((options3) => {
12682
+ const entry = registerWorkspaceGlobally(import_node_path8.default.resolve(options3.workspace), {
12683
+ setDefault: Boolean(options3.default),
12684
+ source: "manual"
12685
+ });
12686
+ const result = {
12687
+ registryPath: getGlobalRegistryPath(),
12688
+ entry
12689
+ };
12690
+ if (options3.json) {
12691
+ printJson(envelope("workspace register", result));
12692
+ return;
12693
+ }
12694
+ process.stdout.write(`Registered workspace: ${entry.path}
12695
+ `);
12696
+ if (options3.default) {
12697
+ process.stdout.write("This workspace is now the global default.\n");
12698
+ }
12699
+ });
12700
+ workspace.command("use").description("Set a registered workspace as the global default").requiredOption("--workspace <path>", "Absolute or relative workspace path").option("--json", "Emit JSON output", false).action((options3) => {
12701
+ const entry = registerWorkspaceGlobally(import_node_path8.default.resolve(options3.workspace), {
12702
+ setDefault: true,
12703
+ source: "manual"
12704
+ });
12705
+ const result = {
12706
+ registryPath: getGlobalRegistryPath(),
12707
+ defaultWorkspace: entry.path
12708
+ };
12709
+ if (options3.json) {
12710
+ printJson(envelope("workspace use", result));
12711
+ return;
12712
+ }
12713
+ process.stdout.write(`Global default workspace: ${entry.path}
12714
+ `);
12301
12715
  });
12716
+ program2.addCommand(workspace);
12302
12717
  program2.command("setup").description("Workspace setup commands").addCommand(
12303
12718
  new Command("workspace").requiredOption("--workspace <path>", "Absolute or relative workspace path").option("--git-init", "Initialize a local Git repository", false).option("--git-remote <url>", "Configure a Git remote URL").option("--no-starter-docs", "Skip creation of starter Markdown documents").option("--force", "Rewrite an existing scaffold", false).option("--json", "Emit JSON output", false).action((options3) => {
12304
- const workspaceRoot = import_node_path7.default.resolve(options3.workspace);
12719
+ const workspaceRoot = import_node_path8.default.resolve(options3.workspace);
12305
12720
  const result = setupWorkspace({
12306
12721
  workspaceRoot,
12307
12722
  gitInit: Boolean(options3.gitInit),
@@ -12555,8 +12970,8 @@ program2.command("read").option("--workspace <path>", "Workspace path. Optional
12555
12970
  const metadataResult = options3.docId ? getDocumentMetadataById(workspaceRoot, options3.docId, {
12556
12971
  autoRebuild: Boolean(options3.autoRebuild)
12557
12972
  }) : (() => {
12558
- const candidatePath = import_node_path7.default.isAbsolute(options3.path) ? options3.path : import_node_path7.default.join(workspaceRoot, options3.path);
12559
- return getDocumentMetadataByPath(workspaceRoot, import_node_path7.default.resolve(candidatePath), {
12973
+ const candidatePath = import_node_path8.default.isAbsolute(options3.path) ? options3.path : import_node_path8.default.join(workspaceRoot, options3.path);
12974
+ return getDocumentMetadataByPath(workspaceRoot, import_node_path8.default.resolve(candidatePath), {
12560
12975
  autoRebuild: Boolean(options3.autoRebuild)
12561
12976
  });
12562
12977
  })();
@@ -12584,7 +12999,7 @@ program2.command("read").option("--workspace <path>", "Workspace path. Optional
12584
12999
  }
12585
13000
  return;
12586
13001
  }
12587
- const rawMarkdown = import_node_fs7.default.readFileSync(metadataResult.metadata.absPath, "utf8");
13002
+ const rawMarkdown = import_node_fs8.default.readFileSync(metadataResult.metadata.absPath, "utf8");
12588
13003
  if (options3.json) {
12589
13004
  printJson(
12590
13005
  envelope(
@@ -12606,7 +13021,7 @@ program2.command("read").option("--workspace <path>", "Workspace path. Optional
12606
13021
  });
12607
13022
  program2.command("history").option("--workspace <path>", "Workspace path. Optional when current directory is already inside a workspace.").option("--doc-id <id>", "Indexed document identifier").option("--path <path>", "Absolute or workspace-relative document path").option("--limit <number>", "Maximum number of commits", "20").option("--json", "Emit JSON output", false).action((options3) => {
12608
13023
  const workspaceRoot = assertWorkspace(options3.workspace);
12609
- const resolvedPath = options3.docId ? resolveDocumentById(workspaceRoot, options3.docId).absPath : import_node_path7.default.resolve(import_node_path7.default.isAbsolute(options3.path) ? options3.path : import_node_path7.default.join(workspaceRoot, options3.path));
13024
+ const resolvedPath = options3.docId ? resolveDocumentById(workspaceRoot, options3.docId).absPath : import_node_path8.default.resolve(import_node_path8.default.isAbsolute(options3.path) ? options3.path : import_node_path8.default.join(workspaceRoot, options3.path));
12610
13025
  const history = getGitHistory(resolvedPath, Number(options3.limit));
12611
13026
  if (options3.json) {
12612
13027
  printJson(envelope("history", { path: resolvedPath, history }));
@@ -12619,7 +13034,7 @@ program2.command("history").option("--workspace <path>", "Workspace path. Option
12619
13034
  });
12620
13035
  program2.command("diff").option("--workspace <path>", "Workspace path. Optional when current directory is already inside a workspace.").option("--doc-id <id>", "Indexed document identifier").option("--path <path>", "Absolute or workspace-relative document path").option("--base <ref>", "Base Git ref", "HEAD").option("--compare <ref>", "Optional compare ref").option("--json", "Emit JSON output", false).action((options3) => {
12621
13036
  const workspaceRoot = assertWorkspace(options3.workspace);
12622
- const resolvedPath = options3.docId ? resolveDocumentById(workspaceRoot, options3.docId).absPath : import_node_path7.default.resolve(import_node_path7.default.isAbsolute(options3.path) ? options3.path : import_node_path7.default.join(workspaceRoot, options3.path));
13037
+ const resolvedPath = options3.docId ? resolveDocumentById(workspaceRoot, options3.docId).absPath : import_node_path8.default.resolve(import_node_path8.default.isAbsolute(options3.path) ? options3.path : import_node_path8.default.join(workspaceRoot, options3.path));
12623
13038
  const diff = getGitDiff(resolvedPath, options3.base, options3.compare);
12624
13039
  if (options3.json) {
12625
13040
  printJson(envelope("diff", { path: resolvedPath, diff }));
package/dist/installer.js CHANGED
@@ -3473,7 +3473,7 @@ var {
3473
3473
  var CLI_NAME = "company-agent-wiki-cli";
3474
3474
  var INSTALLER_NAME = "company-agent-wiki-skill";
3475
3475
  var SKILL_NAME = "company-agent-wiki-cli";
3476
- var CLI_SCHEMA_VERSION = "2026-04-12";
3476
+ var CLI_SCHEMA_VERSION = "2026-04-13";
3477
3477
  var EXIT_CODES = {
3478
3478
  ok: 0,
3479
3479
  usage: 1,
@@ -3484,6 +3484,7 @@ var EXIT_CODES = {
3484
3484
  notFound: 6,
3485
3485
  git: 7,
3486
3486
  sqliteLocked: 8,
3487
+ workspaceBusy: 9,
3487
3488
  runtime: 10
3488
3489
  };
3489
3490
 
@@ -45,7 +45,8 @@ The index and manifest are derived artifacts and should stay ignored in the priv
45
45
  7. `read --metadata --headings` supports a metadata-first retrieval pass before the full Markdown is loaded.
46
46
  8. `search`, `route` and `read` either enforce a fresh index or can explicitly auto-rebuild when `--auto-rebuild` is set.
47
47
  9. Runtime commands may detect the current workspace automatically when the shell is already inside a private workspace.
48
- 10. `serve` exposes the same read-only data through a local web view and now distinguishes `missing`, `stale` and `ok` states with a rebuild action.
48
+ 10. A global per-user workspace registry stores known workspace paths and a default workspace so other agents can resolve the knowledge location automatically on macOS, Windows and Linux.
49
+ 11. `serve` exposes the same read-only data through a local web view and now distinguishes `missing`, `stale` and `ok` states with a rebuild action.
49
50
 
50
51
  ## Onboarding Model
51
52
 
@@ -73,6 +74,8 @@ Reads are pinned to the latest successful `build_id`. If current root snapshots
73
74
 
74
75
  The SQLite runtime also uses a busy timeout and returns a specific `SQLITE_LOCKED` error for transient contention instead of surfacing a generic runtime failure.
75
76
 
77
+ Phase 1 now also adds a workspace-local write lock. The lock serializes rebuilds and other write flows per workspace, while parallel readers continue against the current derived index.
78
+
76
79
  ## Metadata-First Retrieval
77
80
 
78
81
  Phase 1 now explicitly supports a two-step retrieval model:
@@ -82,6 +85,21 @@ Phase 1 now explicitly supports a two-step retrieval model:
82
85
 
83
86
  This keeps the agent loop lighter and encourages stronger filenames plus front matter without forcing a rigid folder taxonomy.
84
87
 
88
+ ## Global Workspace Discovery
89
+
90
+ Phase 1 now persists workspace discovery outside the private workspace itself:
91
+
92
+ - macOS: `~/Library/Application Support/company-agent-wiki/workspaces.json`
93
+ - Windows: `%APPDATA%\\company-agent-wiki\\workspaces.json`
94
+ - Linux: `${XDG_CONFIG_HOME:-~/.config}/company-agent-wiki/workspaces.json`
95
+
96
+ The registry stores known workspace paths plus a default workspace. `setup workspace` registers the workspace automatically. Runtime commands prefer:
97
+
98
+ 1. explicit `--workspace`
99
+ 2. current-directory detection
100
+ 3. global default workspace
101
+ 4. the single registered workspace, if exactly one exists
102
+
85
103
  ## Git Model
86
104
 
87
105
  Phase 1 uses Git for:
@@ -15,7 +15,8 @@
15
15
  - Auto-rebuild is opt-in. Without `--auto-rebuild`, stale or missing indexes still block retrieval on purpose.
16
16
  - Search quality depends on document structure and heading hygiene in the source Markdown.
17
17
  - Front-matter filters are currently focused on common fields such as `type`, `status`, `tags`, `project`, `department`, `owners` and `systems`; there is not yet a generic arbitrary-field query language.
18
- - The SQLite runtime is more tolerant of transient contention now, but the same workspace should still not be hammered by multiple parallel agent reads if that can be avoided.
18
+ - Parallel reads are supported, but there is still only one active write path per workspace at a time.
19
+ - Long-running rebuilds can delay later auto-rebuild requests because they queue behind the same workspace lock.
19
20
  - Search JSON now exposes a normalized `score` plus `rawScore`; the normalized value is better for agents, but it is still only a ranking aid, not a calibrated relevance percentage.
20
21
 
21
22
  ## Git Model
@@ -28,3 +29,4 @@
28
29
 
29
30
  - The CLI can initialize a private Git remote URL, but it does not validate remote policy or access controls.
30
31
  - The package does not enforce OS-level filesystem permissions; the workspace owner must place the private workspace in a properly protected location.
32
+ - The global workspace registry is only a discovery layer, not an access-control boundary. Any agent running as the same local user can read the registered workspace path.
@@ -118,6 +118,25 @@ npm_config_cache="$CACHE" npx -y @codecell-germany/company-agent-wiki-skill@0.1.
118
118
  "$TMP/codex/bin/company-agent-wiki-cli" --help
119
119
  ```
120
120
 
121
+ Falls `npm view` direkt nach dem Publish kurzfristig noch `404` liefert, zusätzlich die Registry-Tarball-URL prüfen und daraus den Install-Smoke fahren:
122
+
123
+ ```bash
124
+ curl -I https://registry.npmjs.org/@codecell-germany/company-agent-wiki-skill/-/company-agent-wiki-skill-0.1.0.tgz
125
+ TMP="$(mktemp -d)"
126
+ cd "$TMP"
127
+ npx -y -p https://registry.npmjs.org/@codecell-germany/company-agent-wiki-skill/-/company-agent-wiki-skill-0.1.0.tgz company-agent-wiki-cli --help
128
+ ```
129
+
130
+ Praktisches Learning aus diesem Release:
131
+
132
+ - `npm dist-tag ls` und die direkte Tarball-URL können bereits korrekt funktionieren, während `npm view` bzw. die Packument-Auflösung noch kurz hinterherhinkt.
133
+ - Für die Veröffentlichungsfreigabe reicht deshalb im Zweifel:
134
+ - erfolgreicher `npm publish`
135
+ - `npm access get status <package>` = `public`
136
+ - `npm dist-tag ls <package>` zeigt `latest`
137
+ - Tarball-URL liefert `200`
138
+ - CLI-/Installer-Smoke direkt von der Tarball-URL funktioniert
139
+
121
140
  ## skills.sh Verification
122
141
 
123
142
  Nach GitHub-Publish:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codecell-germany/company-agent-wiki-skill",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Context is king: agent-first local company knowledge workspace with metadata-first retrieval, Markdown as truth, SQLite-indexed front matter, Git-aware verification, and a Codex skill installer.",
5
5
  "private": false,
6
6
  "license": "MIT",
@@ -17,7 +17,7 @@ Use this skill when the task is about a private company knowledge workspace buil
17
17
  - The public CLI binary is `company-agent-wiki-cli`.
18
18
  - The actual company knowledge workspace is private and lives outside the public code repository.
19
19
  - The private workspace may be the current dedicated local folder; it just must not be the public skill/CLI repo.
20
- - The human should provide the workspace path and, if desired, the private Git remote URL. If the current folder is already inside a private workspace, the CLI may detect it automatically.
20
+ - The human should provide the workspace path at least once and, if desired, the private Git remote URL. After setup or manual registration, the CLI stores the workspace path in a global per-user registry so later agents can resolve it automatically.
21
21
  - Runtime discovery matters. Before relying on the CLI, verify which path is actually available.
22
22
  - In Codex, the most reliable fallback is usually the installed shim under `$CODEX_HOME/bin` or `~/.codex/bin`.
23
23
  - The `npx -p @codecell-germany/company-agent-wiki-skill ...` path only works after the npm package is really published.
@@ -58,7 +58,15 @@ npx -p @codecell-germany/company-agent-wiki-skill company-agent-wiki-cli --help
58
58
  company-agent-wiki-cli setup workspace --workspace /absolute/path/to/private-company-knowledge --git-init
59
59
  ```
60
60
 
61
- This creates starter Markdown documents by default. Use `--no-starter-docs` only when you explicitly want a nearly empty workspace.
61
+ This creates starter Markdown documents by default and registers the workspace globally for future agents. Use `--no-starter-docs` only when you explicitly want a nearly empty workspace.
62
+
63
+ If the workspace already exists, inspect or register it explicitly:
64
+
65
+ ```bash
66
+ company-agent-wiki-cli workspace current --json
67
+ company-agent-wiki-cli workspace list --json
68
+ company-agent-wiki-cli workspace register --workspace /absolute/path/to/private-company-knowledge --default --json
69
+ ```
62
70
 
63
71
  3. Run health checks:
64
72
 
@@ -234,13 +242,19 @@ company-agent-wiki-cli onboarding company \
234
242
  - The local SQLite file lives in the workspace, but should stay out of Git by default.
235
243
  - If the CLI reports `INDEX_STALE`, do not ignore it. Run `index rebuild` or use an explicit `--auto-rebuild` path.
236
244
  - For agent workflows, prefer `--auto-rebuild` on `search`, `route`, `read` and `serve` unless you explicitly want strict stale-index failures.
237
- - Avoid parallel `search`, `route`, `read`, `history` and `diff` calls against the same workspace when possible. SQLite contention is now surfaced clearly, but agent-side serialization is still the safer Phase-1 pattern.
245
+ - Parallel `search`, `route`, `read`, `history` and `diff` calls against the same workspace are now intended to work.
246
+ - Write paths are serialized per workspace. If one agent is rebuilding the index or applying onboarding writes, other write paths wait behind that workspace lock instead of colliding.
238
247
  - Do not put private company knowledge into the public code repository.
239
248
  - Use the read-only web view only for browsing, not editing.
240
249
  - The company onboarding questionnaire is optional. Every answer may be skipped, answered with “nein” or marked as unknown.
241
250
  - Onboarding writes are explicit. Do not assume preview mode changes files; only `--execute` writes draft onboarding Markdown and rebuilds the derived index.
242
251
  - If CLI discovery fails, do not pretend the documented command works. First resolve a real executable path.
243
252
  - If the current folder already contains `.company-agent-wiki/workspace.json`, you may omit `--workspace` and let the CLI detect the workspace root automatically.
253
+ - If the current folder is not inside the workspace, the CLI may still resolve the globally registered default workspace.
254
+ - The global workspace registry lives per user:
255
+ - macOS: `~/Library/Application Support/company-agent-wiki/workspaces.json`
256
+ - Windows: `%APPDATA%\\company-agent-wiki\\workspaces.json`
257
+ - Linux: `${XDG_CONFIG_HOME:-~/.config}/company-agent-wiki/workspaces.json`
244
258
 
245
259
  ## References
246
260
 
@@ -7,6 +7,8 @@
7
7
  "$HOME/.codex/bin/company-agent-wiki-cli" --help
8
8
  company-agent-wiki-cli --help
9
9
  company-agent-wiki-cli setup workspace --workspace /absolute/path --git-init
10
+ company-agent-wiki-cli workspace current --json
11
+ company-agent-wiki-cli workspace list --json
10
12
  company-agent-wiki-cli onboarding company
11
13
  company-agent-wiki-cli onboarding company --workspace /absolute/path --answers-file /absolute/path/to/answers.json
12
14
  company-agent-wiki-cli onboarding company --workspace /absolute/path --answers-file /absolute/path/to/answers.json --execute
@@ -24,6 +26,8 @@ Only after the company profile is clear enough and these checks succeed should y
24
26
  company-agent-wiki-cli setup workspace --workspace /absolute/path --git-init
25
27
  ```
26
28
 
29
+ This setup step also registers the workspace globally so later agents can find it without asking for the same path again.
30
+
27
31
  If the human has a private Git remote ready:
28
32
 
29
33
  ```bash
@@ -49,6 +53,8 @@ For frequent edits, the authoring loop can also use the guarded auto-rebuild pat
49
53
  company-agent-wiki-cli search "KI-Telefonassistent" --workspace /absolute/path --auto-rebuild --json
50
54
  ```
51
55
 
56
+ Parallel reads are allowed. If a write path such as `index rebuild` is already running, later writes wait behind the same workspace lock.
57
+
52
58
  After candidate routing, prefer metadata-first reading:
53
59
 
54
60
  ```bash
@@ -7,6 +7,10 @@
7
7
  "$HOME/.codex/bin/company-agent-wiki-cli" --help
8
8
  company-agent-wiki-cli about --json
9
9
  company-agent-wiki-cli setup workspace --workspace /absolute/path --git-init
10
+ company-agent-wiki-cli workspace current --json
11
+ company-agent-wiki-cli workspace list --json
12
+ company-agent-wiki-cli workspace register --workspace /absolute/path --default --json
13
+ company-agent-wiki-cli workspace use --workspace /absolute/path --json
10
14
  company-agent-wiki-cli onboarding company
11
15
  company-agent-wiki-cli onboarding company --workspace /absolute/path --answers-file /absolute/path/to/answers.json
12
16
  company-agent-wiki-cli onboarding company --workspace /absolute/path --answers-file /absolute/path/to/answers.json --execute
@@ -23,6 +27,7 @@ company-agent-wiki-cli verify --workspace /absolute/path --json
23
27
  ```
24
28
 
25
29
  If you are already inside the private workspace, `doctor`, `verify`, `search`, `route`, `read`, `history`, `diff` and `serve` may omit `--workspace`.
30
+ If not, the CLI can also use the globally registered default workspace.
26
31
 
27
32
  ## Retrieval
28
33
 
@@ -16,6 +16,7 @@
16
16
  ## Main Commands
17
17
 
18
18
  - `setup workspace`
19
+ - `workspace current|list|register|use`
19
20
  - `onboarding company`
20
21
  - `doctor`
21
22
  - `verify`
@@ -32,10 +33,13 @@
32
33
  ## Expected Workflow
33
34
 
34
35
  1. Set up the private workspace.
35
- 2. Use the starter documents or run `onboarding company`, then preview or apply the generated onboarding Markdown from an answers file.
36
- 3. Register any additional Markdown roots.
37
- 4. Rebuild the index.
38
- 5. Verify that the snapshot is fresh. On a brand-new workspace `verify` reports `missing` instead of failing hard.
39
- 6. Search or route to the right document. For active authoring loops, prefer `--auto-rebuild` and front-matter filters such as `--type`, `--project` or `--department`.
40
- 7. Inspect metadata and headings with `read --metadata --headings --auto-rebuild`.
41
- 8. Read the full source document or use the read-only web view.
36
+ 2. The workspace is registered globally so other agents can discover it automatically.
37
+ 3. Use the starter documents or run `onboarding company`, then preview or apply the generated onboarding Markdown from an answers file.
38
+ 4. Register any additional Markdown roots.
39
+ 5. Rebuild the index.
40
+ 6. Verify that the snapshot is fresh. On a brand-new workspace `verify` reports `missing` instead of failing hard.
41
+ 7. Search or route to the right document. For active authoring loops, prefer `--auto-rebuild` and front-matter filters such as `--type`, `--project` or `--department`.
42
+ 8. Inspect metadata and headings with `read --metadata --headings --auto-rebuild`.
43
+ 9. Read the full source document or use the read-only web view.
44
+
45
+ Parallel reads are supported. Writes are serialized per workspace.
@@ -5,6 +5,8 @@
5
5
  - a private workspace folder path
6
6
  - if desired, a private Git remote URL
7
7
 
8
+ The workspace path only has to be provided once. After setup or manual registration, the CLI stores it in a per-user global registry for later agents.
9
+
8
10
  ## What the Agent Can Safely Do
9
11
 
10
12
  - scaffold the local workspace
@@ -13,6 +15,7 @@
13
15
  - create the default starter documents for the first useful company knowledge
14
16
  - preview or apply onboarding-generated starter Markdown from an answers file
15
17
  - register Markdown roots
18
+ - register the workspace globally for later agents
16
19
  - rebuild the derived index
17
20
  - verify freshness or a still-missing first index
18
21
  - run the read-only web view via `company-agent-wiki-cli serve --workspace /absolute/path --port 4187`
@@ -30,6 +33,13 @@ company-agent-wiki-cli --help
30
33
  The package-based `npx` path is only valid once the npm package is published.
31
34
 
32
35
  If the current folder is already inside a private workspace, runtime commands may omit `--workspace`.
36
+ If not, inspect or update the global registry:
37
+
38
+ ```bash
39
+ company-agent-wiki-cli workspace current --json
40
+ company-agent-wiki-cli workspace list --json
41
+ company-agent-wiki-cli workspace register --workspace /absolute/path --default --json
42
+ ```
33
43
 
34
44
  ## What Must Stay Out of This Public Repo
35
45