memorix 1.2.3 → 1.2.4
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/CHANGELOG.md +20 -0
- package/README.md +3 -3
- package/README.zh-CN.md +3 -3
- package/dist/cli/index.js +5187 -4730
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +416 -46
- package/dist/index.js.map +1 -1
- package/dist/maintenance-runner.js +97 -18
- package/dist/maintenance-runner.js.map +1 -1
- package/dist/memcode-runtime/CHANGELOG.md +20 -0
- package/dist/sdk.js +416 -46
- package/dist/sdk.js.map +1 -1
- package/docs/1.2.4-PERSISTENT-MEMORY-DELIVERY.md +86 -0
- package/docs/AGENT_OPERATOR_PLAYBOOK.md +13 -1
- package/docs/API_REFERENCE.md +13 -3
- package/docs/dev-log/progress.txt +48 -7
- package/package.json +1 -1
- package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -1
- package/src/cli/capability-map.ts +1 -1
- package/src/cli/command-guide.ts +4 -1
- package/src/cli/commands/agent-integrations.ts +5 -1
- package/src/cli/commands/codegraph.ts +1 -1
- package/src/cli/commands/context.ts +9 -1
- package/src/cli/commands/resume.ts +31 -0
- package/src/cli/index.ts +3 -1
- package/src/codegraph/auto-context.ts +54 -1
- package/src/codegraph/task-lens.ts +29 -0
- package/src/compact/token-budget.ts +16 -1
- package/src/config/toml-loader.ts +9 -5
- package/src/hooks/handler.ts +127 -66
- package/src/hooks/installers/index.ts +5 -4
- package/src/hooks/official-skills.ts +6 -4
- package/src/hooks/rules/memorix-agent-rules.md +9 -7
- package/src/knowledge/context-assembly.ts +4 -1
- package/src/knowledge/workset.ts +89 -1
- package/src/memory/session.ts +144 -10
- package/src/server.ts +137 -8
- package/src/store/bun-sqlite-compat.ts +118 -15
- package/src/store/sqlite-db.ts +3 -3
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# 1.2.4 Persistent Memory Delivery
|
|
2
|
+
|
|
3
|
+
**Status:** release-ready
|
|
4
|
+
**Date:** 2026-07-26
|
|
5
|
+
|
|
6
|
+
## Problem
|
|
7
|
+
|
|
8
|
+
Memorix already has durable project memory, Code State, session summaries,
|
|
9
|
+
knowledge, visibility policy, CLI, and MCP. The delivery path was uneven:
|
|
10
|
+
|
|
11
|
+
- A fresh agent without a visible MCP tool could guess through many CLI commands
|
|
12
|
+
before finding the relevant state.
|
|
13
|
+
- The normal task Workset deliberately avoided generic old-chat dumping, but it
|
|
14
|
+
also hid the small amount of prior work that matters when a user says
|
|
15
|
+
"continue" or "take over".
|
|
16
|
+
- A new retrieval path must not bypass the session visibility fix shipped in
|
|
17
|
+
1.2.3.
|
|
18
|
+
|
|
19
|
+
This release improves delivery, not storage. It does not create a handoff
|
|
20
|
+
database, a transcript archive, or another default MCP tool.
|
|
21
|
+
|
|
22
|
+
## Product Contract
|
|
23
|
+
|
|
24
|
+
| Situation | Delivery |
|
|
25
|
+
| --- | --- |
|
|
26
|
+
| Start a new, unrelated task | A bounded task Workset. No automatic prior-session text. |
|
|
27
|
+
| Continue or take over prior work | The same Workset plus a bounded prior-work projection. |
|
|
28
|
+
| MCP is available | Use the existing `memorix_project_context` tool with the real task. |
|
|
29
|
+
| MCP is unavailable | Make one CLI call: `memorix resume "<task>" --json` for continuation or `memorix context "<task>" --json` for a new task. |
|
|
30
|
+
| Claude Code hook is installed | An explicit continuation prompt receives the same bounded prior-work Workset through the official `UserPromptSubmit` context channel, unless `memory.inject = "silent"`. |
|
|
31
|
+
| Read-only assessment | The brief may be read, but Memorix does not persist a new observation unless the user explicitly asks to save one. |
|
|
32
|
+
| Need a deliberate session handoff | Keep using the explicit session/handoff surfaces; this projection does not replace them. |
|
|
33
|
+
|
|
34
|
+
Continuation detection is independent of the underlying task lens. For example,
|
|
35
|
+
"continue fixing the authentication timeout" is still a `bugfix` task, with
|
|
36
|
+
bugfix verification guidance, plus bounded prior-work evidence.
|
|
37
|
+
|
|
38
|
+
## Delivery Model
|
|
39
|
+
|
|
40
|
+
```text
|
|
41
|
+
Canonical project identity
|
|
42
|
+
-> existing SQLite sessions + durable observations + visibility policy
|
|
43
|
+
-> Task Workset
|
|
44
|
+
-> normal task: current facts, code state, selected evidence
|
|
45
|
+
-> continuation: normal task + one session summary + <=3 durable anchors
|
|
46
|
+
-> existing MCP Project Context, direct CLI Context/Resume, or an eligible host hook
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The projection is intentionally small and source-aware:
|
|
50
|
+
|
|
51
|
+
- The latest meaningful completed session summary is eligible.
|
|
52
|
+
- At most three durable decision/fix/discovery-style observations are eligible.
|
|
53
|
+
- Project, team, and personal visibility is enforced with the caller's reader.
|
|
54
|
+
- The `ContextReceipt` records selected or budget-withheld continuation items.
|
|
55
|
+
- Current code remains the authority; historical material is a lead, not an
|
|
56
|
+
instruction.
|
|
57
|
+
|
|
58
|
+
## Explicit Non-Goals
|
|
59
|
+
|
|
60
|
+
- Do not inject arbitrary transcript history into every request.
|
|
61
|
+
- Do not infer continuation from every mention of a past file or release.
|
|
62
|
+
- Do not add a separate `handoff` store or duplicate session memory.
|
|
63
|
+
- Do not add an eighth default MCP tool merely for CLI parity.
|
|
64
|
+
- Do not make an unbound terminal see personal or team-scoped memory.
|
|
65
|
+
|
|
66
|
+
## Acceptance Gates
|
|
67
|
+
|
|
68
|
+
1. A normal new task has no `Resume from prior work` section.
|
|
69
|
+
2. A continuation task has a bounded section and keeps its real task lens.
|
|
70
|
+
3. `memorix resume` and `memorix_project_context` share the Workset contract.
|
|
71
|
+
4. A foreign identity cannot receive personal continuation evidence.
|
|
72
|
+
5. Generated guidance performs one precise CLI fallback instead of help/search
|
|
73
|
+
enumeration or inferring an empty memory store from project files.
|
|
74
|
+
6. Compiled package smoke proves CLI positional parsing and stdio MCP output.
|
|
75
|
+
7. Claude Code receives an explicit continuation through its official
|
|
76
|
+
`UserPromptSubmit.additionalContext` path; an unrelated prompt stays quiet.
|
|
77
|
+
8. A fresh agent-style user path evaluates the installed package without
|
|
78
|
+
modifying the maintainer's real agent configuration.
|
|
79
|
+
9. A fresh package installation whose optional native SQLite binary cannot load
|
|
80
|
+
still preserves the same `memorix.db` across separate CLI and hook processes
|
|
81
|
+
through the supported Node 22 SQLite fallback.
|
|
82
|
+
10. A successful Autopilot brief preserves the exact continuation flag/path or
|
|
83
|
+
command needed to act, and does not trigger redundant memory retrieval by
|
|
84
|
+
default.
|
|
85
|
+
11. A read-only continuation assessment neither replays already-delivered
|
|
86
|
+
durable evidence nor writes a new memory without an explicit user request.
|
|
@@ -38,7 +38,7 @@ For the 1.2 release line, the visible product shape is:
|
|
|
38
38
|
- model lanes are separate: `[memory.llm]` for formation/rerank/summaries, `[embedding]` for semantic search, `[agent]` for the model memcode talks to while coding
|
|
39
39
|
- legacy `memorix.yml`, `.env`, and `~/.memorix/config.json` are compatibility inputs, not the recommended setup path
|
|
40
40
|
- generated agent rules treat `memorix_session_start` as optional unless explicit session semantics matter
|
|
41
|
-
- `memorix_project_context` / `memorix context
|
|
41
|
+
- `memorix_project_context` / `memorix context "..."` is the normal black-box entry for non-trivial coding work: it assembles a bounded task Workset instead of injecting a generic memory dump; `memorix resume "..."` is the explicit CLI continuation projection
|
|
42
42
|
- Code State keeps local snapshots and freshness links; a healthy pre-existing local CodeGraph index can add a bounded semantic outline, but Memorix never initializes or synchronizes that external index itself
|
|
43
43
|
- `memorix knowledge` is an explicit review path for source-backed Markdown knowledge and canonical workflows. Do not initialize a versioned workspace or apply a proposal unless the user asks for that managed artifact
|
|
44
44
|
- integration surfaces are agent-specific: Claude Code, Codex, GitHub Copilot CLI, Antigravity, and Hermes receive plugin packages; OpenClaw receives a compatible bundle; Pi and Oh-my-Pi receive package entries; Gemini CLI receives an extension package; OpenCode receives a plugin file and skill; Cursor and other agents receive MCP/rules/hooks where supported
|
|
@@ -766,6 +766,18 @@ memorix doctor
|
|
|
766
766
|
|
|
767
767
|
to inspect active runtime status.
|
|
768
768
|
|
|
769
|
+
### 9. Does a fresh CLI install report a `better-sqlite3` binding error?
|
|
770
|
+
|
|
771
|
+
On the supported Node 22 runtime, current Memorix releases automatically use
|
|
772
|
+
Node's built-in SQLite with the same local `memorix.db` when the optional
|
|
773
|
+
`better-sqlite3` native binary is unavailable. Upgrade the package and rerun
|
|
774
|
+
the command; do not delete the data directory or create a replacement database.
|
|
775
|
+
|
|
776
|
+
Node may print its own experimental SQLite warning on that fallback path. It is
|
|
777
|
+
not a memory-loss condition. If Memorix instead reports that SQLite is
|
|
778
|
+
unavailable, confirm the installed Node version satisfies the package engine
|
|
779
|
+
and include the exact error when filing an issue.
|
|
780
|
+
|
|
769
781
|
---
|
|
770
782
|
|
|
771
783
|
## 11. What Not to Do
|
package/docs/API_REFERENCE.md
CHANGED
|
@@ -59,7 +59,7 @@ The current CLI namespaces are:
|
|
|
59
59
|
Typical examples:
|
|
60
60
|
|
|
61
61
|
```bash
|
|
62
|
-
memorix --cwd /path/to/repo
|
|
62
|
+
memorix --cwd /path/to/repo resume "continue auth bug"
|
|
63
63
|
memorix identity join --agent-type codex --name codex-main
|
|
64
64
|
memorix session start --agent codex-main --agent-type codex --join-team --use
|
|
65
65
|
memorix memory search --query "release blocker"
|
|
@@ -96,6 +96,8 @@ memorix codegraph status
|
|
|
96
96
|
memorix codegraph status --json
|
|
97
97
|
memorix context
|
|
98
98
|
memorix context --task "continue auth bug"
|
|
99
|
+
memorix context "continue auth bug"
|
|
100
|
+
memorix resume "continue auth bug"
|
|
99
101
|
memorix context --task "prepare 1.1.7 release"
|
|
100
102
|
memorix explain
|
|
101
103
|
memorix codegraph context-pack --task "continue auth bug"
|
|
@@ -107,11 +109,11 @@ MCP:
|
|
|
107
109
|
- `memorix_codegraph_status` returns provider/index counts for the current project.
|
|
108
110
|
- `memorix_context_pack` builds a task-specific packet with reliable current memories, lower-trust unbound memories, current code facts, freshness warnings, suggested reads, and suggested verification.
|
|
109
111
|
|
|
110
|
-
`memorix context` defaults to `--refresh auto`, so first use can seed Code State without a separate manual `memorix codegraph refresh`. Its brief puts live package/changelog/Git facts before memory hints and flags old `progress.txt` / dev-log notes as historical when they predate the latest changelog, so agents should treat current facts as the source of truth when files disagree. Task lenses keep the packet shaped to the work: bugfix briefs prefer failing tests and repros, release briefs prefer metadata/changelog/package checks, and onboarding briefs prefer docs and entry points while hiding unrelated suspect details. Use `--refresh never` for read-only inspection and `--refresh always` when you want to force a fresh scan.
|
|
112
|
+
`memorix context` defaults to `--refresh auto`, so first use can seed Code State without a separate manual `memorix codegraph refresh`. Its brief puts live package/changelog/Git facts before memory hints and flags old `progress.txt` / dev-log notes as historical when they predate the latest changelog, so agents should treat current facts as the source of truth when files disagree. Task lenses keep the packet shaped to the work: bugfix briefs prefer failing tests and repros, release briefs prefer metadata/changelog/package checks, and onboarding briefs prefer docs and entry points while hiding unrelated suspect details. Continuation delivery is separate from the task lens: continuation language in `memorix_project_context` enables a bounded prior-work projection, and `memorix resume "..."` makes that choice explicit. It includes only the latest useful session summary and up to three readable durable memories; ordinary new tasks do not receive historical-session context. A completed MCP brief is the default retrieval boundary: search, detail, or Context Pack should expand it only for a named missing fact or an explicit request for deeper history. Use `--refresh never` for read-only inspection and `--refresh always` when you want to force a fresh scan.
|
|
111
113
|
|
|
112
114
|
Project-specific generated, vendored, or cache paths can be excluded from Code State with `[codegraph].exclude_patterns` in `memorix.toml` or `~/.memorix/config.toml` (`codegraph.excludePatterns` in legacy YAML). User patterns extend the built-in excludes and are applied to indexing, Project Context suggested reads, and Context Pack suggested reads.
|
|
113
115
|
|
|
114
|
-
SessionStart hooks keep the default minimal hint lightweight. When memory behavior is configured with `sessionInject=full`,
|
|
116
|
+
SessionStart hooks keep the default minimal hint lightweight. When memory behavior is configured with `sessionInject=full`, Codex receives the compact Memory Autopilot brief at session start instead of only listing recent text memories. Claude Code delivers an explicit continuation through its official `UserPromptSubmit` context channel, so a user saying “continue” receives the bounded prior-work brief even under the default minimal setting. Set `memory.inject = "silent"` to disable automatic hook delivery.
|
|
115
117
|
|
|
116
118
|
The intended loop for agents is: get the project brief when it helps, inspect the suggested current files, use stale or unbound memory only as a lead, store durable outcomes after the work changes the project, and resolve obsolete memories.
|
|
117
119
|
|
|
@@ -220,6 +222,10 @@ require a joined coordination identity; a personal record is readable only by
|
|
|
220
222
|
its creator and explicitly named recipients. Supplying a `topicKey` never
|
|
221
223
|
lets an agent overwrite a record outside its write scope.
|
|
222
224
|
|
|
225
|
+
When the current Autopilot task is read-only or explicitly says not to modify
|
|
226
|
+
files, `memorix_store` returns without writing. Use `overrideReadOnly: true`
|
|
227
|
+
only when the user explicitly asks to preserve a record during that task.
|
|
228
|
+
|
|
223
229
|
Example:
|
|
224
230
|
|
|
225
231
|
```json
|
|
@@ -252,6 +258,8 @@ Important inputs:
|
|
|
252
258
|
- `since`
|
|
253
259
|
- `until`
|
|
254
260
|
- `maxTokens`
|
|
261
|
+
- `purpose` when deliberately expanding beyond the current Autopilot brief
|
|
262
|
+
- `force: true` only when the user explicitly asks to re-read a record already represented in that brief
|
|
255
263
|
|
|
256
264
|
Typical uses:
|
|
257
265
|
|
|
@@ -281,6 +289,8 @@ Global example:
|
|
|
281
289
|
|
|
282
290
|
Fetch full observation detail.
|
|
283
291
|
|
|
292
|
+
After `memorix_project_context`, use `purpose` only for a named missing fact. Set `force: true` only when the user explicitly asks for the full underlying record already represented in the brief.
|
|
293
|
+
|
|
284
294
|
Supports two modes:
|
|
285
295
|
|
|
286
296
|
- `ids` for current-project observations
|
|
@@ -4,13 +4,54 @@
|
|
|
4
4
|
> older notes when they conflict.
|
|
5
5
|
|
|
6
6
|
## Current State
|
|
7
|
-
- Phase: 1.2.
|
|
8
|
-
- Branch: `codex/1.2.
|
|
9
|
-
- Last updated: 2026-07-
|
|
10
|
-
- Released baseline: `memorix@1.2.
|
|
11
|
-
`
|
|
12
|
-
- Goal:
|
|
13
|
-
|
|
7
|
+
- Phase: 1.2.4 persistent memory delivery release candidate
|
|
8
|
+
- Branch: `codex/1.2.4-persistent-memory-delivery`
|
|
9
|
+
- Last updated: 2026-07-26
|
|
10
|
+
- Released baseline: `memorix@1.2.3`, tag `v1.2.3`, `origin/main` at
|
|
11
|
+
`03ceb6d`.
|
|
12
|
+
- Goal: make the existing persistent memory layer deliver useful prior work
|
|
13
|
+
naturally across MCP and CLI without turning ordinary tasks into historical
|
|
14
|
+
context dumps.
|
|
15
|
+
|
|
16
|
+
## 1.2.4 Persistent Memory Delivery
|
|
17
|
+
- Continuation is now a bounded delivery projection over existing sessions and
|
|
18
|
+
durable observations, not a second handoff database. `TaskWorkset` keeps its
|
|
19
|
+
task lens (`bugfix`, `feature`, and so on) while independently recognizing a
|
|
20
|
+
continuation request.
|
|
21
|
+
- A continuation Workset includes at most the latest meaningful completed
|
|
22
|
+
session summary plus three readable durable memory anchors. Each item is
|
|
23
|
+
visible in the existing `ContextReceipt`; ordinary tasks do not receive it.
|
|
24
|
+
- `memorix resume "<task>"` is the CLI-equivalent continuation entry. The
|
|
25
|
+
existing MCP `memorix_project_context` receives the same behavior from the
|
|
26
|
+
user's task text, so the micro MCP profile stays at seven tools.
|
|
27
|
+
- Setup-generated rules now use a single fallback call when MCP is genuinely
|
|
28
|
+
unavailable: `memorix resume` for prior work or `memorix context` for new
|
|
29
|
+
work. They explicitly forbid help probing, command enumeration, and broad
|
|
30
|
+
search loops before the first brief.
|
|
31
|
+
- The visibility reader is carried into continuation retrieval, preserving
|
|
32
|
+
project aliases while failing closed for other agents' personal memory.
|
|
33
|
+
- Continuation text preserves whole technical identifiers instead of emitting a
|
|
34
|
+
misleading partial token. MCP Project Context also establishes a short-lived
|
|
35
|
+
delivery boundary: a follow-up search/detail request does not re-send an
|
|
36
|
+
already represented record unless the caller names a missing fact or the user
|
|
37
|
+
explicitly asks for the underlying detail.
|
|
38
|
+
- A read-only or "do not modify files" Autopilot task blocks `memorix_store`
|
|
39
|
+
by default. The only override is an explicit user request to persist a
|
|
40
|
+
record, represented by `overrideReadOnly: true`.
|
|
41
|
+
- A real Codex user-path smoke, in a fresh Git fixture seeded only through the
|
|
42
|
+
public CLI, made one natural-language continuation request. Codex received
|
|
43
|
+
the bounded brief, used one specific Context Pack expansion, was stopped
|
|
44
|
+
from re-reading already-covered memory, made no `memorix_store` write, and
|
|
45
|
+
returned the correct read-only next step.
|
|
46
|
+
- Completed local gates: TypeScript check, production build, focused
|
|
47
|
+
continuation/hook/CLI regressions, and the full suite (`237` files / `2677`
|
|
48
|
+
tests). A fresh packed install with optional native dependencies omitted used
|
|
49
|
+
Node SQLite to persist the same `memorix.db`; CLI resume retained
|
|
50
|
+
`AUTH_REFRESH_V2`; the micro MCP package surface exposed exactly seven tools
|
|
51
|
+
and enforced duplicate-retrieval plus read-only-write boundaries; and an
|
|
52
|
+
isolated Codex global setup wrote/activated the versioned Personal-marketplace
|
|
53
|
+
plugin with the matching skill guidance. Pending release gates: CI and
|
|
54
|
+
publish.
|
|
14
55
|
|
|
15
56
|
## 1.2.3 Session Context Visibility Hotfix
|
|
16
57
|
- A real installed-package CLI smoke found that a second coordination identity
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@ export const CLI_NATIVE_PARITY: Record<string, string> = Object.freeze({
|
|
|
3
3
|
memorix_search: 'memorix memory search',
|
|
4
4
|
memorix_detail: 'memorix memory detail',
|
|
5
5
|
memorix_graph_context: 'memorix memory graph-context',
|
|
6
|
-
memorix_project_context: 'memorix context',
|
|
6
|
+
memorix_project_context: 'memorix context|resume',
|
|
7
7
|
memorix_context_pack: 'memorix codegraph context-pack',
|
|
8
8
|
memorix_codegraph_status: 'memorix codegraph status',
|
|
9
9
|
memorix_knowledge: 'memorix knowledge init|status|compile|lint|apply|workflow',
|
package/src/cli/command-guide.ts
CHANGED
|
@@ -39,7 +39,10 @@ const GUIDES: Record<string, CliCommandGuide> = {
|
|
|
39
39
|
},
|
|
40
40
|
context: {
|
|
41
41
|
summary: 'Build the bounded task Workset used to resume or begin real work.',
|
|
42
|
-
usage: [
|
|
42
|
+
usage: [
|
|
43
|
+
'memorix context "continue the release fix" [--refresh auto|always|never]',
|
|
44
|
+
'memorix resume "continue the release fix" [--refresh auto|always|never]',
|
|
45
|
+
],
|
|
43
46
|
},
|
|
44
47
|
explain: {
|
|
45
48
|
summary: 'Show why a task context contains its current facts and memory evidence.',
|
|
@@ -776,7 +776,11 @@ function isCurrentGuidance(content: string): boolean {
|
|
|
776
776
|
return (
|
|
777
777
|
content.includes('Memory Autopilot') &&
|
|
778
778
|
content.includes('Default first step for non-trivial coding work') &&
|
|
779
|
-
content.includes('memorix_project_context')
|
|
779
|
+
content.includes('memorix_project_context') &&
|
|
780
|
+
content.includes('memorix resume') &&
|
|
781
|
+
content.includes('Continuation fallback is mandatory') &&
|
|
782
|
+
content.includes('Do not call more Memorix retrieval tools') &&
|
|
783
|
+
content.includes('read-only work')
|
|
780
784
|
);
|
|
781
785
|
}
|
|
782
786
|
|
|
@@ -60,7 +60,7 @@ function formatUsageHint(): string {
|
|
|
60
60
|
' memorix codegraph status --json',
|
|
61
61
|
' memorix codegraph context-pack --task "continue auth bug"',
|
|
62
62
|
'',
|
|
63
|
-
'Tip: use `memorix context
|
|
63
|
+
'Tip: use `memorix context "..."` for new work or `memorix resume "..."` for prior work.',
|
|
64
64
|
].join('\n');
|
|
65
65
|
}
|
|
66
66
|
|
|
@@ -22,11 +22,16 @@ export default defineCommand({
|
|
|
22
22
|
},
|
|
23
23
|
args: {
|
|
24
24
|
task: { type: 'string', description: 'Current task for context shaping' },
|
|
25
|
+
input: { type: 'positional', description: 'Current task for context shaping (ergonomic positional form)' },
|
|
26
|
+
resume: { type: 'boolean', description: 'Always include the bounded prior-work projection' },
|
|
25
27
|
refresh: { type: 'string', description: 'Project scan policy: auto, always, or never' },
|
|
26
28
|
json: { type: 'boolean', description: 'Emit machine-readable JSON output' },
|
|
27
29
|
},
|
|
28
30
|
run: async ({ args }) => {
|
|
29
31
|
const asJson = !!args.json;
|
|
32
|
+
const task = (args.task as string | undefined)?.trim()
|
|
33
|
+
|| (args.input as string | undefined)?.trim()
|
|
34
|
+
|| undefined;
|
|
30
35
|
|
|
31
36
|
try {
|
|
32
37
|
const { project, dataDir, reader } = await getCliProjectContext();
|
|
@@ -34,8 +39,10 @@ export default defineCommand({
|
|
|
34
39
|
project,
|
|
35
40
|
dataDir,
|
|
36
41
|
observations: filterReadableObservations(getAllObservations(), reader),
|
|
37
|
-
task
|
|
42
|
+
task,
|
|
38
43
|
refresh: coerceRefreshMode(args.refresh as string | undefined),
|
|
44
|
+
reader,
|
|
45
|
+
...(args.resume ? { continuation: 'always' as const } : {}),
|
|
39
46
|
});
|
|
40
47
|
|
|
41
48
|
emitResult(
|
|
@@ -49,6 +56,7 @@ export default defineCommand({
|
|
|
49
56
|
providerQuality: context.providerQuality,
|
|
50
57
|
workset: context.workset,
|
|
51
58
|
...(context.task ? { task: context.task } : {}),
|
|
59
|
+
...(context.continuation ? { continuation: context.continuation } : {}),
|
|
52
60
|
},
|
|
53
61
|
formatAutoProjectContextPrompt(context),
|
|
54
62
|
asJson,
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { defineCommand } from 'citty';
|
|
2
|
+
import contextCommand from './context.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A human- and agent-friendly continuation entry point. It deliberately
|
|
6
|
+
* delegates to Project Context so CLI and MCP keep one Workset contract.
|
|
7
|
+
*/
|
|
8
|
+
export default defineCommand({
|
|
9
|
+
meta: {
|
|
10
|
+
name: 'resume',
|
|
11
|
+
description: 'Resume prior project work with one bounded Memory Autopilot brief',
|
|
12
|
+
},
|
|
13
|
+
args: {
|
|
14
|
+
task: { type: 'positional', description: 'Current continuation task', required: false },
|
|
15
|
+
refresh: { type: 'string', description: 'Project scan policy: auto, always, or never' },
|
|
16
|
+
json: { type: 'boolean', description: 'Emit machine-readable JSON output' },
|
|
17
|
+
},
|
|
18
|
+
async run({ args }) {
|
|
19
|
+
await contextCommand.run?.({
|
|
20
|
+
args: {
|
|
21
|
+
_: [],
|
|
22
|
+
input: args.task,
|
|
23
|
+
refresh: args.refresh,
|
|
24
|
+
json: args.json,
|
|
25
|
+
resume: true,
|
|
26
|
+
},
|
|
27
|
+
rawArgs: [],
|
|
28
|
+
cmd: contextCommand,
|
|
29
|
+
} as any);
|
|
30
|
+
},
|
|
31
|
+
});
|
package/src/cli/index.ts
CHANGED
|
@@ -1045,6 +1045,7 @@ const main = defineCommand({
|
|
|
1045
1045
|
integrate: () => import('./commands/integrate.js').then(m => m.default),
|
|
1046
1046
|
memory: () => import('./commands/memory.js').then(m => m.default),
|
|
1047
1047
|
context: () => import('./commands/context.js').then(m => m.default),
|
|
1048
|
+
resume: () => import('./commands/resume.js').then(m => m.default),
|
|
1048
1049
|
explain: () => import('./commands/explain.js').then(m => m.default),
|
|
1049
1050
|
codegraph: () => import('./commands/codegraph.js').then(m => m.default),
|
|
1050
1051
|
knowledge: () => import('./commands/knowledge.js').then(m => m.default),
|
|
@@ -1115,7 +1116,7 @@ const main = defineCommand({
|
|
|
1115
1116
|
// Detect by checking if the first CLI arg matches a registered subcommand name.
|
|
1116
1117
|
const firstArg = process.argv[2];
|
|
1117
1118
|
const knownSubs = ['ask', 'search', 'remember', 'recent', 'help', 'workbench', 'memcode', 'config',
|
|
1118
|
-
'init', 'setup', 'integrate', 'memory', 'context', 'explain', 'codegraph', 'knowledge', 'reasoning', 'retention', 'formation', 'audit', 'transfer', 'skills', 'identity',
|
|
1119
|
+
'init', 'setup', 'integrate', 'memory', 'context', 'resume', 'explain', 'codegraph', 'knowledge', 'reasoning', 'retention', 'formation', 'audit', 'transfer', 'skills', 'identity',
|
|
1119
1120
|
'session', 'team', 'task', 'message', 'lock', 'handoff', 'poll',
|
|
1120
1121
|
'receipt',
|
|
1121
1122
|
'serve', 'serve-http', 'status', 'sync',
|
|
@@ -1155,6 +1156,7 @@ const main = defineCommand({
|
|
|
1155
1156
|
console.error(' session Start/end/context for coding sessions');
|
|
1156
1157
|
console.error(' memory Search/store/detail/timeline/resolve observations');
|
|
1157
1158
|
console.error(' context Show the Memory Autopilot brief for this project');
|
|
1159
|
+
console.error(' resume Resume prior work with one bounded project brief');
|
|
1158
1160
|
console.error(' explain Explain where Memorix project context comes from');
|
|
1159
1161
|
console.error(' codegraph Refresh/status/context-pack for CodeGraph Memory');
|
|
1160
1162
|
console.error(' knowledge Review source-backed knowledge pages and project workflows');
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { existsSync, statSync } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import { truncateToTokenBudget } from '../compact/token-budget.js';
|
|
3
4
|
import { getResolvedConfig } from '../config/resolved-config.js';
|
|
4
5
|
import { buildTaskWorkset, type TaskWorkset, type WorksetCaution } from '../knowledge/workset.js';
|
|
5
6
|
import type { ContextDeliveryTarget } from '../knowledge/context-assembly.js';
|
|
6
|
-
import
|
|
7
|
+
import { sanitizeCredentials } from '../memory/secret-filter.js';
|
|
8
|
+
import type { ObservationReader, ProjectInfo } from '../types.js';
|
|
7
9
|
import { backfillMissingObservationCodeRefs, type CodeRefBackfillResult } from './binder.js';
|
|
8
10
|
import { collectCurrentProjectFacts, formatGitFact, type CurrentProjectFacts } from './current-facts.js';
|
|
9
11
|
import { refreshProjectLite } from './lite-provider.js';
|
|
@@ -21,7 +23,10 @@ import {
|
|
|
21
23
|
} from './project-context.js';
|
|
22
24
|
import { CodeGraphStore } from './store.js';
|
|
23
25
|
import { isEligibleForAutomaticDelivery } from '../memory/admission.js';
|
|
26
|
+
import { getSessionResumeBrief, type SessionResumeBrief } from '../memory/session.js';
|
|
27
|
+
import { initSessionStore } from '../store/session-store.js';
|
|
24
28
|
import {
|
|
29
|
+
isContinuationTask,
|
|
25
30
|
lensPathCandidates,
|
|
26
31
|
lensVerificationHints,
|
|
27
32
|
rankLensPaths,
|
|
@@ -50,6 +55,8 @@ export interface AutoProjectContext {
|
|
|
50
55
|
explain: ProjectContextExplain;
|
|
51
56
|
refresh: AutoContextRefreshResult;
|
|
52
57
|
providerQuality: CodeGraphProviderQuality;
|
|
58
|
+
/** Present only when the caller asked to continue prior work. */
|
|
59
|
+
continuation?: SessionResumeBrief;
|
|
53
60
|
workset: TaskWorkset;
|
|
54
61
|
}
|
|
55
62
|
|
|
@@ -123,6 +130,10 @@ export async function buildAutoProjectContext(input: {
|
|
|
123
130
|
maxFileBytes?: number;
|
|
124
131
|
/** Test-only injection point; production uses the bounded local runner. */
|
|
125
132
|
externalRunner?: ExternalCodeGraphRunner;
|
|
133
|
+
/** Reader used when continuation retrieval loads session and durable memory evidence. */
|
|
134
|
+
reader?: ObservationReader;
|
|
135
|
+
/** Auto detects continuation language; always is used by the explicit resume path. */
|
|
136
|
+
continuation?: 'auto' | 'always' | 'never';
|
|
126
137
|
/**
|
|
127
138
|
* When supplied, a needed refresh is queued instead of running in this
|
|
128
139
|
* request. MCP and hook callers use this to keep their response path fast.
|
|
@@ -135,6 +146,8 @@ export async function buildAutoProjectContext(input: {
|
|
|
135
146
|
const now = input.now ?? new Date();
|
|
136
147
|
const task = input.task?.trim();
|
|
137
148
|
const lens = resolveTaskLens(task);
|
|
149
|
+
const continuationRequested = input.continuation === 'always'
|
|
150
|
+
|| (input.continuation !== 'never' && isContinuationTask(task));
|
|
138
151
|
const codegraphConfig = getResolvedConfig({ projectRoot: input.project.rootPath }).codegraph;
|
|
139
152
|
const exclude = input.exclude ?? codegraphConfig.excludePatterns;
|
|
140
153
|
const maxFileBytes = input.maxFileBytes ?? codegraphConfig.maxFileBytes;
|
|
@@ -278,6 +291,14 @@ export async function buildAutoProjectContext(input: {
|
|
|
278
291
|
if (externalCaution) {
|
|
279
292
|
runtimeCautions.push({ kind: 'external-codegraph-fallback', message: externalCaution });
|
|
280
293
|
}
|
|
294
|
+
// Project Context is also used by lightweight callers that have not touched
|
|
295
|
+
// session APIs yet. Initialize only when continuation was requested so a
|
|
296
|
+
// normal Workset remains independent of session persistence.
|
|
297
|
+
let continuation: SessionResumeBrief | undefined;
|
|
298
|
+
if (continuationRequested) {
|
|
299
|
+
await initSessionStore(input.dataDir);
|
|
300
|
+
continuation = await getSessionResumeBrief(input.project.id, task, input.reader);
|
|
301
|
+
}
|
|
281
302
|
const workset = await buildTaskWorkset({
|
|
282
303
|
projectId: input.project.id,
|
|
283
304
|
dataDir: input.dataDir,
|
|
@@ -287,6 +308,7 @@ export async function buildAutoProjectContext(input: {
|
|
|
287
308
|
...(externalOutline ? { semanticCode: externalOutline } : {}),
|
|
288
309
|
providerQuality,
|
|
289
310
|
currentFacts: worksetFactLines(currentFacts),
|
|
311
|
+
...(continuation ? { continuation } : {}),
|
|
290
312
|
codeState: codeStateLine(overview),
|
|
291
313
|
reliableMemory: sourceSets.reliableSources
|
|
292
314
|
.slice(0, lens.sourceLimit)
|
|
@@ -340,6 +362,7 @@ export async function buildAutoProjectContext(input: {
|
|
|
340
362
|
explain,
|
|
341
363
|
refresh,
|
|
342
364
|
providerQuality,
|
|
365
|
+
...(continuationRequested && workset.continuation ? { continuation: workset.continuation } : {}),
|
|
343
366
|
workset,
|
|
344
367
|
};
|
|
345
368
|
}
|
|
@@ -350,6 +373,13 @@ function formatLanguages(overview: ProjectContextOverview): string {
|
|
|
350
373
|
: 'none indexed yet';
|
|
351
374
|
}
|
|
352
375
|
|
|
376
|
+
function compactContinuationText(text: string, budget: number): string {
|
|
377
|
+
return truncateToTokenBudget(
|
|
378
|
+
sanitizeCredentials(text).replace(/\s+/g, ' ').trim(),
|
|
379
|
+
budget,
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
|
|
353
383
|
function codeStateLine(overview: ProjectContextOverview): string {
|
|
354
384
|
const snapshot = overview.code.latestSnapshot;
|
|
355
385
|
if (!snapshot) return '- Code state: no completed snapshot yet';
|
|
@@ -533,6 +563,29 @@ export function formatAutoProjectContextSummary(context: AutoProjectContext): st
|
|
|
533
563
|
: '- none yet',
|
|
534
564
|
);
|
|
535
565
|
|
|
566
|
+
const continuation = context.workset.continuation;
|
|
567
|
+
if (continuation?.previousSession || continuation?.memories.length) {
|
|
568
|
+
lines.push('', 'Resume from prior work');
|
|
569
|
+
if (continuation.previousSession) {
|
|
570
|
+
const session = continuation.previousSession;
|
|
571
|
+
const source = [session.agent, session.endedAt ? session.endedAt.slice(0, 10) : undefined]
|
|
572
|
+
.filter(Boolean)
|
|
573
|
+
.join(', ');
|
|
574
|
+
lines.push(
|
|
575
|
+
'- Previous session' + (source ? ` (${source})` : '') + ': '
|
|
576
|
+
+ compactContinuationText(session.summary, 44),
|
|
577
|
+
);
|
|
578
|
+
}
|
|
579
|
+
for (const memory of continuation.memories.slice(0, 3)) {
|
|
580
|
+
const detail = memory.detail ? ': ' + compactContinuationText(memory.detail, 20) : '';
|
|
581
|
+
lines.push(
|
|
582
|
+
'- #' + memory.id + ' ' + memory.type + ': '
|
|
583
|
+
+ compactContinuationText(memory.title, 18)
|
|
584
|
+
+ detail,
|
|
585
|
+
);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
536
589
|
return lines.join('\n');
|
|
537
590
|
}
|
|
538
591
|
|
|
@@ -121,6 +121,7 @@ const KEYWORDS: Record<Exclude<TaskLensId, 'general'>, string[]> = {
|
|
|
121
121
|
'fail',
|
|
122
122
|
'failing',
|
|
123
123
|
'fix',
|
|
124
|
+
'fixing',
|
|
124
125
|
'issue',
|
|
125
126
|
'regression',
|
|
126
127
|
'repro',
|
|
@@ -139,6 +140,22 @@ const KEYWORDS: Record<Exclude<TaskLensId, 'general'>, string[]> = {
|
|
|
139
140
|
test: ['coverage', 'fixture', 'smoke', 'spec', 'test', 'tests', 'testing', 'vitest', '测试'],
|
|
140
141
|
};
|
|
141
142
|
|
|
143
|
+
// Continuation is a delivery intent, not a task lens. A request can both resume
|
|
144
|
+
// prior work and be a bugfix, feature, or release task, so callers keep the
|
|
145
|
+
// normal lens and add a bounded prior-work projection separately.
|
|
146
|
+
const CONTINUATION_KEYWORDS = [
|
|
147
|
+
'continue',
|
|
148
|
+
'resume',
|
|
149
|
+
'pick up',
|
|
150
|
+
'carry on',
|
|
151
|
+
'previous session',
|
|
152
|
+
'继续',
|
|
153
|
+
'接手',
|
|
154
|
+
'恢复',
|
|
155
|
+
'延续',
|
|
156
|
+
'上次会话',
|
|
157
|
+
];
|
|
158
|
+
|
|
142
159
|
const LENS_PRIORITY: Exclude<TaskLensId, 'general'>[] = [
|
|
143
160
|
'bugfix',
|
|
144
161
|
'release',
|
|
@@ -229,6 +246,18 @@ export function resolveTaskLens(task?: string): TaskLens {
|
|
|
229
246
|
return best.score > 0 ? LENSES[best.id] : LENSES.general;
|
|
230
247
|
}
|
|
231
248
|
|
|
249
|
+
/**
|
|
250
|
+
* Detect when the caller is asking to continue existing work. This stays
|
|
251
|
+
* separate from task-lens routing so "continue fixing the timeout" remains a
|
|
252
|
+
* bugfix, while still receiving a compact prior-work brief.
|
|
253
|
+
*/
|
|
254
|
+
export function isContinuationTask(task?: string): boolean {
|
|
255
|
+
const normalized = (task ?? '').trim().toLowerCase();
|
|
256
|
+
return normalized.length > 0 && CONTINUATION_KEYWORDS.some((keyword) =>
|
|
257
|
+
containsTaskKeyword(normalized, keyword),
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
|
|
232
261
|
function pathKindScore(path: string, lens: TaskLens): number {
|
|
233
262
|
const normalized = normalizePath(path).toLowerCase();
|
|
234
263
|
const name = normalized.split('/').pop() ?? normalized;
|
|
@@ -56,7 +56,22 @@ export function truncateToTokenBudget(text: string, budget: number): string {
|
|
|
56
56
|
result = result.slice(0, Math.floor(result.length * 0.9));
|
|
57
57
|
}
|
|
58
58
|
if (result.length < text.length) {
|
|
59
|
-
|
|
59
|
+
// A character estimate can end halfway through a flag, path, or symbol.
|
|
60
|
+
// Drop the incomplete whitespace-delimited token instead of returning
|
|
61
|
+
// misleading fragments such as `AUTH...`.
|
|
62
|
+
const nextCharacter = text.charAt(result.length);
|
|
63
|
+
if (nextCharacter && !/[\s.,;:!?)}\]]/.test(nextCharacter)) {
|
|
64
|
+
const boundary = result.search(/\s+\S*$/);
|
|
65
|
+
result = boundary > 0 ? result.slice(0, boundary).trimEnd() : '';
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Keep the suffix inside the stated budget when there is room. An empty
|
|
69
|
+
// prefix is more honest than a partial identifier that appears valid.
|
|
70
|
+
while (result && fitsInBudget(result + '...', budget) === false) {
|
|
71
|
+
const boundary = result.lastIndexOf(' ');
|
|
72
|
+
result = boundary > 0 ? result.slice(0, boundary).trimEnd() : '';
|
|
73
|
+
}
|
|
74
|
+
result = result ? result + '...' : '...';
|
|
60
75
|
}
|
|
61
76
|
}
|
|
62
77
|
|
|
@@ -144,6 +144,9 @@ function parseTomlValue(raw: string, filePath: string, line: number): unknown {
|
|
|
144
144
|
if (raw.startsWith('"') && raw.endsWith('"')) {
|
|
145
145
|
return raw.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, '\\');
|
|
146
146
|
}
|
|
147
|
+
if (raw.startsWith("'") && raw.endsWith("'")) {
|
|
148
|
+
return raw.slice(1, -1);
|
|
149
|
+
}
|
|
147
150
|
if (raw === 'true') return true;
|
|
148
151
|
if (raw === 'false') return false;
|
|
149
152
|
if (/^-?\d+$/.test(raw)) return Number.parseInt(raw, 10);
|
|
@@ -157,7 +160,7 @@ function parseTomlValue(raw: string, filePath: string, line: number): unknown {
|
|
|
157
160
|
}
|
|
158
161
|
|
|
159
162
|
function stripComment(line: string): string {
|
|
160
|
-
let
|
|
163
|
+
let quote: '"' | "'" | null = null;
|
|
161
164
|
let escaped = false;
|
|
162
165
|
for (let i = 0; i < line.length; i++) {
|
|
163
166
|
const char = line[i];
|
|
@@ -165,15 +168,16 @@ function stripComment(line: string): string {
|
|
|
165
168
|
escaped = false;
|
|
166
169
|
continue;
|
|
167
170
|
}
|
|
168
|
-
if (char === '\\' &&
|
|
171
|
+
if (char === '\\' && quote === '"') {
|
|
169
172
|
escaped = true;
|
|
170
173
|
continue;
|
|
171
174
|
}
|
|
172
|
-
if (char === '"') {
|
|
173
|
-
|
|
175
|
+
if (char === '"' || char === "'") {
|
|
176
|
+
if (quote === char) quote = null;
|
|
177
|
+
else if (quote === null) quote = char;
|
|
174
178
|
continue;
|
|
175
179
|
}
|
|
176
|
-
if (char === '#' &&
|
|
180
|
+
if (char === '#' && quote === null) {
|
|
177
181
|
return line.slice(0, i);
|
|
178
182
|
}
|
|
179
183
|
}
|