@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/README.md +14 -6
- package/dist/index.d.ts +267 -55
- package/dist/index.js +658 -237
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/templates/commands/resume.md +52 -0
- package/templates/manifest.json +8 -3
- package/templates/routers/AI-RULES.md +19 -3
- package/templates/routers/CLAUDE.md +1 -1
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
|
|
4741
|
-
import { copyFile as copyFile2, mkdir as
|
|
4742
|
-
import { basename as basename7, dirname as dirname5, extname as extname11, join as
|
|
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
|
|
4774
|
-
|
|
4775
|
-
|
|
4776
|
-
|
|
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
|
-
|
|
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
|
|
4792
|
-
import { copyFile, mkdir as
|
|
4793
|
-
import { dirname as dirname4, isAbsolute as
|
|
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
|
|
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
|
|
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
|
|
5025
|
+
return join24(".claude", "commands", tail);
|
|
4818
5026
|
if (top === "config")
|
|
4819
|
-
return
|
|
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) ||
|
|
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 =
|
|
4834
|
-
if (!
|
|
5041
|
+
const indexPath = join24(templatesDir, MANIFEST_NAME);
|
|
5042
|
+
if (!existsSync10(indexPath))
|
|
4835
5043
|
return null;
|
|
4836
5044
|
try {
|
|
4837
|
-
const parsed = JSON.parse(await
|
|
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 =
|
|
4859
|
-
if (!
|
|
5066
|
+
const shippedAbs = join24(templatesDir, entry.path);
|
|
5067
|
+
if (!existsSync10(shippedAbs))
|
|
4860
5068
|
continue;
|
|
4861
5069
|
const sourceSha256 = await sha256File(shippedAbs);
|
|
4862
|
-
const destAbs =
|
|
5070
|
+
const destAbs = join24(ctx.repoRoot, destRel);
|
|
4863
5071
|
assertUnderRoot(ctx.repoRoot, destAbs);
|
|
4864
5072
|
let installedSha256 = null;
|
|
4865
|
-
if (
|
|
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
|
|
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 =
|
|
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 =
|
|
4906
|
-
if (!
|
|
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 (
|
|
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 (!
|
|
5140
|
+
if (!existsSync10(mp))
|
|
4933
5141
|
return null;
|
|
4934
5142
|
try {
|
|
4935
|
-
const parsed = JSON.parse(await
|
|
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 =
|
|
4996
|
-
if (!
|
|
5206
|
+
const shippedAbs = join24(templatesDir, idx.path);
|
|
5207
|
+
if (!existsSync10(shippedAbs))
|
|
4997
5208
|
continue;
|
|
4998
5209
|
const newSource = await sha256File(shippedAbs);
|
|
4999
|
-
const destAbs =
|
|
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 =
|
|
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 =
|
|
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 (
|
|
5104
|
-
if (await
|
|
5341
|
+
if (existsSync10(newPath)) {
|
|
5342
|
+
if (await readFile19(newPath, "utf8") === content)
|
|
5105
5343
|
return void 0;
|
|
5106
|
-
const backupAbs =
|
|
5107
|
-
await
|
|
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
|
|
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 (!
|
|
5126
|
-
await
|
|
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 =
|
|
5130
|
-
await
|
|
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 (
|
|
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
|
|
5397
|
+
await mkdir7(dirname4(destAbs), { recursive: true });
|
|
5160
5398
|
await atomicWriteFile(destAbs, content);
|
|
5161
5399
|
}
|
|
5162
5400
|
} else if (action.action === "conflict") {
|
|
5163
|
-
await
|
|
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
|
|
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
|
-
|
|
5268
|
-
|
|
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
|
-
|
|
5660
|
+
join25(here, "..", "..", "templates"),
|
|
5379
5661
|
// session-rituals: dist/commands -> templates
|
|
5380
|
-
|
|
5662
|
+
join25(here, "..", "templates"),
|
|
5381
5663
|
// base aggregate: dist -> templates
|
|
5382
|
-
|
|
5664
|
+
join25(here, "templates")
|
|
5383
5665
|
// defensive: alongside the bundle
|
|
5384
5666
|
];
|
|
5385
5667
|
for (const c of candidates) {
|
|
5386
|
-
if (
|
|
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 =
|
|
5395
|
-
if (!
|
|
5676
|
+
const commandsDir = join25(templatesDir, "commands");
|
|
5677
|
+
if (!existsSync11(commandsDir))
|
|
5396
5678
|
return [];
|
|
5397
|
-
const destDir =
|
|
5398
|
-
await
|
|
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 =
|
|
5404
|
-
if (
|
|
5685
|
+
const dest = join25(destDir, name);
|
|
5686
|
+
if (existsSync11(dest))
|
|
5405
5687
|
continue;
|
|
5406
|
-
await copyFile2(
|
|
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 =
|
|
5422
|
-
if (!
|
|
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 =
|
|
5427
|
-
if (!
|
|
5708
|
+
const src = join25(routersDir, name);
|
|
5709
|
+
if (!existsSync11(src))
|
|
5428
5710
|
continue;
|
|
5429
|
-
const dest =
|
|
5430
|
-
if (
|
|
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 =
|
|
5440
|
-
const vortexJson =
|
|
5441
|
-
if (!
|
|
5442
|
-
await
|
|
5443
|
-
const tmpl = templatesDir ?
|
|
5444
|
-
if (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
|
|
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 =
|
|
5463
|
-
if (!
|
|
5464
|
-
await
|
|
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 =
|
|
5482
|
-
if (!
|
|
5483
|
-
await
|
|
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 =
|
|
5495
|
-
if (
|
|
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>"
|
|
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
|
-
|
|
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 =
|
|
5562
|
-
await
|
|
5563
|
-
const worklogPath =
|
|
5564
|
-
await
|
|
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 =
|
|
5569
|
-
const existingText =
|
|
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
|
|
5573
|
-
await
|
|
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 "${
|
|
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, {
|
|
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 =
|
|
5753
|
-
const initialized =
|
|
6101
|
+
const profilePath = join25(dataDir, "_memory", "user_profile.md");
|
|
6102
|
+
const initialized = existsSync11(profilePath);
|
|
5754
6103
|
const counts = {
|
|
5755
|
-
memory: await safeCount(
|
|
5756
|
-
worklog: await safeCount(
|
|
5757
|
-
decisionLog: await safeCount(
|
|
5758
|
-
runbooks: await safeCount(
|
|
5759
|
-
hubs: await safeCount(
|
|
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(
|
|
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
|
|
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 =
|
|
5791
|
-
missing.push(
|
|
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(
|
|
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 (!
|
|
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(
|
|
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 (!
|
|
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 =
|
|
6020
|
-
if (!
|
|
6021
|
-
await
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
6191
|
-
await
|
|
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
|
|
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
|
|
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
|
|
6613
|
+
return join25(dataDir, "worklog", y2, m2, mdName);
|
|
6265
6614
|
}
|
|
6266
6615
|
if (category === "decisionLog")
|
|
6267
|
-
return
|
|
6616
|
+
return join25(dataDir, "decision-log", mdName);
|
|
6268
6617
|
if (category === "runbooks")
|
|
6269
|
-
return
|
|
6618
|
+
return join25(dataDir, "runbooks", mdName);
|
|
6270
6619
|
if (category === "hubs")
|
|
6271
|
-
return
|
|
6620
|
+
return join25(dataDir, "hubs", mdName);
|
|
6272
6621
|
if (category === "memory")
|
|
6273
|
-
return
|
|
6274
|
-
return
|
|
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 =
|
|
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
|
|
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) => !
|
|
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 =
|
|
6472
|
-
if (
|
|
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 =
|
|
6490
|
-
if (!
|
|
6838
|
+
const dirPath = join25(dataDir, d2);
|
|
6839
|
+
if (!existsSync11(dirPath))
|
|
6491
6840
|
continue;
|
|
6492
|
-
const indexPath =
|
|
6493
|
-
if (!
|
|
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(
|
|
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 =
|
|
6624
|
-
if (!
|
|
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 =
|
|
6645
|
-
const raw = await
|
|
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 =
|
|
6708
|
-
if (!
|
|
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
|
|
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
|
-
|
|
6748
|
-
|
|
6749
|
-
|
|
6750
|
-
|
|
6751
|
-
|
|
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 (!
|
|
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
|
|
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
|
|
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
|
|
7105
|
-
import { join as
|
|
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(
|
|
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) =>
|
|
7185
|
-
const local =
|
|
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
|
|
7214
|
-
import { readdir as readdir16, readFile as
|
|
7215
|
-
import { join as
|
|
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 =
|
|
7230
|
-
if (!
|
|
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(
|
|
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 =
|
|
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
|
|
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(
|
|
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 =
|
|
7409
|
-
if (!
|
|
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(
|
|
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(
|
|
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
|
|
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
|
|
7502
|
-
import { dirname as dirname6, join as
|
|
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(
|
|
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
|
|
7514
|
-
await
|
|
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
|
|
7902
|
+
import { existsSync as existsSync14 } from "fs";
|
|
7540
7903
|
import { createHash as createHash3 } from "crypto";
|
|
7541
|
-
import { readFile as
|
|
7542
|
-
import { join as
|
|
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 =
|
|
7985
|
+
const dataDir = join29(repoRoot, "data");
|
|
7623
7986
|
const candidates = [];
|
|
7624
7987
|
let truncated = false;
|
|
7625
|
-
if (
|
|
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(
|
|
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
|
|
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(
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
8223
|
+
raw = readFileSync4(args[++i], "utf8");
|
|
7847
8224
|
break;
|
|
7848
8225
|
}
|
|
7849
8226
|
}
|
|
7850
8227
|
if (raw === null) {
|
|
7851
8228
|
try {
|
|
7852
|
-
raw =
|
|
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
|
-
|
|
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
|
|
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 (!
|
|
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 =
|
|
8003
|
-
const finalDb =
|
|
8004
|
-
if (
|
|
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 =
|
|
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 =
|
|
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 (
|
|
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 (
|
|
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 =
|
|
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:
|
|
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 =
|
|
8237
|
-
if (
|
|
8238
|
-
environment =
|
|
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
|
|
8666
|
+
import { join as join31 } from "path";
|
|
8246
8667
|
function defaultDbPath2(ctx) {
|
|
8247
|
-
return
|
|
8668
|
+
return join31(ctx.dataDir, "_indexes", "memory.sqlite");
|
|
8248
8669
|
}
|
|
8249
8670
|
function createAmbientRecaller(ctx, options) {
|
|
8250
8671
|
const resolveDb = options.dbPath ?? defaultDbPath2;
|