@vortex-os/base 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -280,7 +280,8 @@ var dist_exports2 = {};
280
280
  __export(dist_exports2, {
281
281
  CommandNotFoundError: () => CommandNotFoundError,
282
282
  CommandRegistry: () => CommandRegistry,
283
- runSlash: () => runSlash
283
+ runSlash: () => runSlash,
284
+ runSlashArgv: () => runSlashArgv
284
285
  });
285
286
 
286
287
  // ../modules/slash-commands/dist/registry.js
@@ -337,6 +338,24 @@ async function runSlash(input, { registry, context }) {
337
338
  };
338
339
  return command.handler(ci);
339
340
  }
341
+ async function runSlashArgv(name, argv, { registry, context }) {
342
+ if (name.length === 0) {
343
+ throw new Error("Empty command name");
344
+ }
345
+ const command = registry.get(name);
346
+ if (!command) {
347
+ throw new CommandNotFoundError(name);
348
+ }
349
+ const args = parseArgs(argv, command.args);
350
+ const ci = {
351
+ raw: [name, ...argv].join(" "),
352
+ args,
353
+ rest: argv.join(" "),
354
+ argv,
355
+ context
356
+ };
357
+ return command.handler(ci);
358
+ }
340
359
  function parseArgs(tokens, schema) {
341
360
  const out = {};
342
361
  if (!schema)
@@ -4199,35 +4218,50 @@ __export(dist_exports14, {
4199
4218
  SESSION_END_COMMAND: () => SESSION_END_COMMAND,
4200
4219
  SESSION_START_COMMAND: () => SESSION_START_COMMAND,
4201
4220
  agendaCommand: () => agendaCommand,
4221
+ applyGlobalSetup: () => applyGlobalSetup,
4222
+ argvToSlash: () => argvToSlash,
4202
4223
  buildInstallCommand: () => buildInstallCommand,
4203
4224
  buildOwnershipManifest: () => buildOwnershipManifest,
4204
4225
  buildRegistry: () => buildRegistry,
4205
4226
  catchUpSessions: () => catchUpSessions,
4206
4227
  checkBaseUpdate: () => checkBaseUpdate,
4207
4228
  collectAgenda: () => collectAgenda,
4229
+ collectCarryover: () => collectCarryover,
4208
4230
  collectSessionStartReport: () => collectSessionStartReport,
4209
4231
  compareSemver: () => compareSemver,
4210
4232
  computeCurateFingerprint: () => computeCurateFingerprint,
4233
+ countUncommitted: () => countUncommitted,
4211
4234
  createAmbientRecaller: () => createAmbientRecaller,
4212
4235
  createRitualRegistry: () => createRitualRegistry,
4213
4236
  curateCommand: () => curateCommand,
4214
4237
  decisionCommand: () => decisionCommand,
4238
+ detectInterruptedGitOp: () => detectInterruptedGitOp,
4215
4239
  detectWorklogGaps: () => detectWorklogGaps,
4216
4240
  ensureVortexHooks: () => ensureVortexHooks,
4217
4241
  ensureWorklogEntry: () => ensureWorklogEntry,
4218
4242
  extractNextUp: () => extractNextUp,
4219
4243
  extractOpenTasks: () => extractOpenTasks,
4244
+ globalMemoryPath: () => globalMemoryPath,
4245
+ globalSettingsHasHook: () => globalSettingsHasHook,
4246
+ globalSettingsPath: () => globalSettingsPath,
4247
+ globalStatePath: () => globalStatePath,
4248
+ inspectGlobalSetup: () => inspectGlobalSetup,
4220
4249
  inspectOwnership: () => inspectOwnership,
4250
+ isInstanceRoot: () => isInstanceRoot,
4221
4251
  isNewer: () => isNewer,
4222
4252
  isStableUpdate: () => isStableUpdate,
4223
4253
  logCommand: () => logCommand,
4224
4254
  ownershipManifestPath: () => ownershipManifestPath,
4255
+ parseAdoptArgs: () => parseAdoptArgs,
4225
4256
  parseSettings: () => parseSettings,
4226
4257
  queryNpmLatest: () => queryNpmLatest,
4258
+ readGlobalInstancePointer: () => readGlobalInstancePointer,
4227
4259
  readInstalledBaseVersion: () => readInstalledBaseVersion,
4228
4260
  recallCommand: () => recallCommand,
4261
+ recordGlobalSetupDecline: () => recordGlobalSetupDecline,
4229
4262
  reindexCommand: () => reindexCommand,
4230
4263
  renderAgenda: () => renderAgenda,
4264
+ renderGlobalBlock: () => renderGlobalBlock,
4231
4265
  renderSessionStartReport: () => renderSessionStartReport,
4232
4266
  repairOwnershipManifest: () => repairOwnershipManifest,
4233
4267
  resolveRepoRoot: () => resolveRepoRoot,
@@ -4240,6 +4274,7 @@ __export(dist_exports14, {
4240
4274
  serializeSettings: () => serializeSettings,
4241
4275
  sessionStartCommand: () => sessionStartCommand,
4242
4276
  templateDestRelPath: () => templateDestRelPath,
4277
+ upsertGlobalBlock: () => upsertGlobalBlock,
4243
4278
  validateCuratePayload: () => validateCuratePayload,
4244
4279
  vortexCommand: () => vortexCommand,
4245
4280
  writeOwnershipManifest: () => writeOwnershipManifest
@@ -4737,14 +4772,18 @@ function todayIso2() {
4737
4772
 
4738
4773
  // ../plugins/session-rituals/dist/commands/vortex.js
4739
4774
  import { spawn } from "child_process";
4740
- import { constants, existsSync as existsSync10 } from "fs";
4741
- import { copyFile as copyFile2, mkdir as mkdir7, readdir as readdir15, readFile as readFile19, stat as stat7, writeFile as writeFile10 } from "fs/promises";
4742
- import { basename as basename7, dirname as dirname5, extname as extname11, join as join24, relative as relative5 } from "path";
4775
+ import { constants, existsSync as existsSync11 } from "fs";
4776
+ import { copyFile as copyFile2, mkdir as mkdir8, readdir as readdir15, readFile as readFile20, stat as stat7, writeFile as writeFile11 } from "fs/promises";
4777
+ import { basename as basename7, dirname as dirname5, extname as extname11, join as join25, relative as relative5 } from "path";
4743
4778
  import { fileURLToPath } from "url";
4744
4779
 
4745
4780
  // ../plugins/session-rituals/dist/ensure-hooks.js
4746
- var SESSION_START_COMMAND = "npx --no-install -p @vortex-os/base vortex session-start";
4747
- var SESSION_END_COMMAND = "npx --no-install -p @vortex-os/base vortex session-end";
4781
+ var SESSION_START_COMMAND = "npx --no-install -p @vortex-os/base vortex session-start || exit 0";
4782
+ var SESSION_END_COMMAND = "npx --no-install -p @vortex-os/base vortex session-end || exit 0";
4783
+ var LEGACY_COMMANDS = {
4784
+ SessionStart: "npx --no-install -p @vortex-os/base vortex session-start",
4785
+ SessionEnd: "npx --no-install -p @vortex-os/base vortex session-end"
4786
+ };
4748
4787
  function parseSettings(text) {
4749
4788
  const trimmed = (text ?? "").trim();
4750
4789
  if (trimmed.length === 0)
@@ -4760,22 +4799,46 @@ function parseSettings(text) {
4760
4799
  }
4761
4800
  return parsed;
4762
4801
  }
4763
- function hasCommand(groups, command) {
4764
- if (!groups)
4765
- return false;
4766
- return groups.some((g) => g.hooks?.some((h) => h.command === command));
4767
- }
4768
4802
  function ensureVortexHooks(existing) {
4769
4803
  const base = existing && typeof existing === "object" ? existing : {};
4770
4804
  const hooks = { ...base.hooks ?? {} };
4771
4805
  const added = [];
4772
4806
  const wire = (event, command) => {
4773
- const groups = hooks[event] ? [...hooks[event]] : [];
4774
- if (hasCommand(groups, command))
4775
- return;
4776
- groups.push({ hooks: [{ type: "command", command }] });
4807
+ const legacy = LEGACY_COMMANDS[event];
4808
+ const src = hooks[event] ?? [];
4809
+ let changed = false;
4810
+ let kept = false;
4811
+ const groups = [];
4812
+ for (const g of src) {
4813
+ const hookList = [];
4814
+ for (const h of g.hooks ?? []) {
4815
+ const migrated = h.command === legacy;
4816
+ const cmd = migrated ? command : h.command;
4817
+ if (cmd === command) {
4818
+ if (kept) {
4819
+ changed = true;
4820
+ continue;
4821
+ }
4822
+ kept = true;
4823
+ if (migrated)
4824
+ changed = true;
4825
+ hookList.push(migrated ? { ...h, command } : h);
4826
+ } else {
4827
+ hookList.push(h);
4828
+ }
4829
+ }
4830
+ if (hookList.length > 0)
4831
+ groups.push({ ...g, hooks: hookList });
4832
+ else
4833
+ changed = true;
4834
+ }
4835
+ if (!kept) {
4836
+ groups.push({ hooks: [{ type: "command", command }] });
4837
+ changed = true;
4838
+ }
4777
4839
  hooks[event] = groups;
4778
- added.push(event);
4840
+ if (changed)
4841
+ added.push(event);
4779
4842
  };
4780
4843
  wire("SessionStart", SESSION_START_COMMAND);
4781
4844
  wire("SessionEnd", SESSION_END_COMMAND);
@@ -4786,15 +4849,160 @@ function serializeSettings(settings) {
4786
4849
  return JSON.stringify(settings, null, 2) + "\n";
4787
4850
  }
4788
4851
 
4852
+ // ../plugins/session-rituals/dist/global-setup.js
4853
+ import { homedir } from "os";
4854
+ import { existsSync as existsSync9, readFileSync as readFileSync2 } from "fs";
4855
+ import { mkdir as mkdir6, readFile as readFile18, writeFile as writeFile10 } from "fs/promises";
4856
+ import { isAbsolute as isAbsolute3, join as join23 } from "path";
4857
+ async function readFileIfExists(path) {
4858
+ try {
4859
+ return await readFile18(path, "utf8");
4860
+ } catch (e) {
4861
+ if (e.code === "ENOENT")
4862
+ return null;
4863
+ throw e;
4864
+ }
4865
+ }
4866
+ function isSafeInstanceRoot(dir) {
4867
+ return typeof dir === "string" && dir.trim().length > 0 && isAbsolute3(dir) && !/[\r\n`]/.test(dir) && isInstanceRoot(dir);
4868
+ }
4869
+ function globalClaudeDir(home = homedir()) {
4870
+ return join23(home, ".claude");
4871
+ }
4872
+ function globalSettingsPath(home = homedir()) {
4873
+ return join23(globalClaudeDir(home), "settings.json");
4874
+ }
4875
+ function globalStatePath(home = homedir()) {
4876
+ return join23(globalClaudeDir(home), "vortex-global.json");
4877
+ }
4878
+ function globalMemoryPath(home = homedir()) {
4879
+ return join23(globalClaudeDir(home), "CLAUDE.md");
4880
+ }
4881
+ function readGlobalStateRaw(home = homedir()) {
4882
+ try {
4883
+ const parsed = JSON.parse(readFileSync2(globalStatePath(home), "utf8"));
4884
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
4885
+ return parsed;
4886
+ }
4887
+ } catch {
4888
+ }
4889
+ return null;
4890
+ }
4891
+ function readGlobalInstancePointer(home = homedir()) {
4892
+ const root = readGlobalStateRaw(home)?.instanceRoot;
4893
+ return typeof root === "string" && root.trim().length > 0 ? root.trim() : null;
4894
+ }
4895
+ function isInstanceRoot(dir) {
4896
+ return existsSync9(join23(dir, ".agent", "vortex.json")) || existsSync9(join23(dir, "data", "_memory", "user_profile.md"));
4897
+ }
4898
+ function globalSettingsHasHook(home = homedir()) {
4899
+ try {
4900
+ const settings = parseSettings(readFileSync2(globalSettingsPath(home), "utf8"));
4901
+ const wired = (event, command) => Boolean(settings.hooks?.[event]?.some((g) => g.hooks?.some((h) => h.command === command)));
4902
+ return wired("SessionStart", SESSION_START_COMMAND) && wired("SessionEnd", SESSION_END_COMMAND);
4903
+ } catch {
4904
+ return false;
4905
+ }
4906
+ }
4907
+ function inspectGlobalSetup(home = homedir(), instanceRoot) {
4908
+ const state = readGlobalStateRaw(home) ?? {};
4909
+ const ptr = typeof state.instanceRoot === "string" && state.instanceRoot.trim().length > 0 ? state.instanceRoot.trim() : null;
4910
+ const hooked = globalSettingsHasHook(home);
4911
+ const done = Boolean(ptr) && hooked && (!instanceRoot || ptr === instanceRoot);
4912
+ return { done, declined: typeof state.declinedAt === "string" };
4913
+ }
4914
+ var BLOCK_BEGIN = "<!-- VORTEX-GLOBAL:BEGIN \u2014 managed by `vortex global-setup`; edit OUTSIDE these markers (this block is regenerated) -->";
4915
+ var BLOCK_END = "<!-- VORTEX-GLOBAL:END -->";
4916
+ var BLOCK_RE = /^<!-- VORTEX-GLOBAL:BEGIN\b[^\r\n]*-->[ \t]*\r?\n[\s\S]*?\r?\n<!-- VORTEX-GLOBAL:END -->[ \t]*\r?$/m;
4917
+ function renderGlobalBlock(instanceRoot) {
4918
+ return [
4919
+ BLOCK_BEGIN,
4920
+ "## VortEX (always-on)",
4921
+ "",
4922
+ `A VortEX instance lives at: \`${instanceRoot}\``,
4923
+ "",
4924
+ "When doing VortEX work (worklog, decisions, recall, memory) \u2014 or any substantive coding you want VortEX's working rules for \u2014 read that instance's `AI-RULES.md` and follow it. Run `vortex` commands so they record to that instance (they resolve it automatically; or pass `VORTEX_REPO_ROOT=<instance path>`). Stay in the current folder \u2014 do not `cd` into the instance.",
4925
+ BLOCK_END
4926
+ ].join("\n");
4927
+ }
4928
+ function upsertGlobalBlock(existing, instanceRoot) {
4929
+ const block = renderGlobalBlock(instanceRoot);
4930
+ if (BLOCK_RE.test(existing)) {
4931
+ return existing.replace(BLOCK_RE, block);
4932
+ }
4933
+ const head = existing.replace(/\s*$/, "");
4934
+ return (head.length > 0 ? head + "\n\n" : "") + block + "\n";
4935
+ }
4936
+ async function applyGlobalSetup(opts) {
4937
+ const home = opts.home ?? homedir();
4938
+ const instanceRoot = opts.instanceRoot;
4939
+ const created = [];
4940
+ const modified = [];
4941
+ const skipped = [];
4942
+ const statePath = globalStatePath(home);
4943
+ const settingsPath = globalSettingsPath(home);
4944
+ const instSettingsPath = join23(instanceRoot, ".claude", "settings.json");
4945
+ const mdPath = globalMemoryPath(home);
4946
+ const hadState = existsSync9(statePath);
4947
+ const prevState = readGlobalStateRaw(home) ?? {};
4948
+ const settingsText = await readFileIfExists(settingsPath);
4949
+ const mergedGlobal = ensureVortexHooks(parseSettings(settingsText));
4950
+ const instText = await readFileIfExists(instSettingsPath);
4951
+ const mergedInst = instText !== null ? ensureVortexHooks(parseSettings(instText)) : null;
4952
+ const mdRead = await readFileIfExists(mdPath);
4953
+ const mdText = mdRead ?? "";
4954
+ const nextMd = upsertGlobalBlock(mdText, instanceRoot);
4955
+ await mkdir6(globalClaudeDir(home), { recursive: true });
4956
+ const { declinedAt: _wasDeclined, ...restState } = prevState;
4957
+ const nextState = { ...restState, instanceRoot };
4958
+ if (!hadState || prevState.instanceRoot !== instanceRoot || typeof prevState.declinedAt === "string") {
4959
+ await writeFile10(statePath, JSON.stringify(nextState, null, 2) + "\n", "utf8");
4960
+ (hadState ? modified : created).push(statePath);
4961
+ } else {
4962
+ skipped.push(statePath);
4963
+ }
4964
+ if (!mergedGlobal.alreadyWired) {
4965
+ await writeFile10(settingsPath, serializeSettings(mergedGlobal.settings), "utf8");
4966
+ (settingsText === null ? created : modified).push(settingsPath);
4967
+ } else {
4968
+ skipped.push(settingsPath);
4969
+ }
4970
+ if (mergedInst !== null) {
4971
+ if (!mergedInst.alreadyWired) {
4972
+ await writeFile10(instSettingsPath, serializeSettings(mergedInst.settings), "utf8");
4973
+ modified.push(instSettingsPath);
4974
+ } else {
4975
+ skipped.push(instSettingsPath);
4976
+ }
4977
+ }
4978
+ if (nextMd !== mdText) {
4979
+ await writeFile10(mdPath, nextMd, "utf8");
4980
+ (mdRead === null ? created : modified).push(mdPath);
4981
+ } else {
4982
+ skipped.push(mdPath);
4983
+ }
4984
+ return { created, modified, skipped };
4985
+ }
4986
+ async function recordGlobalSetupDecline(opts) {
4987
+ const home = opts?.home ?? homedir();
4988
+ const now = opts?.now ?? /* @__PURE__ */ new Date();
4989
+ await mkdir6(globalClaudeDir(home), { recursive: true });
4990
+ const statePath = globalStatePath(home);
4991
+ const prevState = readGlobalStateRaw(home) ?? {};
4992
+ const nextState = { ...prevState, declinedAt: now.toISOString() };
4993
+ await writeFile10(statePath, JSON.stringify(nextState, null, 2) + "\n", "utf8");
4994
+ return statePath;
4995
+ }
4996
+
4789
4997
  // ../plugins/session-rituals/dist/update.js
4790
4998
  import { createHash as createHash2 } from "crypto";
4791
- import { existsSync as existsSync9 } from "fs";
4792
- import { copyFile, mkdir as mkdir6, readFile as readFile18 } from "fs/promises";
4793
- import { dirname as dirname4, isAbsolute as isAbsolute3, join as join23, relative as relative4, sep as sep4 } from "path";
4999
+ import { existsSync as existsSync10 } from "fs";
5000
+ import { copyFile, mkdir as mkdir7, readFile as readFile19 } from "fs/promises";
5001
+ import { dirname as dirname4, isAbsolute as isAbsolute4, join as join24, relative as relative4, sep as sep4 } from "path";
4794
5002
  var OWNERSHIP_SCHEMA = "vortex-ownership/1";
4795
5003
  var MANIFEST_NAME = "manifest.json";
4796
5004
  function ownershipManifestPath(ctx) {
4797
- return join23(ctx.dataDir, ".vortex", "ownership.json");
5005
+ return join24(ctx.dataDir, ".vortex", "ownership.json");
4798
5006
  }
4799
5007
  function toPosix(p) {
4800
5008
  return p.split(sep4).join("/");
@@ -4803,7 +5011,7 @@ function sha256(buf) {
4803
5011
  return createHash2("sha256").update(buf).digest("hex");
4804
5012
  }
4805
5013
  async function sha256File(absPath) {
4806
- return sha256(await readFile18(absPath));
5014
+ return sha256(await readFile19(absPath));
4807
5015
  }
4808
5016
  function templateDestRelPath(templateRelPath) {
4809
5017
  const parts = templateRelPath.split("/");
@@ -4814,9 +5022,9 @@ function templateDestRelPath(templateRelPath) {
4814
5022
  if (top === "routers")
4815
5023
  return tail;
4816
5024
  if (top === "commands")
4817
- return join23(".claude", "commands", tail);
5025
+ return join24(".claude", "commands", tail);
4818
5026
  if (top === "config")
4819
- return join23(".agent", tail);
5027
+ return join24(".agent", tail);
4820
5028
  return null;
4821
5029
  }
4822
5030
  function assertUnderRoot(rootAbs, candidateAbs) {
@@ -4825,16 +5033,16 @@ function assertUnderRoot(rootAbs, candidateAbs) {
4825
5033
  const winsensitive = sep4 === "\\";
4826
5034
  const cmp = winsensitive ? rel.toLowerCase() : rel;
4827
5035
  const upCmp = winsensitive ? up.toLowerCase() : up;
4828
- if (rel === ".." || cmp.startsWith(upCmp) || isAbsolute3(rel)) {
5036
+ if (rel === ".." || cmp.startsWith(upCmp) || isAbsolute4(rel)) {
4829
5037
  throw new Error(`Refusing to write outside the instance root: ${candidateAbs}`);
4830
5038
  }
4831
5039
  }
4832
5040
  async function readTemplateIndex(templatesDir) {
4833
- const indexPath = join23(templatesDir, MANIFEST_NAME);
4834
- if (!existsSync9(indexPath))
5041
+ const indexPath = join24(templatesDir, MANIFEST_NAME);
5042
+ if (!existsSync10(indexPath))
4835
5043
  return null;
4836
5044
  try {
4837
- const parsed = JSON.parse(await readFile18(indexPath, "utf8"));
5045
+ const parsed = JSON.parse(await readFile19(indexPath, "utf8"));
4838
5046
  if (!parsed || !Array.isArray(parsed.files))
4839
5047
  return null;
4840
5048
  return parsed;
@@ -4855,14 +5063,14 @@ async function buildOwnershipManifest(ctx, templatesDir) {
4855
5063
  if (seenDest.has(destRel))
4856
5064
  continue;
4857
5065
  seenDest.add(destRel);
4858
- const shippedAbs = join23(templatesDir, entry.path);
4859
- if (!existsSync9(shippedAbs))
5066
+ const shippedAbs = join24(templatesDir, entry.path);
5067
+ if (!existsSync10(shippedAbs))
4860
5068
  continue;
4861
5069
  const sourceSha256 = await sha256File(shippedAbs);
4862
- const destAbs = join23(ctx.repoRoot, destRel);
5070
+ const destAbs = join24(ctx.repoRoot, destRel);
4863
5071
  assertUnderRoot(ctx.repoRoot, destAbs);
4864
5072
  let installedSha256 = null;
4865
- if (existsSync9(destAbs)) {
5073
+ if (existsSync10(destAbs)) {
4866
5074
  const onDisk = await sha256File(destAbs);
4867
5075
  installedSha256 = onDisk === sourceSha256 ? sourceSha256 : null;
4868
5076
  }
@@ -4883,14 +5091,14 @@ async function writeOwnershipManifest(ctx, templatesDir) {
4883
5091
  if (!manifest)
4884
5092
  return null;
4885
5093
  const mp = ownershipManifestPath(ctx);
4886
- await mkdir6(join23(ctx.dataDir, ".vortex"), { recursive: true });
5094
+ await mkdir7(join24(ctx.dataDir, ".vortex"), { recursive: true });
4887
5095
  await atomicWriteFile(mp, JSON.stringify(manifest, null, 2) + "\n");
4888
5096
  return { path: mp, fileCount: manifest.files.length };
4889
5097
  }
4890
5098
  async function inspectOwnership(ctx) {
4891
5099
  const own = await readOwnershipManifest(ctx);
4892
5100
  if (!own) {
4893
- const malformed = existsSync9(ownershipManifestPath(ctx));
5101
+ const malformed = existsSync10(ownershipManifestPath(ctx));
4894
5102
  return { present: false, malformed, total: 0, pristine: 0, modified: 0, missing: 0, unmanaged: 0 };
4895
5103
  }
4896
5104
  let pristine = 0;
@@ -4902,8 +5110,8 @@ async function inspectOwnership(ctx) {
4902
5110
  unmanaged++;
4903
5111
  continue;
4904
5112
  }
4905
- const abs = join23(ctx.repoRoot, e.path);
4906
- if (!existsSync9(abs)) {
5113
+ const abs = join24(ctx.repoRoot, e.path);
5114
+ if (!existsSync10(abs)) {
4907
5115
  missing++;
4908
5116
  continue;
4909
5117
  }
@@ -4920,7 +5128,7 @@ async function inspectOwnership(ctx) {
4920
5128
  }
4921
5129
  async function repairOwnershipManifest(ctx, templatesDir) {
4922
5130
  const mp = ownershipManifestPath(ctx);
4923
- if (existsSync9(mp)) {
5131
+ if (existsSync10(mp)) {
4924
5132
  const existing = await readOwnershipManifest(ctx);
4925
5133
  return existing ? { status: "already-present", path: mp, fileCount: existing.files.length } : { status: "unreadable", path: mp };
4926
5134
  }
@@ -4929,10 +5137,10 @@ async function repairOwnershipManifest(ctx, templatesDir) {
4929
5137
  }
4930
5138
  async function readOwnershipManifest(ctx) {
4931
5139
  const mp = ownershipManifestPath(ctx);
4932
- if (!existsSync9(mp))
5140
+ if (!existsSync10(mp))
4933
5141
  return null;
4934
5142
  try {
4935
- const parsed = JSON.parse(await readFile18(mp, "utf8"));
5143
+ const parsed = JSON.parse(await readFile19(mp, "utf8"));
4936
5144
  if (!parsed || !Array.isArray(parsed.files))
4937
5145
  return null;
4938
5146
  for (const e of parsed.files) {
@@ -4947,6 +5155,7 @@ async function readOwnershipManifest(ctx) {
4947
5155
  }
4948
5156
  async function runTemplatesUpdate(ctx, templatesDir, options = {}) {
4949
5157
  const dryRun = options.dryRun ?? false;
5158
+ const adopt = options.adopt ?? /* @__PURE__ */ new Set();
4950
5159
  const base = {
4951
5160
  subcommand: "update",
4952
5161
  mode: "templates-only",
@@ -4983,6 +5192,8 @@ async function runTemplatesUpdate(ctx, templatesDir, options = {}) {
4983
5192
  const ownByTemplateId = new Map(own.files.map((e) => [e.templateId, e]));
4984
5193
  const seenTemplateIds = /* @__PURE__ */ new Set();
4985
5194
  const seenDest = /* @__PURE__ */ new Set();
5195
+ const adoptMatched = /* @__PURE__ */ new Set();
5196
+ const adoptRefusedConfig = [];
4986
5197
  const ops = [];
4987
5198
  for (const idx of index.files) {
4988
5199
  const destRel = templateDestRelPath(idx.path);
@@ -4992,17 +5203,37 @@ async function runTemplatesUpdate(ctx, templatesDir, options = {}) {
4992
5203
  continue;
4993
5204
  seenDest.add(destRel);
4994
5205
  seenTemplateIds.add(idx.templateId);
4995
- const shippedAbs = join23(templatesDir, idx.path);
4996
- if (!existsSync9(shippedAbs))
5206
+ const shippedAbs = join24(templatesDir, idx.path);
5207
+ if (!existsSync10(shippedAbs))
4997
5208
  continue;
4998
5209
  const newSource = await sha256File(shippedAbs);
4999
- const destAbs = join23(ctx.repoRoot, destRel);
5210
+ const destAbs = join24(ctx.repoRoot, destRel);
5000
5211
  assertUnderRoot(ctx.repoRoot, destAbs);
5001
5212
  const path = toPosix(destRel);
5002
5213
  const templateId = idx.templateId;
5003
- const exists = existsSync9(destAbs);
5214
+ const exists = existsSync10(destAbs);
5004
5215
  const curHash = exists ? await sha256File(destAbs) : null;
5005
5216
  const prior = ownByTemplateId.get(templateId);
5217
+ if (adopt.has(path)) {
5218
+ adoptMatched.add(path);
5219
+ if (!idx.path.startsWith("config/")) {
5220
+ if (exists && curHash === newSource) {
5221
+ ops.push({
5222
+ action: { path, templateId, action: "unchanged", detail: "already matches the template \u2014 re-tracked" },
5223
+ entry: { templateId, path, sourceSha256: newSource, installedSha256: newSource }
5224
+ });
5225
+ } else {
5226
+ ops.push({
5227
+ action: { path, templateId, action: "adopt", detail: "re-adopted to the template \u2014 your version is backed up" },
5228
+ shippedAbs,
5229
+ destAbs,
5230
+ entry: { templateId, path, sourceSha256: newSource, installedSha256: newSource }
5231
+ });
5232
+ }
5233
+ continue;
5234
+ }
5235
+ adoptRefusedConfig.push(path);
5236
+ }
5006
5237
  if (!prior) {
5007
5238
  if (!exists) {
5008
5239
  ops.push({
@@ -5033,6 +5264,13 @@ async function runTemplatesUpdate(ctx, templatesDir, options = {}) {
5033
5264
  continue;
5034
5265
  }
5035
5266
  if (prior.installedSha256 === null) {
5267
+ if (exists && curHash === newSource) {
5268
+ ops.push({
5269
+ action: { path, templateId, action: "unchanged", detail: "matches the template \u2014 re-tracked" },
5270
+ entry: { templateId, path, sourceSha256: newSource, installedSha256: newSource }
5271
+ });
5272
+ continue;
5273
+ }
5036
5274
  ops.push({
5037
5275
  action: { path, templateId, action: "unmanaged", detail: "on-disk file diverges from the template \u2014 left untouched" },
5038
5276
  entry: { ...prior, sourceSha256: newSource }
@@ -5096,15 +5334,15 @@ async function runTemplatesUpdate(ctx, templatesDir, options = {}) {
5096
5334
  const allOps = [...ops, ...orphanOps];
5097
5335
  const appliedActions = [];
5098
5336
  const finalEntries = [];
5099
- const backupRoot = join23(ctx.dataDir, ".vortex", "backups", (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-"));
5337
+ const backupRoot = join24(ctx.dataDir, ".vortex", "backups", (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-"));
5100
5338
  let applyError = false;
5101
5339
  const writeDotNew = async (destAbs, content) => {
5102
5340
  const newPath = destAbs + ".new";
5103
- if (existsSync9(newPath)) {
5104
- if (await readFile18(newPath, "utf8") === content)
5341
+ if (existsSync10(newPath)) {
5342
+ if (await readFile19(newPath, "utf8") === content)
5105
5343
  return void 0;
5106
- const backupAbs = join23(backupRoot, toPosix(relative4(ctx.repoRoot, newPath)));
5107
- await mkdir6(dirname4(backupAbs), { recursive: true });
5344
+ const backupAbs = join24(backupRoot, toPosix(relative4(ctx.repoRoot, newPath)));
5345
+ await mkdir7(dirname4(backupAbs), { recursive: true });
5108
5346
  await copyFile(newPath, backupAbs);
5109
5347
  await atomicWriteFile(newPath, content);
5110
5348
  return toPosix(relative4(ctx.repoRoot, backupAbs));
@@ -5118,16 +5356,16 @@ async function runTemplatesUpdate(ctx, templatesDir, options = {}) {
5118
5356
  if (!dryRun && op.shippedAbs && op.destAbs) {
5119
5357
  const destAbs = op.destAbs;
5120
5358
  try {
5121
- const content = await readFile18(op.shippedAbs, "utf8");
5359
+ const content = await readFile19(op.shippedAbs, "utf8");
5122
5360
  const newSource = op.entry ? op.entry.sourceSha256 : sha256(content);
5123
5361
  if (action.action === "replace") {
5124
5362
  const prior = ownByTemplateId.get(action.templateId);
5125
- if (!existsSync9(destAbs)) {
5126
- await mkdir6(dirname4(destAbs), { recursive: true });
5363
+ if (!existsSync10(destAbs)) {
5364
+ await mkdir7(dirname4(destAbs), { recursive: true });
5127
5365
  await atomicWriteFile(destAbs, content);
5128
5366
  } else if (await sha256File(destAbs) === (prior?.installedSha256 ?? null)) {
5129
- const backupAbs = join23(backupRoot, action.path);
5130
- await mkdir6(dirname4(backupAbs), { recursive: true });
5367
+ const backupAbs = join24(backupRoot, action.path);
5368
+ await mkdir7(dirname4(backupAbs), { recursive: true });
5131
5369
  await copyFile(destAbs, backupAbs);
5132
5370
  await atomicWriteFile(destAbs, content);
5133
5371
  action = { ...action, backupPath: toPosix(relative4(ctx.repoRoot, backupAbs)) };
@@ -5144,7 +5382,7 @@ async function runTemplatesUpdate(ctx, templatesDir, options = {}) {
5144
5382
  entry = { templateId: action.templateId, path: action.path, sourceSha256: newSource, installedSha256: prior?.installedSha256 ?? null };
5145
5383
  }
5146
5384
  } else if (action.action === "restore" || action.action === "install") {
5147
- if (existsSync9(destAbs) && await sha256File(destAbs) !== newSource) {
5385
+ if (existsSync10(destAbs) && await sha256File(destAbs) !== newSource) {
5148
5386
  const backupPath = await writeDotNew(destAbs, content);
5149
5387
  action = {
5150
5388
  path: action.path,
@@ -5156,14 +5394,23 @@ async function runTemplatesUpdate(ctx, templatesDir, options = {}) {
5156
5394
  };
5157
5395
  entry = { templateId: action.templateId, path: action.path, sourceSha256: newSource, installedSha256: null };
5158
5396
  } else {
5159
- await mkdir6(dirname4(destAbs), { recursive: true });
5397
+ await mkdir7(dirname4(destAbs), { recursive: true });
5160
5398
  await atomicWriteFile(destAbs, content);
5161
5399
  }
5162
5400
  } else if (action.action === "conflict") {
5163
- await mkdir6(dirname4(destAbs), { recursive: true });
5401
+ await mkdir7(dirname4(destAbs), { recursive: true });
5164
5402
  const backupPath = await writeDotNew(destAbs, content);
5165
5403
  if (backupPath)
5166
5404
  action = { ...action, backupPath };
5405
+ } else if (action.action === "adopt") {
5406
+ if (existsSync10(destAbs)) {
5407
+ const backupAbs = join24(backupRoot, action.path);
5408
+ await mkdir7(dirname4(backupAbs), { recursive: true });
5409
+ await copyFile(destAbs, backupAbs);
5410
+ action = { ...action, backupPath: toPosix(relative4(ctx.repoRoot, backupAbs)) };
5411
+ }
5412
+ await mkdir7(dirname4(destAbs), { recursive: true });
5413
+ await atomicWriteFile(destAbs, content);
5167
5414
  }
5168
5415
  } catch (e) {
5169
5416
  applyError = true;
@@ -5189,11 +5436,11 @@ async function runTemplatesUpdate(ctx, templatesDir, options = {}) {
5189
5436
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
5190
5437
  files: newEntries
5191
5438
  };
5192
- await mkdir6(join23(ctx.dataDir, ".vortex"), { recursive: true });
5439
+ await mkdir7(join24(ctx.dataDir, ".vortex"), { recursive: true });
5193
5440
  await atomicWriteFile(ownershipManifestPath(ctx), JSON.stringify(manifest, null, 2) + "\n");
5194
5441
  }
5195
5442
  const summary = summarize(appliedActions);
5196
- const changes = summary.replaced + summary.restored + summary.installed;
5443
+ const changes = summary.replaced + summary.restored + summary.installed + summary.adopted;
5197
5444
  const status = dryRun ? "dry-run" : summary.conflicts > 0 ? "conflicts" : changes > 0 ? "updated" : "ok";
5198
5445
  return {
5199
5446
  ...base,
@@ -5202,11 +5449,14 @@ async function runTemplatesUpdate(ctx, templatesDir, options = {}) {
5202
5449
  toVersion,
5203
5450
  actions: appliedActions,
5204
5451
  summary,
5205
- nextActions: buildNextActions(status, summary, appliedActions, dryRun, fromVersion, toVersion)
5452
+ nextActions: buildNextActions(status, summary, appliedActions, dryRun, fromVersion, toVersion, {
5453
+ adoptRefusedConfig,
5454
+ adoptUnknown: [...adopt].filter((p) => !adoptMatched.has(p))
5455
+ })
5206
5456
  };
5207
5457
  }
5208
5458
  function emptySummary() {
5209
- return { replaced: 0, restored: 0, installed: 0, conflicts: 0, unchanged: 0, unmanaged: 0, locallyModified: 0, removedUpstream: 0, errors: 0 };
5459
+ return { replaced: 0, restored: 0, installed: 0, conflicts: 0, unchanged: 0, unmanaged: 0, adopted: 0, locallyModified: 0, removedUpstream: 0, errors: 0 };
5210
5460
  }
5211
5461
  function summarize(actions) {
5212
5462
  const s = {
@@ -5216,6 +5466,7 @@ function summarize(actions) {
5216
5466
  conflicts: 0,
5217
5467
  unchanged: 0,
5218
5468
  unmanaged: 0,
5469
+ adopted: 0,
5219
5470
  locallyModified: 0,
5220
5471
  removedUpstream: 0,
5221
5472
  errors: 0
@@ -5237,6 +5488,8 @@ function summarize(actions) {
5237
5488
  s.unchanged++;
5238
5489
  else if (a.action === "unmanaged")
5239
5490
  s.unmanaged++;
5491
+ else if (a.action === "adopt")
5492
+ s.adopted++;
5240
5493
  else if (a.action === "locally-modified")
5241
5494
  s.locallyModified++;
5242
5495
  else if (a.action === "removed-upstream")
@@ -5244,15 +5497,18 @@ function summarize(actions) {
5244
5497
  }
5245
5498
  return s;
5246
5499
  }
5247
- function buildNextActions(status, summary, actions, dryRun, fromVersion, toVersion) {
5500
+ function buildNextActions(status, summary, actions, dryRun, fromVersion, toVersion, adopt = {
5501
+ adoptRefusedConfig: [],
5502
+ adoptUnknown: []
5503
+ }) {
5248
5504
  const out = [];
5249
5505
  if (dryRun) {
5250
- const changes = summary.replaced + summary.restored + summary.installed;
5506
+ const changes = summary.replaced + summary.restored + summary.installed + summary.adopted;
5251
5507
  out.push(changes + summary.conflicts === 0 ? `Dry run: templates already current (base ${toVersion}). Nothing would change.` : `Dry run (base ${fromVersion} \u2192 ${toVersion}): would update ${changes}, conflict ${summary.conflicts}. Re-run without --dry-run to apply.`);
5252
5508
  } else if (status === "ok") {
5253
5509
  out.push(`Templates already current (base ${toVersion}). Nothing to do.`);
5254
5510
  } else if (status === "updated") {
5255
- const n = summary.replaced + summary.restored + summary.installed;
5511
+ const n = summary.replaced + summary.restored + summary.installed + summary.adopted;
5256
5512
  out.push(summary.errors > 0 ? `Refreshed ${n} framework file(s); ${summary.errors} could not be applied \u2014 base stays ${fromVersion} until those resolve. Backups under data/.vortex/backups/.` : `Refreshed ${n} framework file(s) to base ${toVersion}. Backups under data/.vortex/backups/.`);
5257
5513
  } else if (status === "conflicts") {
5258
5514
  out.push(`Updated what was safe; ${summary.conflicts} file(s) you edited were left untouched \u2014 the new template is alongside as \`<file>.new\`.`);
@@ -5261,11 +5517,29 @@ function buildNextActions(status, summary, actions, dryRun, fromVersion, toVersi
5261
5517
  out.push(` conflict: ${a.path} \u2014 review ${a.newFilePath} and merge by hand.`);
5262
5518
  }
5263
5519
  }
5520
+ if (summary.adopted > 0) {
5521
+ out.push(dryRun ? `Would re-adopt ${summary.adopted} file(s) to the template (your versions would be backed up). Re-run without --dry-run to apply.` : `Re-adopted ${summary.adopted} file(s) to the template \u2014 your prior versions are backed up under data/.vortex/backups/.`);
5522
+ }
5264
5523
  if (summary.errors > 0) {
5265
5524
  out.push(`\u26A0\uFE0F ${summary.errors} file(s) could not be applied (left unchanged) \u2014 see the per-file \`error\`. Re-run after resolving; your files are intact.`);
5266
5525
  }
5267
- if (summary.unmanaged > 0)
5268
- out.push(`${summary.unmanaged} file(s) diverge from the template and are not VortEX-managed \u2014 skipped.`);
5526
+ const unmanaged = actions.filter((a) => a.action === "unmanaged");
5527
+ const adoptable = unmanaged.filter((a) => !a.path.startsWith(".agent/"));
5528
+ const userOwned = unmanaged.filter((a) => a.path.startsWith(".agent/"));
5529
+ if (adoptable.length > 0) {
5530
+ out.push(`${adoptable.length} framework file(s) diverge from the template and are NOT getting updates \u2014 run \`vortex update --adopt <path>\` to refresh one to the template (your version is backed up first):`);
5531
+ for (const a of adoptable)
5532
+ out.push(` diverged: ${a.path}`);
5533
+ }
5534
+ if (userOwned.length > 0) {
5535
+ out.push(`${userOwned.length} user-owned file(s) (your settings) are intentionally not updated \u2014 new options fall back to defaults.`);
5536
+ }
5537
+ for (const p of adopt.adoptRefusedConfig) {
5538
+ out.push(`--adopt ${p}: refused \u2014 that is your settings file; adopting would wipe your settings, and new options already fall back to defaults.`);
5539
+ }
5540
+ for (const p of adopt.adoptUnknown) {
5541
+ out.push(`--adopt ${p}: not a framework-managed file \u2014 nothing to adopt.`);
5542
+ }
5269
5543
  if (summary.locallyModified > 0)
5270
5544
  out.push(`${summary.locallyModified} file(s) you edited are unchanged upstream \u2014 kept as-is.`);
5271
5545
  return out;
@@ -5284,7 +5558,7 @@ var vortexCommand = {
5284
5558
  }
5285
5559
  ],
5286
5560
  handler: async (input) => {
5287
- const tokens = tokenize(input.rest);
5561
+ const tokens = input.argv ?? tokenize(input.rest);
5288
5562
  const sub = tokens[0] ?? "help";
5289
5563
  const restAfterSub = tokens.slice(1);
5290
5564
  if (sub === "init")
@@ -5299,6 +5573,8 @@ var vortexCommand = {
5299
5573
  return runUpdate(input, restAfterSub);
5300
5574
  if (sub === "sync")
5301
5575
  return runSync(input, restAfterSub);
5576
+ if (sub === "global-setup")
5577
+ return runGlobalSetup(input, restAfterSub);
5302
5578
  if (sub === "help" || sub === "")
5303
5579
  return runHelp();
5304
5580
  if (PLANNED_SUBS.includes(sub)) {
@@ -5342,7 +5618,7 @@ function runHelp() {
5342
5618
  },
5343
5619
  {
5344
5620
  name: "update",
5345
- description: "Refresh framework-owned templates (routers, slash-command prompts, config) from the installed package \u2014 hash-guarded so files you edited are never overwritten (a `<file>.new` is written instead). Pass --dry-run to preview. Local; no network.",
5621
+ description: "Refresh framework-owned templates (routers, slash-command prompts, config) from the installed package \u2014 hash-guarded so files you edited are never overwritten (a `<file>.new` is written instead). Pass --dry-run to preview. Pass --adopt <path> (repeatable) to force a file you edited back to the template and re-track it (your version is backed up; config is refused). Local; no network.",
5346
5622
  state: "active"
5347
5623
  },
5348
5624
  {
@@ -5350,6 +5626,11 @@ function runHelp() {
5350
5626
  description: "Framework-developer workflow: git pull \u2192 npm install \u2192 npm run build \u2192 npm run verify. Stops on first failure. End users on npm registry do not need this.",
5351
5627
  state: "active"
5352
5628
  },
5629
+ {
5630
+ name: "global-setup",
5631
+ description: "Enable VortEX from ANY folder: merge a SessionStart/SessionEnd hook + instance pointer into your global ~/.claude (settings.json + vortex-global.json) and add a pointer block to ~/.claude/CLAUDE.md. Merge-safe and idempotent. Pass --decline to dismiss the offer so it stops appearing.",
5632
+ state: "active"
5633
+ },
5353
5634
  { name: "help", description: "Show this list.", state: "active" }
5354
5635
  ],
5355
5636
  siblingCommands: [
@@ -5372,18 +5653,19 @@ function runHelp() {
5372
5653
  ]
5373
5654
  };
5374
5655
  }
5656
+ var DEFAULT_INIT_TASK = "Setting up my VortEX instance";
5375
5657
  function resolveTemplatesDir() {
5376
5658
  const here = dirname5(fileURLToPath(import.meta.url));
5377
5659
  const candidates = [
5378
- join24(here, "..", "..", "templates"),
5660
+ join25(here, "..", "..", "templates"),
5379
5661
  // session-rituals: dist/commands -> templates
5380
- join24(here, "..", "templates"),
5662
+ join25(here, "..", "templates"),
5381
5663
  // base aggregate: dist -> templates
5382
- join24(here, "templates")
5664
+ join25(here, "templates")
5383
5665
  // defensive: alongside the bundle
5384
5666
  ];
5385
5667
  for (const c of candidates) {
5386
- if (existsSync10(join24(c, "commands")) || existsSync10(join24(c, "routers")))
5668
+ if (existsSync11(join25(c, "commands")) || existsSync11(join25(c, "routers")))
5387
5669
  return c;
5388
5670
  }
5389
5671
  return null;
@@ -5391,19 +5673,19 @@ function resolveTemplatesDir() {
5391
5673
  async function installCommandTemplates(repoRoot, templatesDir) {
5392
5674
  if (!templatesDir)
5393
5675
  return [];
5394
- const commandsDir = join24(templatesDir, "commands");
5395
- if (!existsSync10(commandsDir))
5676
+ const commandsDir = join25(templatesDir, "commands");
5677
+ if (!existsSync11(commandsDir))
5396
5678
  return [];
5397
- const destDir = join24(repoRoot, ".claude", "commands");
5398
- await mkdir7(destDir, { recursive: true });
5679
+ const destDir = join25(repoRoot, ".claude", "commands");
5680
+ await mkdir8(destDir, { recursive: true });
5399
5681
  const written = [];
5400
5682
  for (const name of await readdir15(commandsDir)) {
5401
5683
  if (!name.endsWith(".md"))
5402
5684
  continue;
5403
- const dest = join24(destDir, name);
5404
- if (existsSync10(dest))
5685
+ const dest = join25(destDir, name);
5686
+ if (existsSync11(dest))
5405
5687
  continue;
5406
- await copyFile2(join24(commandsDir, name), dest);
5688
+ await copyFile2(join25(commandsDir, name), dest);
5407
5689
  written.push(dest);
5408
5690
  }
5409
5691
  return written;
@@ -5418,16 +5700,16 @@ var ROUTER_FILES = [
5418
5700
  async function installRouterTemplates(repoRoot, templatesDir) {
5419
5701
  if (!templatesDir)
5420
5702
  return [];
5421
- const routersDir = join24(templatesDir, "routers");
5422
- if (!existsSync10(routersDir))
5703
+ const routersDir = join25(templatesDir, "routers");
5704
+ if (!existsSync11(routersDir))
5423
5705
  return [];
5424
5706
  const written = [];
5425
5707
  for (const name of ROUTER_FILES) {
5426
- const src = join24(routersDir, name);
5427
- if (!existsSync10(src))
5708
+ const src = join25(routersDir, name);
5709
+ if (!existsSync11(src))
5428
5710
  continue;
5429
- const dest = join24(repoRoot, name);
5430
- if (existsSync10(dest))
5711
+ const dest = join25(repoRoot, name);
5712
+ if (existsSync11(dest))
5431
5713
  continue;
5432
5714
  await copyFile2(src, dest);
5433
5715
  written.push(dest);
@@ -5436,15 +5718,15 @@ async function installRouterTemplates(repoRoot, templatesDir) {
5436
5718
  }
5437
5719
  async function seedInstanceConfig(repoRoot, templatesDir) {
5438
5720
  const written = [];
5439
- const agentDir = join24(repoRoot, ".agent");
5440
- const vortexJson = join24(agentDir, "vortex.json");
5441
- if (!existsSync10(vortexJson)) {
5442
- await mkdir7(agentDir, { recursive: true });
5443
- const tmpl = templatesDir ? join24(templatesDir, "config", "vortex.json") : null;
5444
- if (tmpl && existsSync10(tmpl)) {
5721
+ const agentDir = join25(repoRoot, ".agent");
5722
+ const vortexJson = join25(agentDir, "vortex.json");
5723
+ if (!existsSync11(vortexJson)) {
5724
+ await mkdir8(agentDir, { recursive: true });
5725
+ const tmpl = templatesDir ? join25(templatesDir, "config", "vortex.json") : null;
5726
+ if (tmpl && existsSync11(tmpl)) {
5445
5727
  await copyFile2(tmpl, vortexJson);
5446
5728
  } else {
5447
- await writeFile10(vortexJson, JSON.stringify({
5729
+ await writeFile11(vortexJson, JSON.stringify({
5448
5730
  autoRecord: {
5449
5731
  sessionStart: true,
5450
5732
  worklog: true,
@@ -5459,9 +5741,9 @@ async function seedInstanceConfig(repoRoot, templatesDir) {
5459
5741
  }
5460
5742
  written.push(vortexJson);
5461
5743
  }
5462
- const pkgPath = join24(repoRoot, "package.json");
5463
- if (!existsSync10(pkgPath)) {
5464
- await writeFile10(pkgPath, JSON.stringify({
5744
+ const pkgPath = join25(repoRoot, "package.json");
5745
+ if (!existsSync11(pkgPath)) {
5746
+ await writeFile11(pkgPath, JSON.stringify({
5465
5747
  name: "vortex-instance",
5466
5748
  version: "0.0.0",
5467
5749
  private: true,
@@ -5478,9 +5760,9 @@ async function runInit(input, tokens) {
5478
5760
  const templatesDir = resolveTemplatesDir();
5479
5761
  const requiredDirs = ["_memory", "worklog", "decision-log", "hubs", "inbox", "runbooks"];
5480
5762
  for (const d2 of requiredDirs) {
5481
- const p = join24(dataDir, d2);
5482
- if (!existsSync10(p))
5483
- await mkdir7(p, { recursive: true });
5763
+ const p = join25(dataDir, d2);
5764
+ if (!existsSync11(p))
5765
+ await mkdir8(p, { recursive: true });
5484
5766
  }
5485
5767
  const scaffolded = [];
5486
5768
  try {
@@ -5491,8 +5773,8 @@ async function runInit(input, tokens) {
5491
5773
  scaffolded.push(...await seedInstanceConfig(repoRoot, templatesDir));
5492
5774
  } catch {
5493
5775
  }
5494
- const profilePath = join24(dataDir, "_memory", "user_profile.md");
5495
- if (existsSync10(profilePath) && !args.force) {
5776
+ const profilePath = join25(dataDir, "_memory", "user_profile.md");
5777
+ if (existsSync11(profilePath) && !args.force) {
5496
5778
  const manifestNotes = [];
5497
5779
  try {
5498
5780
  const m2 = await writeOwnershipManifest(input.context, templatesDir);
@@ -5517,24 +5799,18 @@ async function runInit(input, tokens) {
5517
5799
  };
5518
5800
  }
5519
5801
  const missing = [];
5520
- if (!args.name) {
5802
+ if (!args.name?.trim()) {
5521
5803
  missing.push({
5522
5804
  name: "name",
5523
5805
  prompt: 'What name or handle should VortEX use for you? (e.g. "Alex" or "team-lead")'
5524
5806
  });
5525
5807
  }
5526
- if (!args.role) {
5808
+ if (!args.role?.trim()) {
5527
5809
  missing.push({
5528
5810
  name: "role",
5529
5811
  prompt: 'What is your main role in one word? (e.g. "engineer", "researcher", "writer")'
5530
5812
  });
5531
5813
  }
5532
- if (!args.task) {
5533
- missing.push({
5534
- name: "task",
5535
- prompt: "What is one thing you're working on right now? (one sentence \u2014 this becomes your first worklog seed)"
5536
- });
5537
- }
5538
5814
  if (missing.length > 0) {
5539
5815
  return {
5540
5816
  subcommand: "init",
@@ -5543,7 +5819,8 @@ async function runInit(input, tokens) {
5543
5819
  missingInputs: missing,
5544
5820
  nextActions: [
5545
5821
  "Ask the user the prompts in `missingInputs`, then re-run with the answers:",
5546
- ' /vortex init --name "<name>" --role "<role>" --task "<task>"',
5822
+ ' /vortex init --name "<name>" --role "<role>"',
5823
+ 'Optional: add --task "<one sentence>" to seed your first worklog; omit it and it defaults to "Setting up my VortEX instance".',
5547
5824
  "Optional: append `--force` to overwrite an already-initialized instance."
5548
5825
  ]
5549
5826
  };
@@ -5555,22 +5832,25 @@ async function runInit(input, tokens) {
5555
5832
  const names = scaffolded.map((p) => p.replace(repoRoot, ".").replace(/\\/g, "/"));
5556
5833
  scaffoldNotes.push(`Seeded instance scaffolding: ${names.join(", ")}.`);
5557
5834
  }
5558
- await writeFile10(profilePath, renderUserProfile(args.name, args.role, args.task, today2), "utf8");
5835
+ const name = args.name.trim();
5836
+ const role = args.role.trim();
5837
+ const task = args.task?.trim() || DEFAULT_INIT_TASK;
5838
+ await writeFile11(profilePath, renderUserProfile(name, role, task, today2), "utf8");
5559
5839
  created.push(profilePath);
5560
5840
  const [year, month] = today2.split("-");
5561
- const worklogDir = join24(dataDir, "worklog", year, month);
5562
- await mkdir7(worklogDir, { recursive: true });
5563
- const worklogPath = join24(worklogDir, `${today2}-vortex-init.md`);
5564
- await writeFile10(worklogPath, renderFirstWorklog(args.name, args.role, args.task, today2), "utf8");
5841
+ const worklogDir = join25(dataDir, "worklog", year, month);
5842
+ await mkdir8(worklogDir, { recursive: true });
5843
+ const worklogPath = join25(worklogDir, `${today2}-vortex-init.md`);
5844
+ await writeFile11(worklogPath, renderFirstWorklog(name, role, task, today2), "utf8");
5565
5845
  created.push(worklogPath);
5566
5846
  const hookNotes = [];
5567
5847
  try {
5568
- const settingsPath = join24(input.context.repoRoot, ".claude", "settings.json");
5569
- const existingText = existsSync10(settingsPath) ? await readFile19(settingsPath, "utf8") : null;
5848
+ const settingsPath = join25(input.context.repoRoot, ".claude", "settings.json");
5849
+ const existingText = existsSync11(settingsPath) ? await readFile20(settingsPath, "utf8") : null;
5570
5850
  const { settings, added, alreadyWired } = ensureVortexHooks(parseSettings(existingText));
5571
5851
  if (!alreadyWired) {
5572
- await mkdir7(join24(input.context.repoRoot, ".claude"), { recursive: true });
5573
- await writeFile10(settingsPath, serializeSettings(settings), "utf8");
5852
+ await mkdir8(join25(input.context.repoRoot, ".claude"), { recursive: true });
5853
+ await writeFile11(settingsPath, serializeSettings(settings), "utf8");
5574
5854
  created.push(settingsPath);
5575
5855
  hookNotes.push(`Wired ${added.join(" + ")} hook(s) into .claude/settings.json \u2014 the VortEX boot report runs automatically at session start.`);
5576
5856
  } else {
@@ -5606,8 +5886,10 @@ async function runInit(input, tokens) {
5606
5886
  " /log <one-line update> \u2014 append a section to today's worklog",
5607
5887
  " /decision <slug> <title> \u2014 record a decision",
5608
5888
  " /session-start \u2014 daily start-of-session report",
5609
- `Open ${worklogPath} to see your first worklog \u2014 it already names "${args.task}".`,
5610
- "Hubs grow organically: once 3+ categories accumulate on the same topic, create `hubs/_HUB-<topic>.md` to cross-link them."
5889
+ `Open ${worklogPath} to see your first worklog \u2014 it already names "${task}".`,
5890
+ "Hubs grow organically: once 3+ categories accumulate on the same topic, create `hubs/_HUB-<topic>.md` to cross-link them.",
5891
+ "",
5892
+ "\u{1F310} Optional \u2014 use VortEX from ANY folder, not just this one? Run `vortex global-setup` (adds an instance pointer + session hook to your global ~/.claude, merge-safe). Skip with `vortex global-setup --decline`."
5611
5893
  ];
5612
5894
  const importPrompt = [];
5613
5895
  if (externalFolders && externalFolders.length > 0) {
@@ -5630,10 +5912,77 @@ async function runInit(input, tokens) {
5630
5912
  nextActions: [...baseNext, ...importPrompt]
5631
5913
  };
5632
5914
  }
5915
+ async function runGlobalSetup(input, tokens) {
5916
+ const instanceRoot = input.context.repoRoot;
5917
+ if (tokens.includes("--decline")) {
5918
+ try {
5919
+ const statePath = await recordGlobalSetupDecline();
5920
+ return {
5921
+ subcommand: "global-setup",
5922
+ status: "declined",
5923
+ created: [],
5924
+ modified: [statePath],
5925
+ skipped: [],
5926
+ nextActions: [
5927
+ "Noted \u2014 VortEX will not offer global usage again.",
5928
+ "Changed your mind later? Run `vortex global-setup` any time to enable it."
5929
+ ]
5930
+ };
5931
+ } catch (e) {
5932
+ return globalSetupError(`Could not record the decline: ${e?.message ?? e}`);
5933
+ }
5934
+ }
5935
+ if (!isSafeInstanceRoot(instanceRoot)) {
5936
+ return globalSetupError(`Refusing to enable global usage: "${instanceRoot}" is not a valid initialized VortEX instance (need an absolute path containing .agent/vortex.json or data/_memory/user_profile.md, and no newlines). Run this from your instance folder, or pass VORTEX_REPO_ROOT=<instance path>.`);
5937
+ }
5938
+ try {
5939
+ const { created, modified, skipped } = await applyGlobalSetup({ instanceRoot });
5940
+ const changed = created.length + modified.length;
5941
+ return {
5942
+ subcommand: "global-setup",
5943
+ status: "completed",
5944
+ created,
5945
+ modified,
5946
+ skipped,
5947
+ nextActions: [
5948
+ changed > 0 ? "Global usage enabled \u2014 VortEX now works from any folder." : "Global usage was already set up \u2014 nothing to change.",
5949
+ "Open a NEW agent session (in any folder) so the global rules + session hook load.",
5950
+ `Records still go to this instance: ${instanceRoot}`,
5951
+ "Note: the global session hook fires only where `@vortex-os/base` resolves. If a folder has no local install and the report doesn't appear, run `npm i -g @vortex-os/base` once \u2014 the ~/.claude/CLAUDE.md pointer works regardless.",
5952
+ "Undo: delete the VortEX block from ~/.claude/CLAUDE.md and the VortEX hooks from ~/.claude/settings.json."
5953
+ ]
5954
+ };
5955
+ } catch (e) {
5956
+ return globalSetupError(`Global setup failed: ${e?.message ?? e}. Your ~/.claude was not left mis-routing (the pointer is written before the hook).`);
5957
+ }
5958
+ }
5959
+ function globalSetupError(message) {
5960
+ return {
5961
+ subcommand: "global-setup",
5962
+ status: "error",
5963
+ created: [],
5964
+ modified: [],
5965
+ skipped: [],
5966
+ nextActions: [message]
5967
+ };
5968
+ }
5633
5969
  async function runUpdate(input, tokens) {
5634
5970
  const dryRun = tokens.includes("--dry-run");
5971
+ const adopt = parseAdoptArgs(tokens);
5635
5972
  const templatesDir = resolveTemplatesDir();
5636
- return runTemplatesUpdate(input.context, templatesDir, { dryRun });
5973
+ return runTemplatesUpdate(input.context, templatesDir, {
5974
+ dryRun,
5975
+ adopt: adopt.size > 0 ? adopt : void 0
5976
+ });
5977
+ }
5978
+ function parseAdoptArgs(tokens) {
5979
+ const adopt = /* @__PURE__ */ new Set();
5980
+ for (let i = 0; i < tokens.length; i++) {
5981
+ if (tokens[i] === "--adopt" && i + 1 < tokens.length) {
5982
+ adopt.add(tokens[++i].replace(/\\/g, "/").replace(/^\.\//, ""));
5983
+ }
5984
+ }
5985
+ return adopt;
5637
5986
  }
5638
5987
  function parseInitArgs(tokens) {
5639
5988
  const args = {};
@@ -5749,18 +6098,18 @@ var COUNT_KEY_TO_DIR = {
5749
6098
  };
5750
6099
  async function runStatus(input) {
5751
6100
  const { dataDir } = input.context;
5752
- const profilePath = join24(dataDir, "_memory", "user_profile.md");
5753
- const initialized = existsSync10(profilePath);
6101
+ const profilePath = join25(dataDir, "_memory", "user_profile.md");
6102
+ const initialized = existsSync11(profilePath);
5754
6103
  const counts = {
5755
- memory: await safeCount(join24(dataDir, "_memory"), false),
5756
- worklog: await safeCount(join24(dataDir, "worklog"), true),
5757
- decisionLog: await safeCount(join24(dataDir, "decision-log"), false),
5758
- runbooks: await safeCount(join24(dataDir, "runbooks"), false),
5759
- hubs: await safeCount(join24(dataDir, "hubs"), false)
6104
+ memory: await safeCount(join25(dataDir, "_memory"), false),
6105
+ worklog: await safeCount(join25(dataDir, "worklog"), true),
6106
+ decisionLog: await safeCount(join25(dataDir, "decision-log"), false),
6107
+ runbooks: await safeCount(join25(dataDir, "runbooks"), false),
6108
+ hubs: await safeCount(join25(dataDir, "hubs"), false)
5760
6109
  };
5761
6110
  let latestWorklog;
5762
6111
  try {
5763
- const store = new WorklogStore(join24(dataDir, "worklog"));
6112
+ const store = new WorklogStore(join25(dataDir, "worklog"));
5764
6113
  const latest = await store.getLatest();
5765
6114
  if (latest) {
5766
6115
  latestWorklog = {
@@ -5774,7 +6123,7 @@ async function runStatus(input) {
5774
6123
  let profile;
5775
6124
  if (initialized) {
5776
6125
  try {
5777
- const raw = await readFile19(profilePath, "utf8");
6126
+ const raw = await readFile20(profilePath, "utf8");
5778
6127
  const { body } = parseFrontmatter(raw);
5779
6128
  profile = extractProfile(body);
5780
6129
  } catch {
@@ -5787,13 +6136,13 @@ async function runStatus(input) {
5787
6136
  for (const [key, count] of Object.entries(counts)) {
5788
6137
  if (count === 0) {
5789
6138
  const dirName = COUNT_KEY_TO_DIR[key];
5790
- const dirPath = join24(dataDir, dirName);
5791
- missing.push(existsSync10(dirPath) ? `${dirName}/ is empty` : `${dirName}/ does not exist`);
6139
+ const dirPath = join25(dataDir, dirName);
6140
+ missing.push(existsSync11(dirPath) ? `${dirName}/ is empty` : `${dirName}/ does not exist`);
5792
6141
  }
5793
6142
  }
5794
6143
  const nextActions = [];
5795
6144
  if (!initialized) {
5796
- nextActions.push("Run `/vortex init --name <name> --role <role> --task <task>` to set up this instance.");
6145
+ nextActions.push('Run `/vortex init --name <name> --role <role>` to set up this instance (optional: add --task "<first focus>").');
5797
6146
  } else {
5798
6147
  nextActions.push("Run `/session-start` for a fuller session-opening report.", "Run `/log <section-title>` to append a section to today's worklog.");
5799
6148
  if (counts.decisionLog === 0) {
@@ -5823,7 +6172,7 @@ function extractProfile(body) {
5823
6172
  return out;
5824
6173
  }
5825
6174
  async function safeCount(dir, recursive) {
5826
- if (!existsSync10(dir))
6175
+ if (!existsSync11(dir))
5827
6176
  return 0;
5828
6177
  try {
5829
6178
  return await countMarkdown2(dir, recursive);
@@ -5847,7 +6196,7 @@ async function countMarkdown2(dir, recursive) {
5847
6196
  } else if (e.isDirectory() && recursive) {
5848
6197
  if (e.name.startsWith(".") || e.name.startsWith("_"))
5849
6198
  continue;
5850
- total += await countMarkdown2(join24(dir, e.name), recursive);
6199
+ total += await countMarkdown2(join25(dir, e.name), recursive);
5851
6200
  }
5852
6201
  }
5853
6202
  return total;
@@ -5982,7 +6331,7 @@ async function runImport(input, tokens) {
5982
6331
  ]
5983
6332
  };
5984
6333
  }
5985
- if (!existsSync10(args.from)) {
6334
+ if (!existsSync11(args.from)) {
5986
6335
  return {
5987
6336
  subcommand: "import",
5988
6337
  status: "source-missing",
@@ -6016,9 +6365,9 @@ async function runImport(input, tokens) {
6016
6365
  const systemDirsCreated = [];
6017
6366
  if (!args.dryRun) {
6018
6367
  for (const d2 of systemDirs) {
6019
- const p = join24(dataDir, d2);
6020
- if (!existsSync10(p)) {
6021
- await mkdir7(p, { recursive: true });
6368
+ const p = join25(dataDir, d2);
6369
+ if (!existsSync11(p)) {
6370
+ await mkdir8(p, { recursive: true });
6022
6371
  systemDirsCreated.push(d2);
6023
6372
  }
6024
6373
  }
@@ -6113,7 +6462,7 @@ async function runImport(input, tokens) {
6113
6462
  async function walkAndImport(rootSource, currentDir, dataDir, dryRun, stats) {
6114
6463
  const entries = await readdir15(currentDir, { withFileTypes: true });
6115
6464
  for (const e of entries) {
6116
- const sourcePath = join24(currentDir, e.name);
6465
+ const sourcePath = join25(currentDir, e.name);
6117
6466
  if (e.isDirectory()) {
6118
6467
  if (IMPORT_SKIP_DIRS.has(e.name.toLowerCase()))
6119
6468
  continue;
@@ -6134,7 +6483,7 @@ async function walkAndImport(rootSource, currentDir, dataDir, dryRun, stats) {
6134
6483
  continue;
6135
6484
  }
6136
6485
  stats.totalFiles++;
6137
- const raw = await readFile19(sourcePath, "utf8");
6486
+ const raw = await readFile20(sourcePath, "utf8");
6138
6487
  const parsed = parseFrontmatter(raw);
6139
6488
  const hasFrontmatter = Object.keys(parsed.frontmatter).length > 0;
6140
6489
  const category = classifyFile(sourcePath, rootSource, e.name, parsed.frontmatter);
@@ -6148,13 +6497,13 @@ async function walkAndImport(rootSource, currentDir, dataDir, dryRun, stats) {
6148
6497
  const fileStat = await stat7(sourcePath);
6149
6498
  const enhanced = enhanceFrontmatter(parsed.frontmatter, category, fileStat.birthtime, fileStat.mtime, sourcePath, rootSource);
6150
6499
  const targetPath = computeTargetPath(category, sourcePath, rootSource, dataDir, e.name);
6151
- await mkdir7(dirname5(targetPath), { recursive: true });
6500
+ await mkdir8(dirname5(targetPath), { recursive: true });
6152
6501
  const out = serializeFrontmatter({
6153
6502
  frontmatter: enhanced,
6154
6503
  body: parsed.body
6155
6504
  });
6156
6505
  try {
6157
- await writeFile10(targetPath, out, { encoding: "utf8", flag: "wx" });
6506
+ await writeFile11(targetPath, out, { encoding: "utf8", flag: "wx" });
6158
6507
  stats.copied++;
6159
6508
  } catch (e2) {
6160
6509
  if (e2.code === "EEXIST") {
@@ -6187,8 +6536,8 @@ async function importAttachment(sourcePath, relPath, filename, dataDir, dryRun,
6187
6536
  stats.importedExtensions.add(ext);
6188
6537
  if (dryRun)
6189
6538
  return;
6190
- const targetPath = join24(dataDir, relPath);
6191
- await mkdir7(dirname5(targetPath), { recursive: true });
6539
+ const targetPath = join25(dataDir, relPath);
6540
+ await mkdir8(dirname5(targetPath), { recursive: true });
6192
6541
  try {
6193
6542
  await copyFile2(sourcePath, targetPath, constants.COPYFILE_EXCL);
6194
6543
  stats.attachmentsCopied++;
@@ -6251,27 +6600,27 @@ function computeTargetPath(category, sourcePath, rootSource, dataDir, filename)
6251
6600
  const mdName = withMdExtension(filename);
6252
6601
  if (category === "preserved") {
6253
6602
  const relPath = withMdExtension(sourcePath.substring(rootSource.length).replace(/^[/\\]/, ""));
6254
- return join24(dataDir, relPath);
6603
+ return join25(dataDir, relPath);
6255
6604
  }
6256
6605
  if (category === "worklog") {
6257
6606
  const match = mdName.match(/^(\d{4})-(\d{2})-/);
6258
6607
  if (match) {
6259
- return join24(dataDir, "worklog", match[1], match[2], mdName);
6608
+ return join25(dataDir, "worklog", match[1], match[2], mdName);
6260
6609
  }
6261
6610
  const d2 = /* @__PURE__ */ new Date();
6262
6611
  const y2 = String(d2.getFullYear());
6263
6612
  const m2 = String(d2.getMonth() + 1).padStart(2, "0");
6264
- return join24(dataDir, "worklog", y2, m2, mdName);
6613
+ return join25(dataDir, "worklog", y2, m2, mdName);
6265
6614
  }
6266
6615
  if (category === "decisionLog")
6267
- return join24(dataDir, "decision-log", mdName);
6616
+ return join25(dataDir, "decision-log", mdName);
6268
6617
  if (category === "runbooks")
6269
- return join24(dataDir, "runbooks", mdName);
6618
+ return join25(dataDir, "runbooks", mdName);
6270
6619
  if (category === "hubs")
6271
- return join24(dataDir, "hubs", mdName);
6620
+ return join25(dataDir, "hubs", mdName);
6272
6621
  if (category === "memory")
6273
- return join24(dataDir, "_memory", mdName);
6274
- return join24(dataDir, mdName);
6622
+ return join25(dataDir, "_memory", mdName);
6623
+ return join25(dataDir, mdName);
6275
6624
  }
6276
6625
  function withMdExtension(name) {
6277
6626
  const ext = extname11(name);
@@ -6418,14 +6767,14 @@ async function checkControlBytes(dataDir) {
6418
6767
  return;
6419
6768
  }
6420
6769
  for (const e of entries) {
6421
- const p = join24(dir, e.name);
6770
+ const p = join25(dir, e.name);
6422
6771
  if (e.isDirectory()) {
6423
6772
  if (SKIP_DIRS.has(e.name))
6424
6773
  continue;
6425
6774
  await walk5(p);
6426
6775
  } else if (e.isFile() && CONTROL_SCAN_EXT.has(extname11(e.name).toLowerCase())) {
6427
6776
  try {
6428
- const buf = await readFile19(p);
6777
+ const buf = await readFile20(p);
6429
6778
  for (let i = 0; i < buf.length; i++) {
6430
6779
  const x2 = buf[i];
6431
6780
  if (x2 < 32 && x2 !== 9 && x2 !== 10 && x2 !== 13) {
@@ -6452,7 +6801,7 @@ async function checkControlBytes(dataDir) {
6452
6801
  };
6453
6802
  }
6454
6803
  function checkSystemDirs(dataDir) {
6455
- const missing = DOCTOR_SYSTEM_DIRS.filter((d2) => !existsSync10(join24(dataDir, d2)));
6804
+ const missing = DOCTOR_SYSTEM_DIRS.filter((d2) => !existsSync11(join25(dataDir, d2)));
6456
6805
  if (missing.length === 0) {
6457
6806
  return {
6458
6807
  id: "system-dirs",
@@ -6468,8 +6817,8 @@ function checkSystemDirs(dataDir) {
6468
6817
  };
6469
6818
  }
6470
6819
  function checkUserProfile(dataDir) {
6471
- const profilePath = join24(dataDir, "_memory", "user_profile.md");
6472
- if (existsSync10(profilePath)) {
6820
+ const profilePath = join25(dataDir, "_memory", "user_profile.md");
6821
+ if (existsSync11(profilePath)) {
6473
6822
  return {
6474
6823
  id: "user-profile",
6475
6824
  label: "user_profile.md exists",
@@ -6486,11 +6835,11 @@ function checkUserProfile(dataDir) {
6486
6835
  async function checkIndexes(dataDir) {
6487
6836
  const missing = [];
6488
6837
  for (const d2 of DOCTOR_SYSTEM_DIRS) {
6489
- const dirPath = join24(dataDir, d2);
6490
- if (!existsSync10(dirPath))
6838
+ const dirPath = join25(dataDir, d2);
6839
+ if (!existsSync11(dirPath))
6491
6840
  continue;
6492
- const indexPath = join24(dirPath, "_INDEX.md");
6493
- if (!existsSync10(indexPath))
6841
+ const indexPath = join25(dirPath, "_INDEX.md");
6842
+ if (!existsSync11(indexPath))
6494
6843
  missing.push(`${d2}/_INDEX.md`);
6495
6844
  }
6496
6845
  if (missing.length === 0) {
@@ -6523,7 +6872,7 @@ async function collectAttachmentExtensions(dataDir) {
6523
6872
  if (e.name.startsWith(".") || e.name === "_session-archive" || e.name === "node_modules") {
6524
6873
  continue;
6525
6874
  }
6526
- stack.push(join24(current, e.name));
6875
+ stack.push(join25(current, e.name));
6527
6876
  } else if (e.isFile()) {
6528
6877
  const ext = extname11(e.name);
6529
6878
  if (ext && ext.toLowerCase() !== ".md")
@@ -6620,8 +6969,8 @@ async function checkFrontmatterLint(dataDir, additionalExtensions = []) {
6620
6969
  }
6621
6970
  }
6622
6971
  async function checkRunbookAging(dataDir) {
6623
- const runbooksDir = join24(dataDir, "runbooks");
6624
- if (!existsSync10(runbooksDir)) {
6972
+ const runbooksDir = join25(dataDir, "runbooks");
6973
+ if (!existsSync11(runbooksDir)) {
6625
6974
  return {
6626
6975
  id: "runbook-aging",
6627
6976
  label: `runbooks tested within ${RUNBOOK_AGING_DAYS} days`,
@@ -6641,8 +6990,8 @@ async function checkRunbookAging(dataDir) {
6641
6990
  continue;
6642
6991
  }
6643
6992
  total++;
6644
- const filePath = join24(runbooksDir, e.name);
6645
- const raw = await readFile19(filePath, "utf8");
6993
+ const filePath = join25(runbooksDir, e.name);
6994
+ const raw = await readFile20(filePath, "utf8");
6646
6995
  const { frontmatter } = parseFrontmatter(raw);
6647
6996
  if (!frontmatter.last_tested) {
6648
6997
  stale.push(`${e.name} (no last_tested)`);
@@ -6704,8 +7053,8 @@ function checkNodeVersion() {
6704
7053
  };
6705
7054
  }
6706
7055
  async function checkGitRemote(repoRoot) {
6707
- const gitConfig = join24(repoRoot, ".git", "config");
6708
- if (!existsSync10(gitConfig)) {
7056
+ const gitConfig = join25(repoRoot, ".git", "config");
7057
+ if (!existsSync11(gitConfig)) {
6709
7058
  return {
6710
7059
  id: "git-remote",
6711
7060
  label: "git remote for sync",
@@ -6714,7 +7063,7 @@ async function checkGitRemote(repoRoot) {
6714
7063
  };
6715
7064
  }
6716
7065
  try {
6717
- const raw = await readFile19(gitConfig, "utf8");
7066
+ const raw = await readFile20(gitConfig, "utf8");
6718
7067
  const match = raw.match(/\[remote "origin"\][\s\S]*?url\s*=\s*(.+)/);
6719
7068
  if (!match) {
6720
7069
  return {
@@ -6744,11 +7093,11 @@ async function detectExternalFolders(excludePath) {
6744
7093
  if (!home)
6745
7094
  return void 0;
6746
7095
  const candidates = [
6747
- join24(home, "Documents", "obsidian-vault"),
6748
- join24(home, "Documents", "notes"),
6749
- join24(home, "Documents", "Notebook"),
6750
- join24(home, "notes"),
6751
- join24(home, "Notes")
7096
+ join25(home, "Documents", "obsidian-vault"),
7097
+ join25(home, "Documents", "notes"),
7098
+ join25(home, "Documents", "Notebook"),
7099
+ join25(home, "notes"),
7100
+ join25(home, "Notes")
6752
7101
  ];
6753
7102
  const excludeNorm = excludePath.replace(/[/\\]+$/, "");
6754
7103
  const found = [];
@@ -6757,7 +7106,7 @@ async function detectExternalFolders(excludePath) {
6757
7106
  if (candNorm === excludeNorm || candNorm.startsWith(excludeNorm + "/") || candNorm.startsWith(excludeNorm + "\\") || excludeNorm.startsWith(candNorm + "/") || excludeNorm.startsWith(candNorm + "\\")) {
6758
7107
  continue;
6759
7108
  }
6760
- if (!existsSync10(candidate))
7109
+ if (!existsSync11(candidate))
6761
7110
  continue;
6762
7111
  let mdCount = 0;
6763
7112
  try {
@@ -7094,22 +7443,22 @@ function createRitualRegistry(options) {
7094
7443
 
7095
7444
  // ../plugins/session-rituals/dist/cli-dispatch.js
7096
7445
  import { execFileSync, spawn as spawn2 } from "child_process";
7097
- import { existsSync as existsSync14, readFileSync as readFileSync3, mkdirSync, openSync, writeSync, closeSync, linkSync, rmSync, statSync } from "fs";
7446
+ import { existsSync as existsSync15, readFileSync as readFileSync4, mkdirSync, openSync, writeSync, closeSync, linkSync, rmSync, statSync } from "fs";
7098
7447
  import { createRequire } from "module";
7099
7448
  import { hostname } from "os";
7100
- import { join as join29 } from "path";
7449
+ import { isAbsolute as isAbsolute5, join as join30 } from "path";
7101
7450
 
7102
7451
  // ../plugins/session-rituals/dist/update-check.js
7103
7452
  import { execSync } from "child_process";
7104
- import { existsSync as existsSync11, readFileSync as readFileSync2 } from "fs";
7105
- import { join as join25 } from "path";
7453
+ import { existsSync as existsSync12, readFileSync as readFileSync3 } from "fs";
7454
+ import { join as join26 } from "path";
7106
7455
  var PKG = "@vortex-os/base";
7107
7456
  var NPM_TIMEOUT_MS = 4e3;
7108
7457
  function readInstalledBaseVersion(templatesDir = resolveTemplatesDir()) {
7109
7458
  if (!templatesDir)
7110
7459
  return null;
7111
7460
  try {
7112
- const m2 = JSON.parse(readFileSync2(join25(templatesDir, "manifest.json"), "utf8"));
7461
+ const m2 = JSON.parse(readFileSync3(join26(templatesDir, "manifest.json"), "utf8"));
7113
7462
  return typeof m2.baseVersion === "string" && parseCore(m2.baseVersion) ? m2.baseVersion.trim() : null;
7114
7463
  } catch {
7115
7464
  return null;
@@ -7181,8 +7530,8 @@ function isStableUpdate(latest, installed) {
7181
7530
  return compareSemver(latest, installed) === 1;
7182
7531
  }
7183
7532
  function buildInstallCommand(repoRoot) {
7184
- const has = (f) => existsSync11(join25(repoRoot, f));
7185
- const local = existsSync11(join25(repoRoot, "node_modules", "@vortex-os", "base"));
7533
+ const has = (f) => existsSync12(join26(repoRoot, f));
7534
+ const local = existsSync12(join26(repoRoot, "node_modules", "@vortex-os", "base"));
7186
7535
  let installPart;
7187
7536
  if (!local) {
7188
7537
  installPart = `npm i -g ${PKG}@latest`;
@@ -7210,9 +7559,9 @@ function checkBaseUpdate(ctx) {
7210
7559
  }
7211
7560
 
7212
7561
  // ../plugins/session-rituals/dist/session-start-report.js
7213
- import { existsSync as existsSync12 } from "fs";
7214
- import { readdir as readdir16, readFile as readFile20, stat as stat8 } from "fs/promises";
7215
- import { join as join26 } from "path";
7562
+ import { existsSync as existsSync13 } from "fs";
7563
+ import { readdir as readdir16, readFile as readFile21, stat as stat8 } from "fs/promises";
7564
+ import { join as join27 } from "path";
7216
7565
  var COUNTED_DIRS2 = ["_memory", "worklog", "decision-log"];
7217
7566
  var DEFAULT_GAP_WINDOW_DAYS = 30;
7218
7567
  var BOOT_BANNER = String.raw`
@@ -7226,8 +7575,8 @@ async function collectSessionStartReport(ctx, opts) {
7226
7575
  const counts = {};
7227
7576
  const missing = [];
7228
7577
  for (const name of COUNTED_DIRS2) {
7229
- const dir = join26(ctx.dataDir, name);
7230
- if (!existsSync12(dir)) {
7578
+ const dir = join27(ctx.dataDir, name);
7579
+ if (!existsSync13(dir)) {
7231
7580
  missing.push(name);
7232
7581
  counts[name] = 0;
7233
7582
  continue;
@@ -7237,7 +7586,7 @@ async function collectSessionStartReport(ctx, opts) {
7237
7586
  const { recent, dates, latestBody } = await scanWorklog(ctx.dataDir);
7238
7587
  const cutoff = isoDate(addDays(now, -(opts?.gapWindowDays ?? DEFAULT_GAP_WINDOW_DAYS)));
7239
7588
  const recentWorklogDates = dates.filter((d2) => d2 >= cutoff);
7240
- const mem = await scanMemoryTiers(join26(ctx.dataDir, "_memory"));
7589
+ const mem = await scanMemoryTiers(join27(ctx.dataDir, "_memory"));
7241
7590
  return {
7242
7591
  time: now.toISOString(),
7243
7592
  localTime: formatLocalTime(now),
@@ -7271,7 +7620,7 @@ async function scanMemoryTiers(memoryDir) {
7271
7620
  for (const e of entries) {
7272
7621
  if (!e.isFile() || !e.name.endsWith(".md"))
7273
7622
  continue;
7274
- const full = join26(memoryDir, e.name);
7623
+ const full = join27(memoryDir, e.name);
7275
7624
  if (e.name === "_INDEX.md") {
7276
7625
  indexExists = true;
7277
7626
  try {
@@ -7285,7 +7634,7 @@ async function scanMemoryTiers(memoryDir) {
7285
7634
  memoryCount++;
7286
7635
  try {
7287
7636
  newestMemoryMs = Math.max(newestMemoryMs, (await stat8(full)).mtimeMs);
7288
- const raw = await readFile20(full, "utf8");
7637
+ const raw = await readFile21(full, "utf8");
7289
7638
  const { frontmatter, body } = parseFrontmatter(raw);
7290
7639
  const scopeRaw = frontmatter?.["scope"];
7291
7640
  const scope = typeof scopeRaw === "string" ? scopeRaw.trim().toLowerCase() : "";
@@ -7312,9 +7661,12 @@ function detectWorklogGaps(commitDays, presentDates) {
7312
7661
  const present = new Set(presentDates);
7313
7662
  return [...new Set(commitDays)].filter((d2) => d2 && !present.has(d2)).sort();
7314
7663
  }
7664
+ function countUncommitted(porcelain) {
7665
+ return porcelain.split(/\r?\n/).filter((l3) => l3.trim().length > 0).length;
7666
+ }
7315
7667
  function renderSessionStartReport(report, extras) {
7316
7668
  const lines = [
7317
- "> [VortEX session report \u2014 injected into your context only; the user has NOT seen it. Your first reply must relay the key points in the user's language: the time, what you were doing (\u2705 recent) and what's next (\u23ED\uFE0F), any update notices (\u{1F4E6}/\u2B06\uFE0F), and any \u26A0\uFE0F warnings. Don't assume this was displayed.]",
7669
+ "> [VortEX session report \u2014 injected into your context only; the user has NOT seen it. Your first reply must relay the key points in the user's language: the time, what you were doing (\u2705 recent) and what's next (\u23ED\uFE0F), any update notices (\u{1F4E6}/\u2B06\uFE0F), any offer (\u{1F310}), any carried-over work (\u21A9\uFE0F), and any \u26A0\uFE0F warnings. Don't assume this was displayed.]",
7318
7670
  "",
7319
7671
  BOOT_BANNER,
7320
7672
  ""
@@ -7345,6 +7697,13 @@ function renderSessionStartReport(report, extras) {
7345
7697
  if (gaps.length) {
7346
7698
  lines.push(`- \u26A0\uFE0F work without a worklog: ${gaps.join(", ")} \u2014 backfill from that day's commits`);
7347
7699
  }
7700
+ const carry = extras?.carryover;
7701
+ if (carry?.interrupted) {
7702
+ lines.push(carry.interrupted === "index.lock" ? `- \u26A0\uFE0F a git lock (\`index.lock\`) is present \u2014 another git process may be running, or it is stale from a crash; remove it if nothing is using git. \`/resume\` shows what stopped.` : `- \u26A0\uFE0F interrupted git op (\`${carry.interrupted}\`) \u2014 likely a crashed prior session; finish or abort it before new work. Run \`/resume\` to see what stopped.`);
7703
+ }
7704
+ if (carry && carry.uncommitted > 0) {
7705
+ lines.push(`- \u21A9\uFE0F ${carry.uncommitted} uncommitted change(s) carried over from a prior session \u2014 run \`/resume\` if you're unsure they were meant to stay.`);
7706
+ }
7348
7707
  const cu = extras?.catchUp;
7349
7708
  if (cu && (cu.ingestedLocal > 0 || cu.indexedPulled > 0 || cu.errors > 0)) {
7350
7709
  const parts = [];
@@ -7376,11 +7735,15 @@ function renderSessionStartReport(report, extras) {
7376
7735
  if (uc && uc.newer && uc.latest) {
7377
7736
  lines.push(`- \u2B06\uFE0F update available: ${uc.package} ${uc.installed ?? "?"} \u2192 ${uc.latest} (checked ${uc.registry}) \u2014 ask the user, then to apply: ${uc.command}`);
7378
7737
  }
7738
+ if (extras?.globalSetupOffer) {
7739
+ lines.push("- \u{1F310} use VortEX from any folder? \u2014 enable with `vortex global-setup` (adds an instance pointer + session hook to your global ~/.claude, merge-safe). Ask the user once; on no, run `vortex global-setup --decline` so it stops asking.");
7740
+ }
7379
7741
  if (report.alwaysOnRules.length > 0) {
7380
- lines.push("", "\u2500\u2500\u2500 always-on rules (loaded every session) \u2500\u2500\u2500");
7742
+ lines.push("", "<always_on_rules>", "\u2500\u2500\u2500 always-on rules (loaded every session) \u2500\u2500\u2500");
7381
7743
  for (const r of report.alwaysOnRules) {
7382
7744
  lines.push("", `### ${r.slug}`, r.body + (r.truncated ? "\n\u2026(truncated \u2014 keep always-on rules short)" : ""));
7383
7745
  }
7746
+ lines.push("", "</always_on_rules>");
7384
7747
  }
7385
7748
  return lines.join("\n") + "\n";
7386
7749
  }
@@ -7399,14 +7762,14 @@ async function countMarkdown3(dir, recursive) {
7399
7762
  } else if (e.isDirectory() && recursive) {
7400
7763
  if (e.name.startsWith(".") || e.name.startsWith("_"))
7401
7764
  continue;
7402
- total += await countMarkdown3(join26(dir, e.name), recursive);
7765
+ total += await countMarkdown3(join27(dir, e.name), recursive);
7403
7766
  }
7404
7767
  }
7405
7768
  return total;
7406
7769
  }
7407
7770
  async function scanWorklog(dataDir) {
7408
- const root = join26(dataDir, "worklog");
7409
- if (!existsSync12(root))
7771
+ const root = join27(dataDir, "worklog");
7772
+ if (!existsSync13(root))
7410
7773
  return { recent: null, dates: [], latestBody: "" };
7411
7774
  let bestRel = null;
7412
7775
  const dates = /* @__PURE__ */ new Set();
@@ -7420,7 +7783,7 @@ async function scanWorklog(dataDir) {
7420
7783
  for (const e of entries) {
7421
7784
  const childRel = rel ? `${rel}/${e.name}` : e.name;
7422
7785
  if (e.isDirectory()) {
7423
- await walk5(join26(absDir, e.name), childRel);
7786
+ await walk5(join27(absDir, e.name), childRel);
7424
7787
  } else if (e.isFile()) {
7425
7788
  const m2 = e.name.match(/^(\d{4}-\d{2}-\d{2})-.+\.md$/);
7426
7789
  if (!m2)
@@ -7434,7 +7797,7 @@ async function scanWorklog(dataDir) {
7434
7797
  await walk5(root, "");
7435
7798
  if (bestRel === null)
7436
7799
  return { recent: null, dates: [...dates], latestBody: "" };
7437
- const { title, body } = await readWorklogTitleAndBody(join26(root, bestRel));
7800
+ const { title, body } = await readWorklogTitleAndBody(join27(root, bestRel));
7438
7801
  return { recent: { path: `worklog/${bestRel}`, title }, dates: [...dates], latestBody: body };
7439
7802
  }
7440
7803
  var MAX_WORKLOG_READ_BYTES = 512 * 1024;
@@ -7445,7 +7808,7 @@ async function readWorklogTitleAndBody(absPath) {
7445
7808
  if ((await stat8(absPath)).size > MAX_WORKLOG_READ_BYTES) {
7446
7809
  return { title: fromName, body: "" };
7447
7810
  }
7448
- const raw = await readFile20(absPath, "utf8");
7811
+ const raw = await readFile21(absPath, "utf8");
7449
7812
  const m2 = raw.match(/^#\s+(.+)$/m);
7450
7813
  return { title: m2 ? m2[1].trim() : fromName, body: raw };
7451
7814
  } catch {
@@ -7498,20 +7861,20 @@ function isoDate(d2) {
7498
7861
  }
7499
7862
 
7500
7863
  // ../plugins/session-rituals/dist/worklog-write.js
7501
- import { mkdir as mkdir8, writeFile as writeFile11 } from "fs/promises";
7502
- import { dirname as dirname6, join as join27 } from "path";
7864
+ import { mkdir as mkdir9, writeFile as writeFile12 } from "fs/promises";
7865
+ import { dirname as dirname6, join as join28 } from "path";
7503
7866
  async function ensureWorklogEntry(ctx, opts) {
7504
7867
  const date = isoDate2(opts?.now ?? /* @__PURE__ */ new Date());
7505
7868
  const keyword = (opts?.keyword ?? "worklog").trim() || "worklog";
7506
- const store = new WorklogStore(join27(ctx.dataDir, "worklog"));
7869
+ const store = new WorklogStore(join28(ctx.dataDir, "worklog"));
7507
7870
  const existing = await store.get(date);
7508
7871
  if (existing) {
7509
7872
  return { path: existing.path, date: existing.date, keyword: existing.keyword, created: false };
7510
7873
  }
7511
7874
  const path = store.pathFor(date, keyword);
7512
7875
  const title = opts?.title ?? `${date} worklog`;
7513
- await mkdir8(dirname6(path), { recursive: true });
7514
- await writeFile11(path, renderWorklogFile(date, title, opts?.body ?? ""), "utf8");
7876
+ await mkdir9(dirname6(path), { recursive: true });
7877
+ await writeFile12(path, renderWorklogFile(date, title, opts?.body ?? ""), "utf8");
7515
7878
  return { path, date, keyword, created: true };
7516
7879
  }
7517
7880
  function renderWorklogFile(date, title, body) {
@@ -7536,10 +7899,10 @@ function isoDate2(d2) {
7536
7899
  }
7537
7900
 
7538
7901
  // ../plugins/session-rituals/dist/curate-cli.js
7539
- import { existsSync as existsSync13 } from "fs";
7902
+ import { existsSync as existsSync14 } from "fs";
7540
7903
  import { createHash as createHash3 } from "crypto";
7541
- import { readFile as readFile21, readdir as readdir17 } from "fs/promises";
7542
- import { join as join28 } from "path";
7904
+ import { readFile as readFile22, readdir as readdir17 } from "fs/promises";
7905
+ import { join as join29 } from "path";
7543
7906
  var SYSTEM_META_DIRS3 = /* @__PURE__ */ new Set([
7544
7907
  "worklog",
7545
7908
  "decision-log",
@@ -7619,10 +7982,10 @@ function joinRel(...parts) {
7619
7982
  }
7620
7983
  async function runCurateCandidates(repoRoot, options) {
7621
7984
  const maxEntries = options?.maxEntries ?? 200;
7622
- const dataDir = join28(repoRoot, "data");
7985
+ const dataDir = join29(repoRoot, "data");
7623
7986
  const candidates = [];
7624
7987
  let truncated = false;
7625
- if (existsSync13(dataDir)) {
7988
+ if (existsSync14(dataDir)) {
7626
7989
  async function visit(absDir, relDir) {
7627
7990
  if (candidates.length >= maxEntries) {
7628
7991
  truncated = true;
@@ -7645,7 +8008,7 @@ async function runCurateCandidates(repoRoot, options) {
7645
8008
  continue;
7646
8009
  if (atRoot && (SYSTEM_META_DIRS3.has(e.name) || e.name.startsWith("_")))
7647
8010
  continue;
7648
- await visit(join28(absDir, e.name), joinRel(relDir, e.name));
8011
+ await visit(join29(absDir, e.name), joinRel(relDir, e.name));
7649
8012
  } else if (e.isFile() && e.name.endsWith(".md")) {
7650
8013
  if (NON_DOC_FILES.has(e.name))
7651
8014
  continue;
@@ -7654,7 +8017,7 @@ async function runCurateCandidates(repoRoot, options) {
7654
8017
  let topic = null;
7655
8018
  let tags = [];
7656
8019
  try {
7657
- const raw = await readFile21(join28(absDir, e.name), "utf8");
8020
+ const raw = await readFile22(join29(absDir, e.name), "utf8");
7658
8021
  const parsed = parseFrontmatter(raw);
7659
8022
  if (typeof parsed.frontmatter.topic === "string") {
7660
8023
  topic = parsed.frontmatter.topic.trim().toLowerCase();
@@ -7692,7 +8055,7 @@ async function runCuratePreview(repoRoot, payload, now = /* @__PURE__ */ new Dat
7692
8055
  };
7693
8056
  }
7694
8057
  try {
7695
- validateDataRelativePath(join28(repoRoot, "data"), v2.effectiveRelPath);
8058
+ validateDataRelativePath(join29(repoRoot, "data"), v2.effectiveRelPath);
7696
8059
  } catch (e) {
7697
8060
  return {
7698
8061
  subcommand: "curate-preview",
@@ -7709,10 +8072,10 @@ async function runCuratePreview(repoRoot, payload, now = /* @__PURE__ */ new Dat
7709
8072
  let targetExists;
7710
8073
  let wouldDo;
7711
8074
  if (payload.action === "create-file") {
7712
- targetExists = existsSync13(join28(repoRoot, "data", v2.effectiveRelPath));
8075
+ targetExists = existsSync14(join29(repoRoot, "data", v2.effectiveRelPath));
7713
8076
  wouldDo = targetExists ? `create-file at ${v2.effectiveRelPath} \u2014 but the file already EXISTS, so accept would REFUSE (no overwrite).` : `create a new document at data/${v2.effectiveRelPath}.`;
7714
8077
  } else {
7715
- targetExists = existsSync13(join28(repoRoot, "data", v2.effectiveRelPath));
8078
+ targetExists = existsSync14(join29(repoRoot, "data", v2.effectiveRelPath));
7716
8079
  wouldDo = targetExists ? `append a "## ${payload.sectionHeader}" section to data/${v2.effectiveRelPath}.` : `append-section to data/${v2.effectiveRelPath} \u2014 but the file does NOT exist, so accept would FAIL (append-section never creates).`;
7717
8080
  }
7718
8081
  const nextActions = [];
@@ -7815,7 +8178,7 @@ async function runCurateDecline(repoRoot, payload, now = /* @__PURE__ */ new Dat
7815
8178
  }
7816
8179
 
7817
8180
  // ../plugins/session-rituals/dist/cli-dispatch.js
7818
- var VORTEX_SUBCOMMANDS = ["init", "status", "import", "doctor", "update", "sync"];
8181
+ var VORTEX_SUBCOMMANDS = ["init", "status", "import", "doctor", "update", "sync", "global-setup"];
7819
8182
  async function buildRegistry() {
7820
8183
  try {
7821
8184
  const { vector } = await import("@vortex-os/memory-extended");
@@ -7825,7 +8188,16 @@ async function buildRegistry() {
7825
8188
  }
7826
8189
  }
7827
8190
  function resolveRepoRoot() {
7828
- return process.env.VORTEX_REPO_ROOT?.trim() || process.cwd();
8191
+ const override = process.env.VORTEX_REPO_ROOT?.trim();
8192
+ if (override)
8193
+ return override;
8194
+ const cwd = process.cwd();
8195
+ if (isInstanceRoot(cwd))
8196
+ return cwd;
8197
+ const pointer = readGlobalInstancePointer();
8198
+ if (pointer)
8199
+ return pointer;
8200
+ return cwd;
7829
8201
  }
7830
8202
  function requote(token) {
7831
8203
  if (!/\s/.test(token))
@@ -7834,6 +8206,11 @@ function requote(token) {
7834
8206
  return `'${token}'`;
7835
8207
  return `"${token.replace(/"/g, "")}"`;
7836
8208
  }
8209
+ function argvToSlash(argv) {
8210
+ const name = argv[0] ?? "";
8211
+ const body = argv.slice(1).map(requote).join(" ");
8212
+ return `/${name} ${body}`.trim();
8213
+ }
7837
8214
  function readCuratePayload(args) {
7838
8215
  let raw = null;
7839
8216
  for (let i = 0; i < args.length; i++) {
@@ -7843,13 +8220,13 @@ function readCuratePayload(args) {
7843
8220
  break;
7844
8221
  }
7845
8222
  if (t === "--payload-file" && i + 1 < args.length) {
7846
- raw = readFileSync3(args[++i], "utf8");
8223
+ raw = readFileSync4(args[++i], "utf8");
7847
8224
  break;
7848
8225
  }
7849
8226
  }
7850
8227
  if (raw === null) {
7851
8228
  try {
7852
- raw = readFileSync3(0, "utf8");
8229
+ raw = readFileSync4(0, "utf8");
7853
8230
  } catch {
7854
8231
  raw = "";
7855
8232
  }
@@ -7917,7 +8294,7 @@ Instance shortcuts (also available as \`/vortex <sub>\`):
7917
8294
  status \u2014 instance state report
7918
8295
  import \u2014 bring an existing notes folder into data/
7919
8296
  doctor \u2014 health diagnosis
7920
- update \u2014 refresh framework templates from the installed package (hash-guarded; --dry-run to preview)
8297
+ update \u2014 refresh framework templates from the installed package (hash-guarded; --dry-run to preview; --adopt <path> to re-adopt a file you edited)
7921
8298
 
7922
8299
  Usage: vortex <command> [args...]
7923
8300
  `);
@@ -7925,10 +8302,14 @@ Usage: vortex <command> [args...]
7925
8302
  }
7926
8303
  const name = argv[0];
7927
8304
  const isVortexSub = VORTEX_SUBCOMMANDS.includes(name);
7928
- const body = (isVortexSub ? argv : argv.slice(1)).map(requote).join(" ");
7929
- const slash = isVortexSub ? `/vortex ${body}` : `/${name} ${body}`;
7930
8305
  const context = makeContext(repoRoot);
7931
- const result = await runSlash(slash.trim(), { registry, context });
8306
+ if (isVortexSub || name === "vortex") {
8307
+ const subArgv = isVortexSub ? argv : argv.slice(1);
8308
+ const result2 = await runSlashArgv("vortex", subArgv, { registry, context });
8309
+ out(JSON.stringify(result2, null, 2) + "\n");
8310
+ return 0;
8311
+ }
8312
+ const result = await runSlash(argvToSlash(argv), { registry, context });
7932
8313
  out(JSON.stringify(result, null, 2) + "\n");
7933
8314
  return 0;
7934
8315
  } catch (e) {
@@ -7973,12 +8354,12 @@ function memoryExtendedPresent() {
7973
8354
  }
7974
8355
  var VECTORIZE_LOCK_TTL_MS = 6 * 60 * 60 * 1e3;
7975
8356
  function vectorizeLockPath(ctx) {
7976
- return join29(ctx.dataDir, "_indexes", ".vectorize.lock");
8357
+ return join30(ctx.dataDir, "_indexes", ".vectorize.lock");
7977
8358
  }
7978
8359
  function vectorizeSetupInProgress(ctx) {
7979
8360
  const lock = vectorizeLockPath(ctx);
7980
8361
  try {
7981
- if (!existsSync14(lock))
8362
+ if (!existsSync15(lock))
7982
8363
  return false;
7983
8364
  return Date.now() - statSync(lock).mtimeMs < VECTORIZE_LOCK_TTL_MS;
7984
8365
  } catch {
@@ -7999,9 +8380,9 @@ function spawnVectorizeSetup(repoRoot) {
7999
8380
  }
8000
8381
  async function runVectorizeSetup(repoRoot, out, err) {
8001
8382
  const ctx = makeContext(repoRoot);
8002
- const indexDir = join29(ctx.dataDir, "_indexes");
8003
- const finalDb = join29(indexDir, "memory.sqlite");
8004
- if (existsSync14(finalDb)) {
8383
+ const indexDir = join30(ctx.dataDir, "_indexes");
8384
+ const finalDb = join30(indexDir, "memory.sqlite");
8385
+ if (existsSync15(finalDb)) {
8005
8386
  out("recall index already present \u2014 nothing to do\n");
8006
8387
  return;
8007
8388
  }
@@ -8032,7 +8413,7 @@ async function runVectorizeSetup(repoRoot, out, err) {
8032
8413
  return;
8033
8414
  }
8034
8415
  }
8035
- const tmpDb = join29(indexDir, `memory.sqlite.building-${process.pid}`);
8416
+ const tmpDb = join30(indexDir, `memory.sqlite.building-${process.pid}`);
8036
8417
  const tmpSidecars = [tmpDb + "-wal", tmpDb + "-shm", tmpDb + "-journal"];
8037
8418
  const cleanTmp = () => {
8038
8419
  rmSync(tmpDb, { force: true });
@@ -8042,7 +8423,7 @@ async function runVectorizeSetup(repoRoot, out, err) {
8042
8423
  let tokenWritten = false;
8043
8424
  const releaseLock = () => {
8044
8425
  try {
8045
- const cur = existsSync14(lockPath) ? readFileSync3(lockPath, "utf8").trim() : "";
8426
+ const cur = existsSync15(lockPath) ? readFileSync4(lockPath, "utf8").trim() : "";
8046
8427
  if (cur === token || cur === "" && !tokenWritten)
8047
8428
  rmSync(lockPath, { force: true });
8048
8429
  } catch {
@@ -8055,7 +8436,7 @@ async function runVectorizeSetup(repoRoot, out, err) {
8055
8436
  } finally {
8056
8437
  closeSync(lockFd);
8057
8438
  }
8058
- if (existsSync14(finalDb)) {
8439
+ if (existsSync15(finalDb)) {
8059
8440
  out("recall index already present \u2014 nothing to do\n");
8060
8441
  return;
8061
8442
  }
@@ -8071,7 +8452,7 @@ async function runVectorizeSetup(repoRoot, out, err) {
8071
8452
  } finally {
8072
8453
  db.close();
8073
8454
  }
8074
- if (existsSync14(tmpDb + "-wal")) {
8455
+ if (existsSync15(tmpDb + "-wal")) {
8075
8456
  throw new Error("temp index retained a WAL sidecar after consolidation; refusing to publish");
8076
8457
  }
8077
8458
  try {
@@ -8119,6 +8500,7 @@ async function runSessionStart(repoRoot, out) {
8119
8500
  }
8120
8501
  } catch {
8121
8502
  }
8503
+ const carryover = collectCarryover(repoRoot);
8122
8504
  const report = await collectSessionStartReport(ctx, { environment });
8123
8505
  let missingWorklogDays = [];
8124
8506
  try {
@@ -8138,7 +8520,7 @@ async function runSessionStart(repoRoot, out) {
8138
8520
  let vectorized = null;
8139
8521
  let vectorizeSetupStarted = false;
8140
8522
  if (config.autoRecord.vectorize) {
8141
- const dbExists = existsSync14(join29(ctx.dataDir, "_indexes", "memory.sqlite"));
8523
+ const dbExists = existsSync15(join30(ctx.dataDir, "_indexes", "memory.sqlite"));
8142
8524
  const action = decideVectorizeAction({
8143
8525
  vectorizeOn: true,
8144
8526
  dbExists,
@@ -8182,6 +8564,12 @@ async function runSessionStart(repoRoot, out) {
8182
8564
  } catch {
8183
8565
  }
8184
8566
  }
8567
+ let globalSetupOffer = false;
8568
+ try {
8569
+ const gs = inspectGlobalSetup(void 0, repoRoot);
8570
+ globalSetupOffer = !gs.done && !gs.declined;
8571
+ } catch {
8572
+ }
8185
8573
  out(renderSessionStartReport(report, {
8186
8574
  git,
8187
8575
  missingWorklogDays,
@@ -8189,7 +8577,9 @@ async function runSessionStart(repoRoot, out) {
8189
8577
  vectorized: vectorized ?? void 0,
8190
8578
  vectorizeSetup: vectorizeSetupStarted || void 0,
8191
8579
  templateUpdate: templateUpdate ?? void 0,
8192
- updateCheck: updateCheck ?? void 0
8580
+ updateCheck: updateCheck ?? void 0,
8581
+ globalSetupOffer: globalSetupOffer || void 0,
8582
+ carryover: carryover ?? void 0
8193
8583
  }));
8194
8584
  }
8195
8585
  async function runSessionEnd(repoRoot, out) {
@@ -8211,6 +8601,37 @@ function gitOut(cwd, gitArgs) {
8211
8601
  stdio: ["ignore", "pipe", "ignore"]
8212
8602
  });
8213
8603
  }
8604
+ function detectInterruptedGitOp(repoRoot) {
8605
+ const markers = [
8606
+ "MERGE_HEAD",
8607
+ "rebase-merge",
8608
+ "rebase-apply",
8609
+ "CHERRY_PICK_HEAD",
8610
+ "REVERT_HEAD",
8611
+ "BISECT_LOG",
8612
+ "index.lock"
8613
+ ];
8614
+ try {
8615
+ const args = ["rev-parse", ...markers.flatMap((m2) => ["--git-path", m2])];
8616
+ const resolved = gitOut(repoRoot, args).split(/\r?\n/).map((s) => s.trim());
8617
+ for (let i = 0; i < markers.length; i++) {
8618
+ const p = resolved[i];
8619
+ if (p && existsSync15(isAbsolute5(p) ? p : join30(repoRoot, p)))
8620
+ return markers[i];
8621
+ }
8622
+ } catch {
8623
+ }
8624
+ return null;
8625
+ }
8626
+ function collectCarryover(repoRoot) {
8627
+ const interrupted = detectInterruptedGitOp(repoRoot);
8628
+ let uncommitted = 0;
8629
+ try {
8630
+ uncommitted = countUncommitted(gitOut(repoRoot, ["status", "--porcelain"]));
8631
+ } catch {
8632
+ }
8633
+ return uncommitted > 0 || interrupted ? { uncommitted, interrupted } : null;
8634
+ }
8214
8635
  function hadActivityToday(repoRoot) {
8215
8636
  try {
8216
8637
  const dirty = gitOut(repoRoot, ["status", "--porcelain"]).trim();
@@ -8228,23 +8649,23 @@ function resolveSessionEnvironment(ctx, config) {
8228
8649
  let environment = resolveEnvironment(config, {
8229
8650
  hostname: hostname(),
8230
8651
  env: process.env,
8231
- pathExists: existsSync14
8652
+ pathExists: existsSync15
8232
8653
  });
8233
8654
  if (!environment)
8234
8655
  environment = process.env.VORTEX_ENV?.trim() || null;
8235
8656
  if (!environment) {
8236
- const envFile = join29(ctx.repoRoot, ".agent", "environment");
8237
- if (existsSync14(envFile)) {
8238
- environment = readFileSync3(envFile, "utf8").split(/\r?\n/)[0]?.trim() || null;
8657
+ const envFile = join30(ctx.repoRoot, ".agent", "environment");
8658
+ if (existsSync15(envFile)) {
8659
+ environment = readFileSync4(envFile, "utf8").split(/\r?\n/)[0]?.trim() || null;
8239
8660
  }
8240
8661
  }
8241
8662
  return environment;
8242
8663
  }
8243
8664
 
8244
8665
  // ../plugins/session-rituals/dist/ambient-recall.js
8245
- import { join as join30 } from "path";
8666
+ import { join as join31 } from "path";
8246
8667
  function defaultDbPath2(ctx) {
8247
- return join30(ctx.dataDir, "_indexes", "memory.sqlite");
8668
+ return join31(ctx.dataDir, "_indexes", "memory.sqlite");
8248
8669
  }
8249
8670
  function createAmbientRecaller(ctx, options) {
8250
8671
  const resolveDb = options.dbPath ?? defaultDbPath2;