@vortex-os/base 0.10.0 → 0.12.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/LICENSE +21 -21
- package/README.md +48 -1
- package/bin/vortex.mjs +17 -17
- package/dist/{catch-up-KIHTAUPX.js → catch-up-GDDKPZHJ.js} +2 -2
- package/dist/chunk-2FVNWW77.js +166 -0
- package/dist/chunk-2FVNWW77.js.map +1 -0
- package/dist/{chunk-7SNLVGBO.js → chunk-3L5DLEGP.js} +1 -1
- package/dist/chunk-3L5DLEGP.js.map +1 -0
- package/dist/chunk-EAKDR5B2.js +501 -0
- package/dist/chunk-EAKDR5B2.js.map +1 -0
- package/dist/chunk-T53UWSTR.js +301 -0
- package/dist/chunk-T53UWSTR.js.map +1 -0
- package/dist/chunk-UV76ZEDC.js +292 -0
- package/dist/chunk-UV76ZEDC.js.map +1 -0
- package/dist/failures-PMURLMVB.js +25 -0
- package/dist/failures-PMURLMVB.js.map +1 -0
- package/dist/guard-IMJR6ET7.js +23 -0
- package/dist/guard-IMJR6ET7.js.map +1 -0
- package/dist/index.d.ts +425 -3
- package/dist/index.js +472 -709
- package/dist/index.js.map +1 -1
- package/dist/statusline-6KSHISXO.js +36 -0
- package/dist/statusline-6KSHISXO.js.map +1 -0
- package/dist/{vectorize-RBDBTSTW.js → vectorize-PN4Y7XMO.js} +1 -1
- package/dist/vectorize-PN4Y7XMO.js.map +1 -0
- package/package.json +1 -1
- package/templates/commands/agenda.md +15 -15
- package/templates/commands/handoff.md +26 -26
- package/templates/commands/resume.md +52 -52
- package/templates/config/vortex.json +13 -13
- package/templates/manifest.json +2 -2
- package/templates/routers/.cursorrules +14 -14
- package/templates/routers/AGENTS.md +27 -27
- package/templates/routers/AI-RULES.md +3 -1
- package/templates/routers/GEMINI.md +16 -16
- package/dist/chunk-7SNLVGBO.js.map +0 -1
- package/dist/vectorize-RBDBTSTW.js.map +0 -1
- /package/dist/{catch-up-KIHTAUPX.js.map → catch-up-GDDKPZHJ.js.map} +0 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import {
|
|
2
|
+
LOCAL_BIN_RELPATH,
|
|
3
|
+
collectStatuslineProbes,
|
|
4
|
+
effortMeter,
|
|
5
|
+
ensureStatusline,
|
|
6
|
+
formatTokens,
|
|
7
|
+
formatWindow,
|
|
8
|
+
isOurStatuslineCommand,
|
|
9
|
+
makeBar,
|
|
10
|
+
parseStatuslineInput,
|
|
11
|
+
renderStatusline,
|
|
12
|
+
runStatuslineCli,
|
|
13
|
+
safeSegment,
|
|
14
|
+
sniffEffortFromTranscript,
|
|
15
|
+
statuslineCommand
|
|
16
|
+
} from "./chunk-EAKDR5B2.js";
|
|
17
|
+
import "./chunk-2FVNWW77.js";
|
|
18
|
+
import "./chunk-T53UWSTR.js";
|
|
19
|
+
import "./chunk-PZ5AY32C.js";
|
|
20
|
+
export {
|
|
21
|
+
LOCAL_BIN_RELPATH,
|
|
22
|
+
collectStatuslineProbes,
|
|
23
|
+
effortMeter,
|
|
24
|
+
ensureStatusline,
|
|
25
|
+
formatTokens,
|
|
26
|
+
formatWindow,
|
|
27
|
+
isOurStatuslineCommand,
|
|
28
|
+
makeBar,
|
|
29
|
+
parseStatuslineInput,
|
|
30
|
+
renderStatusline,
|
|
31
|
+
runStatuslineCli,
|
|
32
|
+
safeSegment,
|
|
33
|
+
sniffEffortFromTranscript,
|
|
34
|
+
statuslineCommand
|
|
35
|
+
};
|
|
36
|
+
//# sourceMappingURL=statusline-6KSHISXO.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../plugins/session-rituals/src/vectorize.ts"],"sourcesContent":["import { join } from \"node:path\";\nimport type { ModuleContext } from \"@vortex-os/core\";\n// Type-only — erased at compile time, so importing it does NOT pull the\n// `@vortex-os/memory-extended` add-on (or its native deps) into the module\n// graph of consumers that only need the types. The runtime engine is loaded\n// lazily inside the function body via `await import(...)`.\nimport type { vector } from \"@vortex-os/memory-extended\";\n\n/**\n * Start-of-session vectorization: fold newly-archived sessions and memories\n * into the semantic-recall index so `/recall` and the ambient \"did we discuss\n * this?\" path stay current without a manual rebuild.\n *\n * The heavy one-time cost (downloading the embedding model + embedding the\n * whole backlog) happens the first time recall is set up — via an explicit\n * `/recall`, or via the `vortex vectorize` setup worker that session start\n * spawns in the background when the add-on is installed and\n * `autoRecord.vectorizeAutoDownload` is on. From then on the model is cached\n * and this runs **incrementally** — memory rows whose hash changed, plus\n * sessions not yet vectorized (`onlyMissing`) — so the per-session-start cost\n * is small.\n *\n * `allowDownload` defaults false, so the automatic inline session-start call is\n * cache-only and never triggers a model download on its own; only the explicit\n * setup paths pass `allowDownload: true`. A throw is treated as \"skip\"\n * (best-effort): a missing model, a rebuild hiccup, or the add-on being absent\n * must never fail session start.\n */\nexport interface VectorizeResult {\n /** Memory rows (re)embedded this run. */\n readonly memories: number;\n /** Session chunks embedded this run (only the not-yet-vectorized ones). */\n readonly sessionChunks: number;\n}\n\nexport interface VectorizeOptions {\n /**\n * Embedding function. Default: the bundled local model\n * (`vector.createLocalEmbedder()`). Tests inject a deterministic fake so the\n * run needs no model and no network.\n */\n readonly embed?: vector.EmbedFn;\n /** Override the index DB path. Default `<dataDir>/_indexes/memory.sqlite`. */\n readonly dbPath?: string;\n /**\n * Allow the embedder to DOWNLOAD the model when it is not cached. Default\n * `false`: the automatic session-start path stays cache-only (a cache miss\n * throws fast — no ~470 MB download). The explicit `vortex vectorize` setup\n * worker passes `true` to bring the model into the cache the first time.\n * Ignored when `embed` is supplied (tests inject their own embedder).\n */\n readonly allowDownload?: boolean;\n}\n\nexport async function vectorizeIndex(\n ctx: ModuleContext,\n opts?: VectorizeOptions,\n): Promise<VectorizeResult> {\n // Lazy-load the optional add-on (mirrors catch-up). Resolves only when\n // `memory-extended` is installed alongside base; a lean install throws here\n // and the call site treats it as \"skip\".\n const { sqlite, vector, sessionArchive } = await import(\"@vortex-os/memory-extended\");\n\n const dbPath = opts?.dbPath ?? join(ctx.dataDir, \"_indexes\", \"memory.sqlite\");\n const memoryDir = join(ctx.dataDir, \"_memory\");\n // `localFilesOnly` is the real safety gate for this automatic path: if the\n // embedding model is not already cached, the first embed throws immediately\n // (no ~470 MB download) and the caller skips. So session-start vectorization\n // can never trigger an unprompted download, even if the index db exists for\n // some other reason. The model is downloaded only by the explicit, consented\n // `/recall` setup, which uses the default network-enabled embedder.\n const embed = opts?.embed ?? vector.createLocalEmbedder({ localFilesOnly: !opts?.allowDownload });\n\n const sqlStore = new sqlite.MemorySqliteStore(dbPath);\n const vecStore = new vector.MemoryVectorStore({ db: dbPath });\n const archive = new sessionArchive.SessionArchiveStore(ctx.dataDir);\n try {\n // Memory: refresh the sqlite hard-filter index (no model needed) then its\n // vectors. Best-effort per step — a missing _memory dir must not abort the\n // session pass.\n try {\n await sqlite.rebuildFromMemoryDir(sqlStore, memoryDir);\n } catch {\n // no _memory dir yet — nothing to index\n }\n // Both incremental (`onlyMissing`): embed only newly-added memories and\n // not-yet-vectorized sessions, so the per-session-start cost is bounded to\n // what actually changed — usually nothing or a single new session.\n const mem = await vecStore.rebuild(sqlStore, embed, { onlyMissing: true });\n const sess = await vecStore.rebuildSessions(archive, embed, { onlyMissing: true });\n return { memories: mem.indexed, sessionChunks: sess.chunks };\n } finally {\n archive.close();\n vecStore.close();\n sqlStore.close();\n }\n}\n"],"mappings":";;;AAAA,SAAS,YAAY;AAsDrB,eAAsB,eACpB,KACA,MAAuB;AAKvB,QAAM,EAAE,QAAQ,QAAQ,eAAc,IAAK,MAAM,OAAO,4BAA4B;AAEpF,QAAM,SAAS,MAAM,UAAU,KAAK,IAAI,SAAS,YAAY,eAAe;AAC5E,QAAM,YAAY,KAAK,IAAI,SAAS,SAAS;AAO7C,QAAM,QAAQ,MAAM,SAAS,OAAO,oBAAoB,EAAE,gBAAgB,CAAC,MAAM,cAAa,CAAE;AAEhG,QAAM,WAAW,IAAI,OAAO,kBAAkB,MAAM;AACpD,QAAM,WAAW,IAAI,OAAO,kBAAkB,EAAE,IAAI,OAAM,CAAE;AAC5D,QAAM,UAAU,IAAI,eAAe,oBAAoB,IAAI,OAAO;AAClE,MAAI;AAIF,QAAI;AACF,YAAM,OAAO,qBAAqB,UAAU,SAAS;IACvD,QAAQ;IAER;AAIA,UAAM,MAAM,MAAM,SAAS,QAAQ,UAAU,OAAO,EAAE,aAAa,KAAI,CAAE;AACzE,UAAM,OAAO,MAAM,SAAS,gBAAgB,SAAS,OAAO,EAAE,aAAa,KAAI,CAAE;AACjF,WAAO,EAAE,UAAU,IAAI,SAAS,eAAe,KAAK,OAAM;EAC5D;AACE,YAAQ,MAAK;AACb,aAAS,MAAK;AACd,aAAS,MAAK;EAChB;AACF;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
---
|
|
2
|
-
description: What should I focus on? Synthesizes open tasks, decisions, and recent activity.
|
|
3
|
-
allowed-tools: Bash(npx vortex:*)
|
|
4
|
-
---
|
|
5
|
-
Show the user what to work on, synthesized from their existing records.
|
|
6
|
-
|
|
7
|
-
1. From the instance root, run: `npx vortex agenda`
|
|
8
|
-
2. Parse the JSON: `openTasks[]`, `openDecisions[]`, `nextUp[]`, `lastWorklog`,
|
|
9
|
-
`isEmpty`, `nothingOpen`.
|
|
10
|
-
3. Present plainly — never dump raw JSON:
|
|
11
|
-
- `isEmpty` → it's a fresh instance; nudge them to just start working (a
|
|
12
|
-
worklog grows on its own; nothing to set up).
|
|
13
|
-
- `nothingOpen` → say they're clear, and mention the last activity.
|
|
14
|
-
- Otherwise → a short "today" view (≤8 lines): open tasks first, then active
|
|
15
|
-
decisions, then what's queued next (`nextUp`).
|
|
1
|
+
---
|
|
2
|
+
description: What should I focus on? Synthesizes open tasks, decisions, and recent activity.
|
|
3
|
+
allowed-tools: Bash(npx vortex:*)
|
|
4
|
+
---
|
|
5
|
+
Show the user what to work on, synthesized from their existing records.
|
|
6
|
+
|
|
7
|
+
1. From the instance root, run: `npx vortex agenda`
|
|
8
|
+
2. Parse the JSON: `openTasks[]`, `openDecisions[]`, `nextUp[]`, `lastWorklog`,
|
|
9
|
+
`isEmpty`, `nothingOpen`.
|
|
10
|
+
3. Present plainly — never dump raw JSON:
|
|
11
|
+
- `isEmpty` → it's a fresh instance; nudge them to just start working (a
|
|
12
|
+
worklog grows on its own; nothing to set up).
|
|
13
|
+
- `nothingOpen` → say they're clear, and mention the last activity.
|
|
14
|
+
- Otherwise → a short "today" view (≤8 lines): open tasks first, then active
|
|
15
|
+
decisions, then what's queued next (`nextUp`).
|
|
@@ -1,26 +1,26 @@
|
|
|
1
|
-
---
|
|
2
|
-
description: Leave a clean hand-off so the next session resumes where this one stops — one file per session under `data/_handoff/`, with concrete next steps, current state, and pointers.
|
|
3
|
-
allowed-tools: Bash(npx vortex:*), Bash(git add:*), Bash(git commit:*), Read, Edit, Write, Glob
|
|
4
|
-
---
|
|
5
|
-
Capture a hand-off so the NEXT session — a fresh, empty context window — can resume exactly where this one stops, instead of re-deriving everything. Use this at wind-down, or whenever the user says "hand off / wrap up so I can resume / 이어가게 정리 / 인수인계". (This is also a default behavior on those phrases — the command just makes it explicit.)
|
|
6
|
-
|
|
7
|
-
A hand-off is a **forward-looking baton**, kept SEPARATE from the worklog (the permanent, backward-looking "what happened" record). It lives in `data/_handoff/`, one file per session, and is disposable: old ones are swept to `_handoff/_archive/` automatically at session start, so you never tidy them by hand.
|
|
8
|
-
|
|
9
|
-
INVARIANTS (never break):
|
|
10
|
-
- **One file per SESSION, covering everything you touched.** A single session that worked on topic A, then B, then back to A writes ONE hand-off with all of it — do NOT split per topic, and do NOT create a "hand-off" worklog file.
|
|
11
|
-
- **The baton points forward; the narrative stays in the worklog.** Put next steps + how-to-resume here; the "what/why we did" story belongs in the worklog. (So discarding an old hand-off never loses anything — the durable record is the worklog, and git keeps old hand-offs anyway.)
|
|
12
|
-
- **No carry-forward.** Don't copy another session's pending items into yours. Concurrent sessions each leave their own hand-off — multiple active ones are NORMAL; the start report surfaces them all.
|
|
13
|
-
- **Real, not padding.** Write what genuinely remains. If nothing is pending, say so plainly rather than inventing tasks.
|
|
14
|
-
- **Commit is offered; push stays explicit.** Offer to commit the hand-off for durability; push only if the user asks.
|
|
15
|
-
|
|
16
|
-
## 1. Create the hand-off file
|
|
17
|
-
Run `npx vortex handoff "<short title>"` — it creates `data/_handoff/<YYYY-MM-DD_HHMM>.md` (a `-2` suffix is added automatically only if another session wrote one in the same minute, so nothing is ever clobbered) and prints the path. Fill THAT file; don't hand-name it.
|
|
18
|
-
|
|
19
|
-
## 2. Fill the hand-off
|
|
20
|
-
The skeleton has three sections — fill them in this order:
|
|
21
|
-
1. **`## 다음 작업` — concrete next steps, as unchecked `- [ ]` items, first.** Each one a single action the next session can pick up directly. The start report surfaces these as the "resume from here" pointer. Long-lived TODOs that outlive one session belong as `- [ ]` tasks in the worklog, not carried forward here.
|
|
22
|
-
2. **`## 현재 상태`** — what's done vs in progress across everything this session touched, and any decision already made.
|
|
23
|
-
3. **`## 이어받을 포인터`** — the files, commit hashes, and decision-log entries the next session will need.
|
|
24
|
-
|
|
25
|
-
## 3. Make it durable
|
|
26
|
-
Briefly confirm what you captured, then offer to commit it (and, only if the user asks, push) so the hand-off survives into the next session — and across machines, if the instance syncs.
|
|
1
|
+
---
|
|
2
|
+
description: Leave a clean hand-off so the next session resumes where this one stops — one file per session under `data/_handoff/`, with concrete next steps, current state, and pointers.
|
|
3
|
+
allowed-tools: Bash(npx vortex:*), Bash(git add:*), Bash(git commit:*), Read, Edit, Write, Glob
|
|
4
|
+
---
|
|
5
|
+
Capture a hand-off so the NEXT session — a fresh, empty context window — can resume exactly where this one stops, instead of re-deriving everything. Use this at wind-down, or whenever the user says "hand off / wrap up so I can resume / 이어가게 정리 / 인수인계". (This is also a default behavior on those phrases — the command just makes it explicit.)
|
|
6
|
+
|
|
7
|
+
A hand-off is a **forward-looking baton**, kept SEPARATE from the worklog (the permanent, backward-looking "what happened" record). It lives in `data/_handoff/`, one file per session, and is disposable: old ones are swept to `_handoff/_archive/` automatically at session start, so you never tidy them by hand.
|
|
8
|
+
|
|
9
|
+
INVARIANTS (never break):
|
|
10
|
+
- **One file per SESSION, covering everything you touched.** A single session that worked on topic A, then B, then back to A writes ONE hand-off with all of it — do NOT split per topic, and do NOT create a "hand-off" worklog file.
|
|
11
|
+
- **The baton points forward; the narrative stays in the worklog.** Put next steps + how-to-resume here; the "what/why we did" story belongs in the worklog. (So discarding an old hand-off never loses anything — the durable record is the worklog, and git keeps old hand-offs anyway.)
|
|
12
|
+
- **No carry-forward.** Don't copy another session's pending items into yours. Concurrent sessions each leave their own hand-off — multiple active ones are NORMAL; the start report surfaces them all.
|
|
13
|
+
- **Real, not padding.** Write what genuinely remains. If nothing is pending, say so plainly rather than inventing tasks.
|
|
14
|
+
- **Commit is offered; push stays explicit.** Offer to commit the hand-off for durability; push only if the user asks.
|
|
15
|
+
|
|
16
|
+
## 1. Create the hand-off file
|
|
17
|
+
Run `npx vortex handoff "<short title>"` — it creates `data/_handoff/<YYYY-MM-DD_HHMM>.md` (a `-2` suffix is added automatically only if another session wrote one in the same minute, so nothing is ever clobbered) and prints the path. Fill THAT file; don't hand-name it.
|
|
18
|
+
|
|
19
|
+
## 2. Fill the hand-off
|
|
20
|
+
The skeleton has three sections — fill them in this order:
|
|
21
|
+
1. **`## 다음 작업` — concrete next steps, as unchecked `- [ ]` items, first.** Each one a single action the next session can pick up directly. The start report surfaces these as the "resume from here" pointer. Long-lived TODOs that outlive one session belong as `- [ ]` tasks in the worklog, not carried forward here.
|
|
22
|
+
2. **`## 현재 상태`** — what's done vs in progress across everything this session touched, and any decision already made.
|
|
23
|
+
3. **`## 이어받을 포인터`** — the files, commit hashes, and decision-log entries the next session will need.
|
|
24
|
+
|
|
25
|
+
## 3. Make it durable
|
|
26
|
+
Briefly confirm what you captured, then offer to commit it (and, only if the user asks, push) so the hand-off survives into the next session — and across machines, if the instance syncs.
|
|
@@ -1,52 +1,52 @@
|
|
|
1
|
-
---
|
|
2
|
-
description: Reconstruct what the PREVIOUS session was doing and whether it was wrapped up — for after a crash / abrupt close when no handoff was written
|
|
3
|
-
allowed-tools: Bash, Read, Grep, Glob
|
|
4
|
-
---
|
|
5
|
-
Back-trace the last session to answer three things: **what was being worked on, how far it got, and is anything left unfinished or broken.** Use this after an abrupt close or a crash, or whenever the start-of-session report flags carried-over uncommitted work.
|
|
6
|
-
|
|
7
|
-
INVARIANTS (never break):
|
|
8
|
-
- **READ-ONLY**: never modify the instance or any repo you inspect, and never commit. (Parsing a transcript into a throwaway scratch file under the OS temp dir is fine — just never touch the user's work or repos.)
|
|
9
|
-
- **OFFER, don't act**: report findings and offer to continue or to commit/record loose ends; fix nothing without the user's go-ahead.
|
|
10
|
-
- **Token-frugal**: lead with the cheap signals; read a transcript ONLY if they're inconclusive, and even then extract just the bounded narrative — never the whole `.jsonl` into context.
|
|
11
|
-
|
|
12
|
-
## 1. Cheap signals first (almost free — often enough)
|
|
13
|
-
In the instance root and any repo you know you were modifying:
|
|
14
|
-
- `git status --porcelain` — uncommitted changes are work that was **not saved**. List them.
|
|
15
|
-
- Interrupted git op — check via `git rev-parse --git-path MERGE_HEAD` / `… rebase-merge` / `… rebase-apply` (the `.git` dir can be a FILE in worktrees/submodules, so resolve the path, don't hardcode `.git/…`), or a stray `*.lock` in the git dir. `COMMIT_EDITMSG` alone is normal — ignore it; a lock can also be stale from non-crash tooling — treat it as a hint, not proof.
|
|
16
|
-
- `git diff --stat` over the uncommitted files → what was being edited.
|
|
17
|
-
- `git log --oneline -6`; compare to today's worklog under `data/worklog/<YYYY>/<MM>/`. A commit dated today with no matching worklog = a record gap.
|
|
18
|
-
|
|
19
|
-
If the git-tracked signals are all clean, say "**git-tracked work looks wrapped up**" — but note this only covers git: edits to **non-git folders, gitignored/generated files, or another repo you can't see from here won't show**. So if there's any reason to suspect more (the report flagged a crash, or the user is unsure), continue to step 2 anyway; otherwise you may stop here.
|
|
20
|
-
|
|
21
|
-
## 2. If inconclusive: find the PREVIOUS session's transcript
|
|
22
|
-
**Prefer the normalized archive** under `data/_session-archive/normalized/.../` — those are PAST sessions (archived at this session's start), so they cannot include the live `/resume` session. Take the most recently modified one there.
|
|
23
|
-
Only if needed, fall back to the live Claude Code transcripts at `~/.claude/projects/<slug>/*.jsonl` (slug = the cwd path with `/`, `\`, `:` replaced by `-`). **The current session's transcript is the newest and still growing — EXCLUDE it**; take the one immediately before it (its last activity predates this session's start).
|
|
24
|
-
|
|
25
|
-
## 3. Extract ONLY a bounded narrative (the token-saver)
|
|
26
|
-
Do **not** `Read` the raw `.jsonl` into context. Pull just **the first user turn (the original task) plus the last ~12 user/assistant text turns (where it stopped)**, dropping every `tool_use` / `tool_result`, each truncated. Use whatever your platform has — `jq`, a `node` one-liner, or PowerShell `ConvertFrom-Json`. Reference logic (node):
|
|
27
|
-
```js
|
|
28
|
-
// node <script> <transcript.jsonl> → first user turn + last 12 text turns, tool noise dropped
|
|
29
|
-
import { readFileSync } from "node:fs";
|
|
30
|
-
const turns = [];
|
|
31
|
-
for (const line of readFileSync(process.argv[2], "utf8").split("\n")) {
|
|
32
|
-
if (!line.trim()) continue;
|
|
33
|
-
let o; try { o = JSON.parse(line); } catch { continue; }
|
|
34
|
-
if (o.type !== "user" && o.type !== "assistant") continue;
|
|
35
|
-
const c = o.message?.content;
|
|
36
|
-
const text = Array.isArray(c)
|
|
37
|
-
? c.filter((x) => x && x.type === "text").map((x) => x.text).join("")
|
|
38
|
-
: (typeof c === "string" ? c : "");
|
|
39
|
-
if (text.trim()) turns.push(`### ${o.type}\n${text.slice(0, 600)}`);
|
|
40
|
-
}
|
|
41
|
-
const picked = turns.length > 13 ? [turns[0], "… (middle elided) …", ...turns.slice(-12)] : turns;
|
|
42
|
-
console.log(picked.join("\n\n"));
|
|
43
|
-
```
|
|
44
|
-
Read only that output. The first user turn = the session's original task; the last turns = where it stopped.
|
|
45
|
-
|
|
46
|
-
## 4. Cross-reference and report (concise)
|
|
47
|
-
- **What it was** — the original task / goal.
|
|
48
|
-
- **How far** — what's done & committed vs. what's in progress.
|
|
49
|
-
- **Loose ends** — uncommitted files, missing worklog, interrupted git op, an unrun build/test — each with *where* it stopped.
|
|
50
|
-
- **Verdict** — "wrapped up", or exactly what remains to finish.
|
|
51
|
-
|
|
52
|
-
Then **offer** to continue, or to commit / record the loose ends — never act without the user's go-ahead.
|
|
1
|
+
---
|
|
2
|
+
description: Reconstruct what the PREVIOUS session was doing and whether it was wrapped up — for after a crash / abrupt close when no handoff was written
|
|
3
|
+
allowed-tools: Bash, Read, Grep, Glob
|
|
4
|
+
---
|
|
5
|
+
Back-trace the last session to answer three things: **what was being worked on, how far it got, and is anything left unfinished or broken.** Use this after an abrupt close or a crash, or whenever the start-of-session report flags carried-over uncommitted work.
|
|
6
|
+
|
|
7
|
+
INVARIANTS (never break):
|
|
8
|
+
- **READ-ONLY**: never modify the instance or any repo you inspect, and never commit. (Parsing a transcript into a throwaway scratch file under the OS temp dir is fine — just never touch the user's work or repos.)
|
|
9
|
+
- **OFFER, don't act**: report findings and offer to continue or to commit/record loose ends; fix nothing without the user's go-ahead.
|
|
10
|
+
- **Token-frugal**: lead with the cheap signals; read a transcript ONLY if they're inconclusive, and even then extract just the bounded narrative — never the whole `.jsonl` into context.
|
|
11
|
+
|
|
12
|
+
## 1. Cheap signals first (almost free — often enough)
|
|
13
|
+
In the instance root and any repo you know you were modifying:
|
|
14
|
+
- `git status --porcelain` — uncommitted changes are work that was **not saved**. List them.
|
|
15
|
+
- Interrupted git op — check via `git rev-parse --git-path MERGE_HEAD` / `… rebase-merge` / `… rebase-apply` (the `.git` dir can be a FILE in worktrees/submodules, so resolve the path, don't hardcode `.git/…`), or a stray `*.lock` in the git dir. `COMMIT_EDITMSG` alone is normal — ignore it; a lock can also be stale from non-crash tooling — treat it as a hint, not proof.
|
|
16
|
+
- `git diff --stat` over the uncommitted files → what was being edited.
|
|
17
|
+
- `git log --oneline -6`; compare to today's worklog under `data/worklog/<YYYY>/<MM>/`. A commit dated today with no matching worklog = a record gap.
|
|
18
|
+
|
|
19
|
+
If the git-tracked signals are all clean, say "**git-tracked work looks wrapped up**" — but note this only covers git: edits to **non-git folders, gitignored/generated files, or another repo you can't see from here won't show**. So if there's any reason to suspect more (the report flagged a crash, or the user is unsure), continue to step 2 anyway; otherwise you may stop here.
|
|
20
|
+
|
|
21
|
+
## 2. If inconclusive: find the PREVIOUS session's transcript
|
|
22
|
+
**Prefer the normalized archive** under `data/_session-archive/normalized/.../` — those are PAST sessions (archived at this session's start), so they cannot include the live `/resume` session. Take the most recently modified one there.
|
|
23
|
+
Only if needed, fall back to the live Claude Code transcripts at `~/.claude/projects/<slug>/*.jsonl` (slug = the cwd path with `/`, `\`, `:` replaced by `-`). **The current session's transcript is the newest and still growing — EXCLUDE it**; take the one immediately before it (its last activity predates this session's start).
|
|
24
|
+
|
|
25
|
+
## 3. Extract ONLY a bounded narrative (the token-saver)
|
|
26
|
+
Do **not** `Read` the raw `.jsonl` into context. Pull just **the first user turn (the original task) plus the last ~12 user/assistant text turns (where it stopped)**, dropping every `tool_use` / `tool_result`, each truncated. Use whatever your platform has — `jq`, a `node` one-liner, or PowerShell `ConvertFrom-Json`. Reference logic (node):
|
|
27
|
+
```js
|
|
28
|
+
// node <script> <transcript.jsonl> → first user turn + last 12 text turns, tool noise dropped
|
|
29
|
+
import { readFileSync } from "node:fs";
|
|
30
|
+
const turns = [];
|
|
31
|
+
for (const line of readFileSync(process.argv[2], "utf8").split("\n")) {
|
|
32
|
+
if (!line.trim()) continue;
|
|
33
|
+
let o; try { o = JSON.parse(line); } catch { continue; }
|
|
34
|
+
if (o.type !== "user" && o.type !== "assistant") continue;
|
|
35
|
+
const c = o.message?.content;
|
|
36
|
+
const text = Array.isArray(c)
|
|
37
|
+
? c.filter((x) => x && x.type === "text").map((x) => x.text).join("")
|
|
38
|
+
: (typeof c === "string" ? c : "");
|
|
39
|
+
if (text.trim()) turns.push(`### ${o.type}\n${text.slice(0, 600)}`);
|
|
40
|
+
}
|
|
41
|
+
const picked = turns.length > 13 ? [turns[0], "… (middle elided) …", ...turns.slice(-12)] : turns;
|
|
42
|
+
console.log(picked.join("\n\n"));
|
|
43
|
+
```
|
|
44
|
+
Read only that output. The first user turn = the session's original task; the last turns = where it stopped.
|
|
45
|
+
|
|
46
|
+
## 4. Cross-reference and report (concise)
|
|
47
|
+
- **What it was** — the original task / goal.
|
|
48
|
+
- **How far** — what's done & committed vs. what's in progress.
|
|
49
|
+
- **Loose ends** — uncommitted files, missing worklog, interrupted git op, an unrun build/test — each with *where* it stopped.
|
|
50
|
+
- **Verdict** — "wrapped up", or exactly what remains to finish.
|
|
51
|
+
|
|
52
|
+
Then **offer** to continue, or to commit / record the loose ends — never act without the user's go-ahead.
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
{
|
|
2
|
-
"autoRecord": {
|
|
3
|
-
"sessionStart": true,
|
|
4
|
-
"worklog": true,
|
|
5
|
-
"decision": true,
|
|
6
|
-
"ambientRecall": true,
|
|
7
|
-
"archive": true
|
|
8
|
-
},
|
|
9
|
-
"updates": {
|
|
10
|
-
"check": "session"
|
|
11
|
-
},
|
|
12
|
-
"environments": []
|
|
13
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"autoRecord": {
|
|
3
|
+
"sessionStart": true,
|
|
4
|
+
"worklog": true,
|
|
5
|
+
"decision": true,
|
|
6
|
+
"ambientRecall": true,
|
|
7
|
+
"archive": true
|
|
8
|
+
},
|
|
9
|
+
"updates": {
|
|
10
|
+
"check": "session"
|
|
11
|
+
},
|
|
12
|
+
"environments": []
|
|
13
|
+
}
|
package/templates/manifest.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema": "vortex-template-index/1",
|
|
3
|
-
"baseVersion": "0.
|
|
3
|
+
"baseVersion": "0.12.0",
|
|
4
4
|
"files": [
|
|
5
5
|
{
|
|
6
6
|
"templateId": "commands/agenda.md",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
{
|
|
46
46
|
"templateId": "routers/AI-RULES.md",
|
|
47
47
|
"path": "routers/AI-RULES.md",
|
|
48
|
-
"sha256": "
|
|
48
|
+
"sha256": "44f41b61ef2e25f833d6cb58e27f7e43883c5256a482935ae68bf8f839d0479a"
|
|
49
49
|
},
|
|
50
50
|
{
|
|
51
51
|
"templateId": "routers/CLAUDE.md",
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
VortEX Instance — Multi-Agent Personal AI Work OS
|
|
2
|
-
|
|
3
|
-
When working in this repository, you (Cursor) MUST read AI-RULES.md first. AI-RULES.md is the
|
|
4
|
-
single source of truth for shared behavior across all agents: the data/ layout, conventions,
|
|
5
|
-
default behaviors, and the commands available.
|
|
6
|
-
|
|
7
|
-
Cursor-specific config lives in .cursor/. Shared agent config lives in .agent/.
|
|
8
|
-
|
|
9
|
-
Per-agent entry files (CLAUDE.md, AGENTS.md, GEMINI.md, .cursorrules) hold only that agent's
|
|
10
|
-
own supplements; the shared rules live in AI-RULES.md, never duplicated per agent.
|
|
11
|
-
|
|
12
|
-
This instance was created from the @vortex-os/base framework. The framework code lives in
|
|
13
|
-
node_modules/@vortex-os/; you do not edit it. You work in data/ and use the vortex CLI and
|
|
14
|
-
its slash-commands (run `npx vortex --list` to see them).
|
|
1
|
+
VortEX Instance — Multi-Agent Personal AI Work OS
|
|
2
|
+
|
|
3
|
+
When working in this repository, you (Cursor) MUST read AI-RULES.md first. AI-RULES.md is the
|
|
4
|
+
single source of truth for shared behavior across all agents: the data/ layout, conventions,
|
|
5
|
+
default behaviors, and the commands available.
|
|
6
|
+
|
|
7
|
+
Cursor-specific config lives in .cursor/. Shared agent config lives in .agent/.
|
|
8
|
+
|
|
9
|
+
Per-agent entry files (CLAUDE.md, AGENTS.md, GEMINI.md, .cursorrules) hold only that agent's
|
|
10
|
+
own supplements; the shared rules live in AI-RULES.md, never duplicated per agent.
|
|
11
|
+
|
|
12
|
+
This instance was created from the @vortex-os/base framework. The framework code lives in
|
|
13
|
+
node_modules/@vortex-os/; you do not edit it. You work in data/ and use the vortex CLI and
|
|
14
|
+
its slash-commands (run `npx vortex --list` to see them).
|
|
@@ -1,27 +1,27 @@
|
|
|
1
|
-
---
|
|
2
|
-
type: agent-entry
|
|
3
|
-
status: active
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# VortEX Instance — Codex CLI entry
|
|
7
|
-
|
|
8
|
-
This is the file Codex CLI auto-loads in this VortEX instance. It holds only Codex-specific supplements; the rules shared by **every** agent live in `AI-RULES.md`.
|
|
9
|
-
|
|
10
|
-
## CRITICAL — load the shared rules first
|
|
11
|
-
|
|
12
|
-
The shared rules, default behaviors, `data/` layout, and commands for every agent in this instance are defined in **`AI-RULES.md`**. You MUST read and follow `./AI-RULES.md` **before responding to any request** — it is required reading, not optional.
|
|
13
|
-
|
|
14
|
-
👉 Read now: `./AI-RULES.md`
|
|
15
|
-
|
|
16
|
-
> Codex CLI has no auto-import directive, so this instruction *is* the load mechanism — do not skip it. Any other `AGENTS.md`-aware tool that reads this file must do the same: treat `AI-RULES.md` as the shared source of truth.
|
|
17
|
-
|
|
18
|
-
## Codex-specific supplements
|
|
19
|
-
|
|
20
|
-
- Dedicated dir: `.codex/`
|
|
21
|
-
- Shared dir: `.agent/`
|
|
22
|
-
|
|
23
|
-
## Cross-check tool (per `AI-RULES.md` → "Cross-check significant decisions")
|
|
24
|
-
|
|
25
|
-
For a **cross-model** check, Codex can call Claude Code headless: `claude -p "<prompt>"`. Otherwise use a clean-context Codex sub-task with only the relevant facts. Return only the verdict — you still decide and close by verifying against the code/tests. When you shell out to `claude -p`: close stdin (`< /dev/null`) so it doesn't wait ~3s for input, and set your own timeout.
|
|
26
|
-
|
|
27
|
-
For the **security-review** lens (per `AI-RULES.md` → "Security-review code changes"), run the same kind of pass but focused on security — path traversal / arbitrary write, command / SQL / template injection, secret leakage, SSRF, unsafe deserialization, missing input validation — via `claude -p` (cross-model) or a clean-context Codex sub-task given only the diff + the threat surface.
|
|
1
|
+
---
|
|
2
|
+
type: agent-entry
|
|
3
|
+
status: active
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# VortEX Instance — Codex CLI entry
|
|
7
|
+
|
|
8
|
+
This is the file Codex CLI auto-loads in this VortEX instance. It holds only Codex-specific supplements; the rules shared by **every** agent live in `AI-RULES.md`.
|
|
9
|
+
|
|
10
|
+
## CRITICAL — load the shared rules first
|
|
11
|
+
|
|
12
|
+
The shared rules, default behaviors, `data/` layout, and commands for every agent in this instance are defined in **`AI-RULES.md`**. You MUST read and follow `./AI-RULES.md` **before responding to any request** — it is required reading, not optional.
|
|
13
|
+
|
|
14
|
+
👉 Read now: `./AI-RULES.md`
|
|
15
|
+
|
|
16
|
+
> Codex CLI has no auto-import directive, so this instruction *is* the load mechanism — do not skip it. Any other `AGENTS.md`-aware tool that reads this file must do the same: treat `AI-RULES.md` as the shared source of truth.
|
|
17
|
+
|
|
18
|
+
## Codex-specific supplements
|
|
19
|
+
|
|
20
|
+
- Dedicated dir: `.codex/`
|
|
21
|
+
- Shared dir: `.agent/`
|
|
22
|
+
|
|
23
|
+
## Cross-check tool (per `AI-RULES.md` → "Cross-check significant decisions")
|
|
24
|
+
|
|
25
|
+
For a **cross-model** check, Codex can call Claude Code headless: `claude -p "<prompt>"`. Otherwise use a clean-context Codex sub-task with only the relevant facts. Return only the verdict — you still decide and close by verifying against the code/tests. When you shell out to `claude -p`: close stdin (`< /dev/null`) so it doesn't wait ~3s for input, and set your own timeout.
|
|
26
|
+
|
|
27
|
+
For the **security-review** lens (per `AI-RULES.md` → "Security-review code changes"), run the same kind of pass but focused on security — path traversal / arbitrary write, command / SQL / template injection, secret leakage, SSRF, unsafe deserialization, missing input validation — via `claude -p` (cross-model) or a clean-context Codex sub-task given only the diff + the threat surface.
|
|
@@ -33,6 +33,7 @@ Operating principle: **auto-maintain the operational memory; propose changes to
|
|
|
33
33
|
- **Worklog.** As a work unit completes — and at session wind-down ("that's it for today", "wrap up") — ensure today's worklog exists and append the work. The agent is the primary path; the net is the **next session's start report**, which flags any day with commits but no worklog. Backfill each (newest first, **at most 3 per session** — list the rest): reconstruct what happened from that day's archived session(s), or from its commits/diffs when there is no archive, write it via `ensureWorklogEntry`, and **open the file with a one-line note that it was reconstructed after the fact** (which day was missed, when and from what it was rebuilt — in the worklog's language) so it is not taken for a live-authored record; then tell the user plainly what you filled in. Skip a day with nothing substantive, and skip entirely if the pull diverged.
|
|
34
34
|
- **Session hand-off.** At wind-down — or whenever the user asks to hand off / continue later ("hand off", "wrap up so I can resume", "leave it for next time", "인수인계") — write a hand-off with `/handoff` (or `npx vortex handoff "<title>"`): **one file per session** under `data/_handoff/`, carrying the concrete next steps as unchecked `- [ ]` items, plus the current state and the pointers to resume from (files, commit hashes, decisions). A hand-off is a **forward-looking baton kept SEPARATE from the worklog** (the permanent "what happened" record): put next steps + how-to-resume here, leave the narrative in the worklog. One session that spans several topics writes **ONE** hand-off covering all of them — never a per-topic split, never a "hand-off" worklog file, and never carry another session's items forward (concurrent sessions each leave their own). The next session's start report surfaces the active hand-off(s) automatically (multiple are normal), and hand-offs older than the retention window (default 7 days) are swept to `_handoff/_archive/` on their own — so you never tidy them by hand.
|
|
35
35
|
- **Decisions.** When the user makes a *substantive* decision ("let's go with X"), record it in the Decision Log. Real decisions, not casual asides.
|
|
36
|
+
- **Failure ledger (the self-improvement loop).** When one of your own failures becomes visible — the user corrects you or pushes back on a mistake, a verification you ran disproves your work, a cross-check finds a real defect — record it: `npx vortex failure record --key <root-cause-key> --what "<one line>" [--signal user_pushback|tool_error|test_failure|cross_check_miss|guard_denied|self_detected] [--severity low|medium|high] [--rule <memory-slug-if-one-already-covers-it>] [--evidence <path>]`. The **key names the root cause, in stable kebab-case** — the same cause must reuse the same key (check `npx vortex failure list` when unsure), because the count is what drives escalation. The command returns the occurrence count and the **ladder stage**; relay its one-line note. The ladder: **1st** occurrence = recorded; **2nd** while a rule already covers it = that rule's write-time gate is **mandatory** for the rest of the session; **3rd+** = **propose** a deterministic defense to the user (a hook, a pre-commit check, a wrapper script — whatever removes the failure class mechanically), never self-install it. Promoting a lesson into `data/_memory/` requires: evidence links to the ledger entries, a replay check ("would this rule have prevented the recorded cases?"), and a conflict check against existing memories — then propose it as a diff for approval. The session-start report surfaces recurring keys automatically, so escalation never depends on anyone's memory. Mistakes by the user are NOT failures to ledger; this loop is about the agent's own repeated misses. **Write-guard denials record themselves** (the hook appends the ledger entry, with a per-incident dedup) — when a write is denied, fix the write and relay what happened, but do NOT record it again by hand.
|
|
36
37
|
- **Ambient recall** (read-only). When the user references earlier work, run `/recall <the reference>` and weave a confident hit into one sentence. One nudge, not a list; drop it if waved off. Conversations are vectorized automatically at session start. With the `@vortex-os/memory-extended` add-on installed, the first session also sets recall up on its own — downloading the local search model (~470 MB, once) in the **background** — so recall and these ambient lookups just work with no prompt and no manual step (the session-start report notes the one-time download while it runs; relay that). To keep that download manual instead (metered line, CI), set `autoRecord.vectorizeAutoDownload: false` (or env `VORTEX_VECTORIZE_AUTO_DOWNLOAD=0`) and set it up once with `/recall` or `vortex vectorize`.
|
|
37
38
|
|
|
38
39
|
These are **append-only and transparent**: do them without a yes/no prompt, but surface a brief, non-blocking note so the user always sees what was recorded. Everything lands in git and is reversible; disable any of them via `.agent/vortex.json` (`autoRecord.*`). `git push` stays **explicit**, never automatic.
|
|
@@ -98,6 +99,7 @@ Your content lives under `data/`. Each category answers a different question:
|
|
|
98
99
|
| `runbooks/` | "What is the procedure when X breaks?" — repeatable steps with `last_tested` aging. |
|
|
99
100
|
| `hubs/` | "Where do I find everything about topic Z?" — a cross-cut landing page; grows organically once 3+ categories accumulate on the same topic. |
|
|
100
101
|
| `inbox/` | "Where does an unfiled note land before I sort it?" |
|
|
102
|
+
| `_failures/` | "Which of the AGENT's failures keep recurring?" — append-only ledger (one file per occurrence, grouped by `recurrence_key`) that drives the self-improvement escalation ladder; see "Default behaviors" → Failure ledger. |
|
|
101
103
|
|
|
102
104
|
A typical day flows from short-lived capture (worklog) into longer-lived artifacts (decision-log, runbook, memory, hub). These are common promotion patterns, not required flows.
|
|
103
105
|
|
|
@@ -125,7 +127,7 @@ These slash-commands are installed into `.claude/commands/` by `vortex init` (th
|
|
|
125
127
|
| `/resume` | After a crash / abrupt close with no handoff: back-traces git state + the last session transcript to find what was in progress and whether it's wrapped up. Read-only; cheap signals first, transcript only if needed. |
|
|
126
128
|
| `/reindex [dir]` | Regenerate `_INDEX.md` for a category (or all). Idempotent. |
|
|
127
129
|
|
|
128
|
-
You can also run any of these directly: `npx vortex <command> [args]`.
|
|
130
|
+
You can also run any of these directly: `npx vortex <command> [args]`. CLI-only (no slash command): `vortex failure record|list` — the failure ledger behind the self-improvement loop (see "Default behaviors"); `vortex statusline install [--lite]` — optional colored Claude Code status bar; `vortex guard write` — the write-guard hook body `vortex init` wires (denies literal control bytes in file writes; remove the PreToolUse group in `.claude/settings.json` to opt out).
|
|
129
131
|
|
|
130
132
|
## Optional add-ons
|
|
131
133
|
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
# GEMINI.md
|
|
2
|
-
|
|
3
|
-
This is the first file Gemini CLI reads when working in this VortEX instance.
|
|
4
|
-
|
|
5
|
-
## Shared rules (required — auto-loaded)
|
|
6
|
-
|
|
7
|
-
The rules shared by every agent in this instance live in `AI-RULES.md`, imported here so Gemini CLI loads it automatically at launch:
|
|
8
|
-
|
|
9
|
-
@AI-RULES.md
|
|
10
|
-
|
|
11
|
-
This `GEMINI.md` holds only Gemini-specific supplements; everything shared is in `AI-RULES.md` (above).
|
|
12
|
-
|
|
13
|
-
## Gemini-specific config
|
|
14
|
-
|
|
15
|
-
- Dedicated dir: `.gemini/`
|
|
16
|
-
- Shared dir: `.agent/`
|
|
1
|
+
# GEMINI.md
|
|
2
|
+
|
|
3
|
+
This is the first file Gemini CLI reads when working in this VortEX instance.
|
|
4
|
+
|
|
5
|
+
## Shared rules (required — auto-loaded)
|
|
6
|
+
|
|
7
|
+
The rules shared by every agent in this instance live in `AI-RULES.md`, imported here so Gemini CLI loads it automatically at launch:
|
|
8
|
+
|
|
9
|
+
@AI-RULES.md
|
|
10
|
+
|
|
11
|
+
This `GEMINI.md` holds only Gemini-specific supplements; everything shared is in `AI-RULES.md` (above).
|
|
12
|
+
|
|
13
|
+
## Gemini-specific config
|
|
14
|
+
|
|
15
|
+
- Dedicated dir: `.gemini/`
|
|
16
|
+
- Shared dir: `.agent/`
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../plugins/session-rituals/src/catch-up.ts"],"sourcesContent":["import type { ModuleContext } from \"@vortex-os/core\";\r\n// Type-only — erased at compile time, so importing it does NOT pull the\r\n// `@vortex-os/memory-extended` add-on (or its native sqlite/level deps) into\r\n// the module graph of consumers that only need the types. The runtime engine\r\n// is loaded lazily inside the function body via `await import(...)`.\r\nimport type { sessionArchive } from \"@vortex-os/memory-extended\";\r\n\r\n/**\r\n * Start-of-session \"catch-up\": fold conversation transcripts into the local\r\n * search archive without the user ever having to wrap up a session.\r\n *\r\n * Two sources, one pass:\r\n * - **local (a)** — this machine's own transcripts that are not archived yet,\r\n * read from every detected agent host's transcript store (Claude Code,\r\n * Codex, Gemini) and scoped to the current project. Because all hosts are\r\n * swept on every start, a single Claude Code session-start also folds in the\r\n * Codex/Gemini sessions you ran in the same project, into one archive.\r\n * - **pulled (b)** — transcripts created on another machine that arrived as\r\n * normalized text via git sync. Their text is present but this machine's\r\n * DB (local, derived, gitignored) has never indexed them.\r\n *\r\n * Text only — vectorization is deferred to recall/rebuild so session start\r\n * stays fast. The whole step is gated by `autoRecord.archive` at the call site\r\n * and is best-effort: callers should treat a thrown archive backend (e.g. the\r\n * native sqlite module not built) as \"skip\", never as a fatal start error.\r\n */\r\nexport interface CatchUpResult {\r\n /** Local transcripts newly archived this run (source a). */\r\n readonly ingestedLocal: number;\r\n /** Normalized transcripts from another machine newly indexed (source b). */\r\n readonly indexedPulled: number;\r\n /** Per-session ingest errors (source a). */\r\n readonly errors: number;\r\n}\r\n\r\nexport interface CatchUpOptions {\r\n /** Restrict local ingest to one project's transcripts. Default: `ctx.repoRoot`. */\r\n readonly cwd?: string;\r\n /**\r\n * Transcript adapters for local ingest. Default: all CLI hosts — Claude Code,\r\n * Codex, and Gemini. Each adapter's `detect()` returns false when its host is\r\n * absent, so registering all three is ~free on a machine that only uses one.\r\n * (Claude Desktop is opt-in — it needs the `classic-level` dependency — so it\r\n * is not in the default set; a caller can add it.) Tests inject fakes (or a\r\n * sandbox `env.home`) so the scan never touches the real home directory.\r\n */\r\n readonly adapters?: sessionArchive.IngestParams[\"adapters\"];\r\n /** Adapter environment override (e.g. a sandbox HOME). Tests use this. */\r\n readonly env?: sessionArchive.IngestParams[\"env\"];\r\n}\r\n\r\nexport async function catchUpSessions(\r\n ctx: ModuleContext,\r\n opts?: CatchUpOptions,\r\n): Promise<CatchUpResult> {\r\n // Lazy-load the optional add-on. Base ships without `memory-extended`; this\r\n // import resolves only when the add-on is installed alongside it. The call\r\n // site already gates this step on `autoRecord.archive` and treats a thrown\r\n // backend as \"skip\", so a missing add-on surfaces as a normal load error\r\n // the caller can catch.\r\n const { sessionArchive } = await import(\"@vortex-os/memory-extended\");\r\n\r\n const dataDir = ctx.dataDir;\r\n const cwd = opts?.cwd ?? ctx.repoRoot;\r\n const adapters = opts?.adapters ?? [\r\n sessionArchive.claudeCodeAdapter,\r\n sessionArchive.codexAdapter,\r\n sessionArchive.geminiAdapter,\r\n ];\r\n\r\n // (a) Ingest this machine's not-yet-archived transcripts for the current\r\n // project. Writes the normalized copy into the archive (which git syncs) and\r\n // a local DB row. Text only — no vectorization here.\r\n const ingestResult = await sessionArchive.ingest({ adapters, dataDir, cwd, env: opts?.env });\r\n\r\n // (b) Index normalized transcripts that arrived from another machine via git\r\n // pull — their text is on disk but this machine's DB has no row yet.\r\n const store = new sessionArchive.SessionArchiveStore(dataDir);\r\n let indexedPulled = 0;\r\n try {\r\n indexedPulled = store.reindexFromNormalized().indexed;\r\n } finally {\r\n store.close();\r\n }\r\n\r\n return {\r\n ingestedLocal: ingestResult.sessionsIngested,\r\n indexedPulled,\r\n errors: ingestResult.errors.length,\r\n };\r\n}\r\n"],"mappings":";AAmDA,eAAsB,gBACpB,KACA,MAAqB;AAOrB,QAAM,EAAE,eAAc,IAAK,MAAM,OAAO,4BAA4B;AAEpE,QAAM,UAAU,IAAI;AACpB,QAAM,MAAM,MAAM,OAAO,IAAI;AAC7B,QAAM,WAAW,MAAM,YAAY;IACjC,eAAe;IACf,eAAe;IACf,eAAe;;AAMjB,QAAM,eAAe,MAAM,eAAe,OAAO,EAAE,UAAU,SAAS,KAAK,KAAK,MAAM,IAAG,CAAE;AAI3F,QAAM,QAAQ,IAAI,eAAe,oBAAoB,OAAO;AAC5D,MAAI,gBAAgB;AACpB,MAAI;AACF,oBAAgB,MAAM,sBAAqB,EAAG;EAChD;AACE,UAAM,MAAK;EACb;AAEA,SAAO;IACL,eAAe,aAAa;IAC5B;IACA,QAAQ,aAAa,OAAO;;AAEhC;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../plugins/session-rituals/src/vectorize.ts"],"sourcesContent":["import { join } from \"node:path\";\r\nimport type { ModuleContext } from \"@vortex-os/core\";\r\n// Type-only — erased at compile time, so importing it does NOT pull the\r\n// `@vortex-os/memory-extended` add-on (or its native deps) into the module\r\n// graph of consumers that only need the types. The runtime engine is loaded\r\n// lazily inside the function body via `await import(...)`.\r\nimport type { vector } from \"@vortex-os/memory-extended\";\r\n\r\n/**\r\n * Start-of-session vectorization: fold newly-archived sessions and memories\r\n * into the semantic-recall index so `/recall` and the ambient \"did we discuss\r\n * this?\" path stay current without a manual rebuild.\r\n *\r\n * The heavy one-time cost (downloading the embedding model + embedding the\r\n * whole backlog) happens the first time recall is set up — via an explicit\r\n * `/recall`, or via the `vortex vectorize` setup worker that session start\r\n * spawns in the background when the add-on is installed and\r\n * `autoRecord.vectorizeAutoDownload` is on. From then on the model is cached\r\n * and this runs **incrementally** — memory rows whose hash changed, plus\r\n * sessions not yet vectorized (`onlyMissing`) — so the per-session-start cost\r\n * is small.\r\n *\r\n * `allowDownload` defaults false, so the automatic inline session-start call is\r\n * cache-only and never triggers a model download on its own; only the explicit\r\n * setup paths pass `allowDownload: true`. A throw is treated as \"skip\"\r\n * (best-effort): a missing model, a rebuild hiccup, or the add-on being absent\r\n * must never fail session start.\r\n */\r\nexport interface VectorizeResult {\r\n /** Memory rows (re)embedded this run. */\r\n readonly memories: number;\r\n /** Session chunks embedded this run (only the not-yet-vectorized ones). */\r\n readonly sessionChunks: number;\r\n}\r\n\r\nexport interface VectorizeOptions {\r\n /**\r\n * Embedding function. Default: the bundled local model\r\n * (`vector.createLocalEmbedder()`). Tests inject a deterministic fake so the\r\n * run needs no model and no network.\r\n */\r\n readonly embed?: vector.EmbedFn;\r\n /** Override the index DB path. Default `<dataDir>/_indexes/memory.sqlite`. */\r\n readonly dbPath?: string;\r\n /**\r\n * Allow the embedder to DOWNLOAD the model when it is not cached. Default\r\n * `false`: the automatic session-start path stays cache-only (a cache miss\r\n * throws fast — no ~470 MB download). The explicit `vortex vectorize` setup\r\n * worker passes `true` to bring the model into the cache the first time.\r\n * Ignored when `embed` is supplied (tests inject their own embedder).\r\n */\r\n readonly allowDownload?: boolean;\r\n}\r\n\r\nexport async function vectorizeIndex(\r\n ctx: ModuleContext,\r\n opts?: VectorizeOptions,\r\n): Promise<VectorizeResult> {\r\n // Lazy-load the optional add-on (mirrors catch-up). Resolves only when\r\n // `memory-extended` is installed alongside base; a lean install throws here\r\n // and the call site treats it as \"skip\".\r\n const { sqlite, vector, sessionArchive } = await import(\"@vortex-os/memory-extended\");\r\n\r\n const dbPath = opts?.dbPath ?? join(ctx.dataDir, \"_indexes\", \"memory.sqlite\");\r\n const memoryDir = join(ctx.dataDir, \"_memory\");\r\n // `localFilesOnly` is the real safety gate for this automatic path: if the\r\n // embedding model is not already cached, the first embed throws immediately\r\n // (no ~470 MB download) and the caller skips. So session-start vectorization\r\n // can never trigger an unprompted download, even if the index db exists for\r\n // some other reason. The model is downloaded only by the explicit, consented\r\n // `/recall` setup, which uses the default network-enabled embedder.\r\n const embed = opts?.embed ?? vector.createLocalEmbedder({ localFilesOnly: !opts?.allowDownload });\r\n\r\n const sqlStore = new sqlite.MemorySqliteStore(dbPath);\r\n const vecStore = new vector.MemoryVectorStore({ db: dbPath });\r\n const archive = new sessionArchive.SessionArchiveStore(ctx.dataDir);\r\n try {\r\n // Memory: refresh the sqlite hard-filter index (no model needed) then its\r\n // vectors. Best-effort per step — a missing _memory dir must not abort the\r\n // session pass.\r\n try {\r\n await sqlite.rebuildFromMemoryDir(sqlStore, memoryDir);\r\n } catch {\r\n // no _memory dir yet — nothing to index\r\n }\r\n // Both incremental (`onlyMissing`): embed only newly-added memories and\r\n // not-yet-vectorized sessions, so the per-session-start cost is bounded to\r\n // what actually changed — usually nothing or a single new session.\r\n const mem = await vecStore.rebuild(sqlStore, embed, { onlyMissing: true });\r\n const sess = await vecStore.rebuildSessions(archive, embed, { onlyMissing: true });\r\n return { memories: mem.indexed, sessionChunks: sess.chunks };\r\n } finally {\r\n archive.close();\r\n vecStore.close();\r\n sqlStore.close();\r\n }\r\n}\r\n"],"mappings":";;;AAAA,SAAS,YAAY;AAsDrB,eAAsB,eACpB,KACA,MAAuB;AAKvB,QAAM,EAAE,QAAQ,QAAQ,eAAc,IAAK,MAAM,OAAO,4BAA4B;AAEpF,QAAM,SAAS,MAAM,UAAU,KAAK,IAAI,SAAS,YAAY,eAAe;AAC5E,QAAM,YAAY,KAAK,IAAI,SAAS,SAAS;AAO7C,QAAM,QAAQ,MAAM,SAAS,OAAO,oBAAoB,EAAE,gBAAgB,CAAC,MAAM,cAAa,CAAE;AAEhG,QAAM,WAAW,IAAI,OAAO,kBAAkB,MAAM;AACpD,QAAM,WAAW,IAAI,OAAO,kBAAkB,EAAE,IAAI,OAAM,CAAE;AAC5D,QAAM,UAAU,IAAI,eAAe,oBAAoB,IAAI,OAAO;AAClE,MAAI;AAIF,QAAI;AACF,YAAM,OAAO,qBAAqB,UAAU,SAAS;IACvD,QAAQ;IAER;AAIA,UAAM,MAAM,MAAM,SAAS,QAAQ,UAAU,OAAO,EAAE,aAAa,KAAI,CAAE;AACzE,UAAM,OAAO,MAAM,SAAS,gBAAgB,SAAS,OAAO,EAAE,aAAa,KAAI,CAAE;AACjF,WAAO,EAAE,UAAU,IAAI,SAAS,eAAe,KAAK,OAAM;EAC5D;AACE,YAAQ,MAAK;AACb,aAAS,MAAK;AACd,aAAS,MAAK;EAChB;AACF;","names":[]}
|
|
File without changes
|