@vortex-os/base 0.7.0 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -131,6 +131,21 @@ interface AutoRecordConfig {
131
131
  * `vectorize` is off, nothing runs regardless.
132
132
  */
133
133
  readonly vectorizeAutoDownload: boolean;
134
+ /**
135
+ * Auto-commit framework bookkeeping. When `vortex update` refreshes
136
+ * framework-owned files it also rewrites the ownership manifest
137
+ * (`data/.vortex/ownership.json`); on its own that leaves an uncommitted
138
+ * "trail" that the next session reports as carried-over work, even though it
139
+ * is plumbing the user never touched. With this on, `vortex update` commits
140
+ * exactly the framework files it changed (the manifest + the templates it
141
+ * refreshed) — and nothing else in the working tree — so an update leaves a
142
+ * clean tree. Best-effort: a non-git folder, or any git failure, is a silent
143
+ * no-op (the command still succeeds). On by default; set `false` to keep the
144
+ * commit manual (e.g. you prefer to review and commit framework changes
145
+ * yourself). Conflicts (`<file>.new`) are never auto-committed — those still
146
+ * wait for you to merge.
147
+ */
148
+ readonly commitFrameworkChanges: boolean;
134
149
  }
135
150
  /**
136
151
  * One environment label plus the signal that selects it. Rules are evaluated
@@ -2653,7 +2668,7 @@ declare function detectInterruptedGitOp(repoRoot: string): string | null;
2653
2668
  * null when there is nothing to report (clean tree / not a git repo). Exported
2654
2669
  * for tests. (decision-log 2026-06-04-session-recovery-two-tier.)
2655
2670
  */
2656
- declare function collectCarryover(repoRoot: string): {
2671
+ declare function collectCarryover(repoRoot: string, ignore?: (repoRelPosixPath: string) => boolean): {
2657
2672
  uncommitted: number;
2658
2673
  interrupted: string | null;
2659
2674
  } | null;
@@ -3084,8 +3099,14 @@ declare function detectWorklogGaps(commitDays: readonly string[], presentDates:
3084
3099
  * non-empty line. Pure: the hook runs git; this turns its stdout into the
3085
3100
  * Tier-1 carryover count. (`--porcelain` emits exactly one line per changed
3086
3101
  * path, including untracked, so a line count is the change count.)
3102
+ *
3103
+ * `ignore`, when given, drops paths it returns true for from the count — used to
3104
+ * exclude framework-generated bookkeeping (the ownership manifest under
3105
+ * `data/.vortex/`), which is auto-maintained plumbing the user never edits, so
3106
+ * it must not be reported as carried-over WIP. A line whose path can't be parsed
3107
+ * is counted (fail toward surfacing, not hiding).
3087
3108
  */
3088
- declare function countUncommitted(porcelain: string): number;
3109
+ declare function countUncommitted(porcelain: string, ignore?: (repoRelPosixPath: string) => boolean): number;
3089
3110
  /**
3090
3111
  * Render a session-start report as a compact markdown block for a host hook
3091
3112
  * to inject as session context. A pull conflict and any worklog gaps are
package/dist/index.js CHANGED
@@ -105,7 +105,7 @@ function moduleDir(ctx, moduleName) {
105
105
  import { existsSync, readFileSync } from "fs";
106
106
  import { join as join2 } from "path";
107
107
  var DEFAULT_CONFIG = {
108
- autoRecord: { sessionStart: true, worklog: true, decision: true, ambientRecall: true, archive: true, vectorize: true, vectorizeAutoDownload: true },
108
+ autoRecord: { sessionStart: true, worklog: true, decision: true, ambientRecall: true, archive: true, vectorize: true, vectorizeAutoDownload: true, commitFrameworkChanges: true },
109
109
  updates: { check: "session" },
110
110
  environments: []
111
111
  };
@@ -147,11 +147,13 @@ function loadVortexConfig(ctx) {
147
147
  const check = rawCheck === void 0 ? "session" : typeof rawCheck === "string" && rawCheck.trim().toLowerCase() === "session" ? "session" : "off";
148
148
  const rawAuto = raw.autoRecord && typeof raw.autoRecord === "object" && !Array.isArray(raw.autoRecord) ? raw.autoRecord : {};
149
149
  const vectorizeAutoDownload = rawAuto.vectorizeAutoDownload === void 0 ? true : rawAuto.vectorizeAutoDownload === true;
150
+ const commitFrameworkChanges = rawAuto.commitFrameworkChanges === void 0 ? true : rawAuto.commitFrameworkChanges === true;
150
151
  return {
151
152
  autoRecord: {
152
153
  ...DEFAULT_CONFIG.autoRecord,
153
154
  ...raw.autoRecord ?? {},
154
- vectorizeAutoDownload
155
+ vectorizeAutoDownload,
156
+ commitFrameworkChanges
155
157
  },
156
158
  updates: { check },
157
159
  environments
@@ -5005,6 +5007,21 @@ var MANIFEST_NAME = "manifest.json";
5005
5007
  function ownershipManifestPath(ctx) {
5006
5008
  return join24(ctx.dataDir, ".vortex", "ownership.json");
5007
5009
  }
5010
+ function frameworkBookkeepingPrefix(ctx) {
5011
+ return toPosix(relative4(ctx.repoRoot, join24(ctx.dataDir, ".vortex"))) + "/";
5012
+ }
5013
+ function committableUpdatePaths(ctx, result) {
5014
+ const out = /* @__PURE__ */ new Set();
5015
+ for (const a of result.actions) {
5016
+ if (a.error)
5017
+ continue;
5018
+ if (a.action === "replace" || a.action === "restore" || a.action === "install" || a.action === "adopt") {
5019
+ out.add(a.path);
5020
+ }
5021
+ }
5022
+ out.add(toPosix(relative4(ctx.repoRoot, ownershipManifestPath(ctx))));
5023
+ return [...out];
5024
+ }
5008
5025
  function toPosix(p) {
5009
5026
  return p.split(sep4).join("/");
5010
5027
  }
@@ -5030,6 +5047,8 @@ function templateDestRelPath(templateRelPath) {
5030
5047
  const parts = templateRelPath.split("/");
5031
5048
  if (parts.length < 2)
5032
5049
  return null;
5050
+ if (parts.some((p) => p === ".." || p === "."))
5051
+ return null;
5033
5052
  const [top, ...rest] = parts;
5034
5053
  const tail = rest.join("/");
5035
5054
  if (top === "routers")
@@ -5608,6 +5627,41 @@ function buildNextActions(status, summary, actions, dryRun, fromVersion, toVersi
5608
5627
  return out;
5609
5628
  }
5610
5629
 
5630
+ // ../plugins/session-rituals/dist/git-commit.js
5631
+ import { execFileSync } from "child_process";
5632
+ function git(repoRoot, args) {
5633
+ return execFileSync("git", [...args], {
5634
+ cwd: repoRoot,
5635
+ encoding: "utf8",
5636
+ // GIT_LITERAL_PATHSPECS=1 makes every `-- <path>` a LITERAL filename, not a
5637
+ // pathspec: it disables glob magic (`*` `?` `[…]`) and `:(…)` prefixes. So
5638
+ // even a path carrying those bytes (e.g. from a malformed template index)
5639
+ // can only ever match the exact file named — never a wider set.
5640
+ env: { ...process.env, GIT_LITERAL_PATHSPECS: "1" },
5641
+ // Suppress git's own stderr; the caller treats a non-zero exit as "no commit".
5642
+ stdio: ["ignore", "pipe", "ignore"]
5643
+ });
5644
+ }
5645
+ function commitFrameworkPaths(repoRoot, paths, message) {
5646
+ if (paths.length === 0)
5647
+ return { committed: false, reason: "no-paths" };
5648
+ try {
5649
+ git(repoRoot, ["rev-parse", "--git-dir"]);
5650
+ } catch {
5651
+ return { committed: false, reason: "not-a-git-repo" };
5652
+ }
5653
+ try {
5654
+ const status = git(repoRoot, ["status", "--porcelain", "--", ...paths]).trim();
5655
+ if (!status)
5656
+ return { committed: false, reason: "nothing-to-commit" };
5657
+ git(repoRoot, ["add", "--", ...paths]);
5658
+ git(repoRoot, ["commit", "-m", message, "--", ...paths]);
5659
+ return { committed: true };
5660
+ } catch (e) {
5661
+ return { committed: false, reason: "git-error", error: e?.message ?? String(e) };
5662
+ }
5663
+ }
5664
+
5611
5665
  // ../plugins/session-rituals/dist/commands/vortex.js
5612
5666
  var PLANNED_SUBS = [];
5613
5667
  var vortexCommand = {
@@ -6033,10 +6087,25 @@ async function runUpdate(input, tokens) {
6033
6087
  const dryRun = tokens.includes("--dry-run");
6034
6088
  const adopt = parseAdoptArgs(tokens);
6035
6089
  const templatesDir = resolveTemplatesDir();
6036
- return runTemplatesUpdate(input.context, templatesDir, {
6090
+ const result = await runTemplatesUpdate(input.context, templatesDir, {
6037
6091
  dryRun,
6038
6092
  adopt: adopt.size > 0 ? adopt : void 0
6039
6093
  });
6094
+ if (dryRun || result.status === "no-manifest" || result.status === "no-templates")
6095
+ return result;
6096
+ if (!loadVortexConfig(input.context).autoRecord.commitFrameworkChanges)
6097
+ return result;
6098
+ const paths = committableUpdatePaths(input.context, result);
6099
+ const commit = commitFrameworkPaths(input.context.repoRoot, paths, `chore(vortex): sync framework templates to base ${result.toVersion ?? "?"}`);
6100
+ if (!commit.committed)
6101
+ return result;
6102
+ return {
6103
+ ...result,
6104
+ nextActions: [
6105
+ ...result.nextActions,
6106
+ `Committed the framework changes so nothing is left uncommitted (autoRecord.commitFrameworkChanges; set false to commit these yourself).`
6107
+ ]
6108
+ };
6040
6109
  }
6041
6110
  function parseAdoptArgs(tokens) {
6042
6111
  const adopt = /* @__PURE__ */ new Set();
@@ -7505,7 +7574,7 @@ function createRitualRegistry(options) {
7505
7574
  }
7506
7575
 
7507
7576
  // ../plugins/session-rituals/dist/cli-dispatch.js
7508
- import { execFileSync, spawn as spawn2 } from "child_process";
7577
+ import { execFileSync as execFileSync2, spawn as spawn2 } from "child_process";
7509
7578
  import { existsSync as existsSync15, readFileSync as readFileSync4, mkdirSync, openSync, writeSync, closeSync, linkSync, rmSync, statSync } from "fs";
7510
7579
  import { createRequire } from "module";
7511
7580
  import { hostname } from "os";
@@ -7767,8 +7836,28 @@ function detectWorklogGaps(commitDays, presentDates) {
7767
7836
  const present = new Set(presentDates);
7768
7837
  return [...new Set(commitDays)].filter((d2) => d2 && !present.has(d2)).sort();
7769
7838
  }
7770
- function countUncommitted(porcelain) {
7771
- return porcelain.split(/\r?\n/).filter((l3) => l3.trim().length > 0).length;
7839
+ function porcelainPath(line) {
7840
+ const body = line.slice(3);
7841
+ if (!body)
7842
+ return null;
7843
+ const arrow = body.indexOf(" -> ");
7844
+ const raw = (arrow >= 0 ? body.slice(arrow + 4) : body).trim();
7845
+ if (raw.length >= 2 && raw.startsWith('"') && raw.endsWith('"'))
7846
+ return raw.slice(1, -1);
7847
+ return raw;
7848
+ }
7849
+ function countUncommitted(porcelain, ignore) {
7850
+ const lines = porcelain.split(/\r?\n/).filter((l3) => l3.trim().length > 0);
7851
+ if (!ignore)
7852
+ return lines.length;
7853
+ let n = 0;
7854
+ for (const l3 of lines) {
7855
+ const p = porcelainPath(l3);
7856
+ if (p && ignore(p))
7857
+ continue;
7858
+ n++;
7859
+ }
7860
+ return n;
7772
7861
  }
7773
7862
  function renderSessionStartReport(report, extras) {
7774
7863
  const lines = [
@@ -7779,9 +7868,9 @@ function renderSessionStartReport(report, extras) {
7779
7868
  ];
7780
7869
  const env = report.environment ? ` \xB7 env: ${envLabel(report.environment)}` : "";
7781
7870
  lines.push(`- time: ${report.localTime ?? report.time}${env}`);
7782
- const git = extras?.git;
7783
- if (git?.ran) {
7784
- lines.push(git.conflict ? `- git: \u26A0\uFE0F ${git.summary} \u2014 resolve manually (not auto-resolved)` : `- git: ${git.summary}`);
7871
+ const git2 = extras?.git;
7872
+ if (git2?.ran) {
7873
+ lines.push(git2.conflict ? `- git: \u26A0\uFE0F ${git2.summary} \u2014 resolve manually (not auto-resolved)` : `- git: ${git2.summary}`);
7785
7874
  }
7786
7875
  const countStr = COUNTED_DIRS2.map((d2) => `${d2} ${report.counts[d2] ?? 0}`).join(" \xB7 ");
7787
7876
  const miss = report.missing.length ? ` (missing: ${report.missing.join(", ")})` : "";
@@ -8599,16 +8688,16 @@ async function runSessionStart(repoRoot, out) {
8599
8688
  if (!config.autoRecord.sessionStart)
8600
8689
  return;
8601
8690
  const environment = resolveSessionEnvironment(ctx, config);
8602
- let git = null;
8691
+ let git2 = null;
8603
8692
  try {
8604
8693
  const remotes = gitOut(repoRoot, ["remote"]).trim();
8605
8694
  if (remotes) {
8606
8695
  try {
8607
8696
  const pulled = gitOut(repoRoot, ["pull", "--ff-only"]);
8608
8697
  const lastLine = pulled.trim().split(/\r?\n/).pop() || "up to date";
8609
- git = { ran: true, summary: lastLine, conflict: false };
8698
+ git2 = { ran: true, summary: lastLine, conflict: false };
8610
8699
  } catch {
8611
- git = {
8700
+ git2 = {
8612
8701
  ran: true,
8613
8702
  summary: "fast-forward pull failed (diverged or dirty tree)",
8614
8703
  conflict: true
@@ -8617,7 +8706,8 @@ async function runSessionStart(repoRoot, out) {
8617
8706
  }
8618
8707
  } catch {
8619
8708
  }
8620
- const carryover = collectCarryover(repoRoot);
8709
+ const bookkeepingPrefix = frameworkBookkeepingPrefix(ctx);
8710
+ const carryover = collectCarryover(repoRoot, (p) => p.startsWith(bookkeepingPrefix));
8621
8711
  const report = await collectSessionStartReport(ctx, { environment });
8622
8712
  let missingWorklogDays = [];
8623
8713
  try {
@@ -8688,7 +8778,7 @@ async function runSessionStart(repoRoot, out) {
8688
8778
  } catch {
8689
8779
  }
8690
8780
  out(renderSessionStartReport(report, {
8691
- git,
8781
+ git: git2,
8692
8782
  missingWorklogDays,
8693
8783
  catchUp: catchUp ?? void 0,
8694
8784
  vectorized: vectorized ?? void 0,
@@ -8712,7 +8802,7 @@ async function runSessionEnd(repoRoot, out) {
8712
8802
  }
8713
8803
  }
8714
8804
  function gitOut(cwd, gitArgs) {
8715
- return execFileSync("git", [...gitArgs], {
8805
+ return execFileSync2("git", [...gitArgs], {
8716
8806
  cwd,
8717
8807
  encoding: "utf8",
8718
8808
  stdio: ["ignore", "pipe", "ignore"]
@@ -8740,11 +8830,11 @@ function detectInterruptedGitOp(repoRoot) {
8740
8830
  }
8741
8831
  return null;
8742
8832
  }
8743
- function collectCarryover(repoRoot) {
8833
+ function collectCarryover(repoRoot, ignore) {
8744
8834
  const interrupted = detectInterruptedGitOp(repoRoot);
8745
8835
  let uncommitted = 0;
8746
8836
  try {
8747
- uncommitted = countUncommitted(gitOut(repoRoot, ["status", "--porcelain"]));
8837
+ uncommitted = countUncommitted(gitOut(repoRoot, ["status", "--porcelain"]), ignore);
8748
8838
  } catch {
8749
8839
  }
8750
8840
  return uncommitted > 0 || interrupted ? { uncommitted, interrupted } : null;