memorix 1.2.0 → 1.2.2

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.
Files changed (212) hide show
  1. package/CHANGELOG.md +30 -1
  2. package/README.md +18 -4
  3. package/README.zh-CN.md +18 -4
  4. package/TEAM.md +86 -86
  5. package/dist/cli/index.js +15919 -14055
  6. package/dist/cli/index.js.map +1 -1
  7. package/dist/index.js +1997 -1021
  8. package/dist/index.js.map +1 -1
  9. package/dist/maintenance-runner.d.ts +1 -1
  10. package/dist/maintenance-runner.js +8481 -8005
  11. package/dist/maintenance-runner.js.map +1 -1
  12. package/dist/memcode-runtime/CHANGELOG.md +30 -1
  13. package/dist/sdk.d.ts +7 -2
  14. package/dist/sdk.js +2022 -1024
  15. package/dist/sdk.js.map +1 -1
  16. package/dist/types.d.ts +49 -1
  17. package/dist/types.js.map +1 -1
  18. package/docs/1.2.2-MEMORY-CONTROL-PLANE.md +434 -0
  19. package/docs/AGENT_OPERATOR_PLAYBOOK.md +4 -0
  20. package/docs/API_REFERENCE.md +27 -5
  21. package/docs/DESIGN_DECISIONS.md +357 -357
  22. package/docs/DEVELOPMENT.md +4 -0
  23. package/docs/README.md +1 -1
  24. package/docs/SETUP.md +7 -1
  25. package/docs/dev-log/progress.txt +91 -11
  26. package/docs/knowledge/workflows/memorix-release.md +57 -0
  27. package/package.json +1 -1
  28. package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -1
  29. package/src/audit/index.ts +156 -156
  30. package/src/cli/command-guide.ts +192 -0
  31. package/src/cli/commands/audit-list.ts +89 -89
  32. package/src/cli/commands/audit.ts +9 -4
  33. package/src/cli/commands/background.ts +659 -659
  34. package/src/cli/commands/cleanup.ts +5 -1
  35. package/src/cli/commands/codegraph.ts +17 -8
  36. package/src/cli/commands/context.ts +3 -2
  37. package/src/cli/commands/doctor.ts +4 -2
  38. package/src/cli/commands/explain.ts +9 -3
  39. package/src/cli/commands/formation.ts +48 -48
  40. package/src/cli/commands/git-hook-install.ts +111 -111
  41. package/src/cli/commands/handoff.ts +75 -61
  42. package/src/cli/commands/hooks-status.ts +63 -63
  43. package/src/cli/commands/identity.ts +116 -0
  44. package/src/cli/commands/ingest-commit.ts +153 -153
  45. package/src/cli/commands/ingest-image.ts +71 -69
  46. package/src/cli/commands/ingest-log.ts +180 -180
  47. package/src/cli/commands/ingest.ts +44 -44
  48. package/src/cli/commands/integrate-shared.ts +15 -15
  49. package/src/cli/commands/knowledge.ts +40 -0
  50. package/src/cli/commands/lock.ts +93 -92
  51. package/src/cli/commands/memory.ts +58 -21
  52. package/src/cli/commands/message.ts +123 -118
  53. package/src/cli/commands/operator-shared.ts +98 -3
  54. package/src/cli/commands/poll.ts +74 -64
  55. package/src/cli/commands/purge-all-memory.ts +85 -85
  56. package/src/cli/commands/purge-project-memory.ts +83 -83
  57. package/src/cli/commands/reasoning.ts +135 -121
  58. package/src/cli/commands/retention.ts +9 -4
  59. package/src/cli/commands/serve-http.ts +22 -43
  60. package/src/cli/commands/serve-shared.ts +118 -118
  61. package/src/cli/commands/session.ts +29 -3
  62. package/src/cli/commands/setup.ts +9 -3
  63. package/src/cli/commands/skills.ts +124 -119
  64. package/src/cli/commands/status.ts +4 -3
  65. package/src/cli/commands/task.ts +193 -184
  66. package/src/cli/commands/team.ts +14 -10
  67. package/src/cli/commands/transfer.ts +108 -55
  68. package/src/cli/commands/uninstall-project-artifacts.ts +85 -85
  69. package/src/cli/identity.ts +89 -0
  70. package/src/cli/index.ts +96 -19
  71. package/src/cli/invocation.ts +115 -0
  72. package/src/cli/tui/ChatView.tsx +234 -234
  73. package/src/cli/tui/CommandBar.tsx +312 -312
  74. package/src/cli/tui/ContextRail.tsx +118 -118
  75. package/src/cli/tui/HeaderBar.tsx +72 -72
  76. package/src/cli/tui/LogoBanner.tsx +51 -51
  77. package/src/cli/tui/Sidebar.tsx +179 -179
  78. package/src/cli/tui/chat-service.ts +41 -18
  79. package/src/cli/tui/data.ts +23 -44
  80. package/src/cli/tui/index.ts +41 -41
  81. package/src/cli/tui/markdown-render.tsx +371 -371
  82. package/src/cli/tui/operator-context.ts +60 -0
  83. package/src/cli/tui/use-mouse.ts +157 -157
  84. package/src/cli/tui/useNavigation.ts +56 -56
  85. package/src/cli/tui/views/MemoryView.tsx +10 -8
  86. package/src/cli/update-checker.ts +211 -211
  87. package/src/cli/version.ts +7 -7
  88. package/src/cli/workbench.ts +1 -1
  89. package/src/codegraph/auto-context.ts +34 -17
  90. package/src/codegraph/context-pack.ts +1 -0
  91. package/src/codegraph/current-facts.ts +19 -1
  92. package/src/codegraph/project-context.ts +2 -0
  93. package/src/codegraph/task-lens.ts +49 -5
  94. package/src/compact/engine.ts +26 -10
  95. package/src/compact/index-format.ts +25 -2
  96. package/src/compact/token-budget.ts +74 -74
  97. package/src/dashboard/project-classification.ts +64 -64
  98. package/src/dashboard/server.ts +58 -52
  99. package/src/embedding/fastembed-provider.ts +142 -142
  100. package/src/embedding/transformers-provider.ts +111 -111
  101. package/src/git/extractor.ts +209 -209
  102. package/src/git/hooks-path.ts +85 -85
  103. package/src/hooks/admission.ts +117 -0
  104. package/src/hooks/handler.ts +98 -91
  105. package/src/hooks/pattern-detector.ts +173 -173
  106. package/src/hooks/significance-filter.ts +250 -250
  107. package/src/knowledge/claims.ts +51 -1
  108. package/src/knowledge/context-assembly.ts +97 -0
  109. package/src/knowledge/types.ts +1 -0
  110. package/src/knowledge/workflows.ts +34 -3
  111. package/src/knowledge/workset.ts +179 -10
  112. package/src/llm/memory-manager.ts +328 -328
  113. package/src/llm/provider.ts +885 -885
  114. package/src/llm/quality.ts +248 -248
  115. package/src/memory/admission.ts +57 -0
  116. package/src/memory/attribution-guard.ts +249 -249
  117. package/src/memory/auto-relations.ts +21 -0
  118. package/src/memory/consolidation.ts +13 -2
  119. package/src/memory/disclosure-policy.ts +140 -135
  120. package/src/memory/entity-extractor.ts +197 -197
  121. package/src/memory/export-import.ts +11 -3
  122. package/src/memory/formation/evaluate.ts +217 -217
  123. package/src/memory/formation/extract.ts +361 -361
  124. package/src/memory/formation/index.ts +417 -417
  125. package/src/memory/formation/resolve.ts +344 -344
  126. package/src/memory/formation/types.ts +315 -315
  127. package/src/memory/freshness.ts +122 -122
  128. package/src/memory/graph-context.ts +8 -2
  129. package/src/memory/graph-scope.ts +46 -0
  130. package/src/memory/graph.ts +197 -197
  131. package/src/memory/observations.ts +162 -4
  132. package/src/memory/quality-audit.ts +2 -0
  133. package/src/memory/refs.ts +94 -94
  134. package/src/memory/retention.ts +22 -2
  135. package/src/memory/secret-filter.ts +79 -79
  136. package/src/memory/session.ts +5 -2
  137. package/src/memory/visibility.ts +80 -0
  138. package/src/multimodal/image-loader.ts +143 -143
  139. package/src/orchestrate/adapters/claude-stream.ts +192 -192
  140. package/src/orchestrate/adapters/claude.ts +111 -111
  141. package/src/orchestrate/adapters/codex-stream.ts +134 -134
  142. package/src/orchestrate/adapters/codex.ts +41 -41
  143. package/src/orchestrate/adapters/gemini-stream.ts +166 -166
  144. package/src/orchestrate/adapters/gemini.ts +42 -42
  145. package/src/orchestrate/adapters/index.ts +73 -73
  146. package/src/orchestrate/adapters/opencode-stream.ts +143 -143
  147. package/src/orchestrate/adapters/opencode.ts +47 -47
  148. package/src/orchestrate/adapters/spawn-helper.ts +286 -286
  149. package/src/orchestrate/adapters/types.ts +77 -77
  150. package/src/orchestrate/capability-router.ts +284 -284
  151. package/src/orchestrate/context-compact.ts +188 -188
  152. package/src/orchestrate/cost-tracker.ts +219 -219
  153. package/src/orchestrate/error-recovery.ts +191 -191
  154. package/src/orchestrate/evidence.ts +140 -140
  155. package/src/orchestrate/ledger.ts +110 -110
  156. package/src/orchestrate/memorix-bridge.ts +378 -340
  157. package/src/orchestrate/output-budget.ts +80 -80
  158. package/src/orchestrate/permission.ts +152 -152
  159. package/src/orchestrate/pipeline-trace.ts +131 -131
  160. package/src/orchestrate/prompt-builder.ts +155 -155
  161. package/src/orchestrate/ring-buffer.ts +37 -37
  162. package/src/orchestrate/task-graph.ts +389 -389
  163. package/src/orchestrate/verify-gate.ts +33 -10
  164. package/src/orchestrate/worktree.ts +232 -232
  165. package/src/project/aliases.ts +374 -374
  166. package/src/project/detector.ts +268 -268
  167. package/src/rules/adapters/claude-code.ts +99 -99
  168. package/src/rules/adapters/codex.ts +97 -97
  169. package/src/rules/adapters/copilot.ts +124 -124
  170. package/src/rules/adapters/cursor.ts +114 -114
  171. package/src/rules/adapters/kiro.ts +126 -126
  172. package/src/rules/adapters/trae.ts +56 -56
  173. package/src/rules/adapters/windsurf.ts +83 -83
  174. package/src/rules/syncer.ts +235 -235
  175. package/src/runtime/control-plane-maintenance.ts +1 -0
  176. package/src/runtime/isolated-maintenance.ts +1 -0
  177. package/src/runtime/lifecycle.ts +18 -0
  178. package/src/runtime/maintenance-jobs.ts +1 -0
  179. package/src/runtime/maintenance-runner.ts +2 -0
  180. package/src/runtime/project-maintenance.ts +89 -0
  181. package/src/sdk.ts +334 -304
  182. package/src/search/intent-detector.ts +289 -289
  183. package/src/search/query-expansion.ts +52 -52
  184. package/src/server/formation-timeout.ts +27 -27
  185. package/src/server.ts +334 -93
  186. package/src/skills/mini-skills.ts +386 -386
  187. package/src/store/chat-store.ts +119 -119
  188. package/src/store/graph-store.ts +249 -249
  189. package/src/store/mini-skill-store.ts +349 -349
  190. package/src/store/orama-store.ts +61 -6
  191. package/src/store/persistence-json.ts +212 -212
  192. package/src/store/persistence.ts +291 -291
  193. package/src/store/project-affinity.ts +195 -195
  194. package/src/store/sqlite-db.ts +23 -1
  195. package/src/store/sqlite-store.ts +12 -2
  196. package/src/team/event-bus.ts +76 -76
  197. package/src/team/file-locks.ts +173 -173
  198. package/src/team/handoff.ts +168 -161
  199. package/src/team/messages.ts +203 -203
  200. package/src/team/poll.ts +132 -132
  201. package/src/team/tasks.ts +211 -211
  202. package/src/types.ts +51 -0
  203. package/src/wiki/generator.ts +2 -0
  204. package/src/workspace/mcp-adapters/codex.ts +191 -191
  205. package/src/workspace/mcp-adapters/copilot.ts +105 -105
  206. package/src/workspace/mcp-adapters/cursor.ts +53 -53
  207. package/src/workspace/mcp-adapters/kiro.ts +64 -64
  208. package/src/workspace/mcp-adapters/opencode.ts +123 -123
  209. package/src/workspace/mcp-adapters/trae.ts +134 -134
  210. package/src/workspace/mcp-adapters/windsurf.ts +91 -91
  211. package/src/workspace/sanitizer.ts +60 -60
  212. package/src/workspace/workflow-sync.ts +131 -131
@@ -1,6 +1,7 @@
1
1
  import { defineCommand } from 'citty';
2
2
  import { CodeGraphStore } from '../../codegraph/store.js';
3
3
  import { ClaimStore } from '../../knowledge/claim-store.js';
4
+ import { reviewClaim } from '../../knowledge/claims.js';
4
5
  import { applyKnowledgeProposal, compileKnowledgeWorkspace, lintKnowledgeWorkspace } from '../../knowledge/wiki.js';
5
6
  import { initializeKnowledgeWorkspace, loadKnowledgeWorkspace } from '../../knowledge/workspace.js';
6
7
  import { KnowledgeWorkspaceStore } from '../../knowledge/workspace-store.js';
@@ -28,6 +29,8 @@ function usage(): string {
28
29
  ' memorix knowledge init [--mode local]',
29
30
  ' memorix knowledge init --mode versioned --path C:\\project\\docs\\knowledge',
30
31
  ' memorix knowledge status',
32
+ ' memorix knowledge claims',
33
+ ' memorix knowledge review --id <claim-id> --review approved --detail "checked current source evidence"',
31
34
  ' memorix knowledge compile',
32
35
  ' memorix knowledge lint',
33
36
  ' memorix knowledge apply --proposal <id> [--force]',
@@ -57,6 +60,11 @@ function workflowVerdict(value: unknown): 'passed' | 'failed' | 'not-run' | unde
57
60
  throw new Error('workflow verdict must be passed, failed, or not-run');
58
61
  }
59
62
 
63
+ function claimReviewState(value: unknown): 'approved' | 'rejected' {
64
+ if (value === 'approved' || value === 'rejected') return value;
65
+ throw new Error('claim review must be approved or rejected');
66
+ }
67
+
60
68
  function evidenceList(value: unknown): string[] {
61
69
  if (typeof value !== 'string' || !value.trim()) return [];
62
70
  return [...new Set(value.split(',').map(item => item.trim()).filter(Boolean))];
@@ -81,6 +89,8 @@ export default defineCommand({
81
89
  failure: { type: 'string', description: 'Sanitized failure reason for a workflow run' },
82
90
  snapshot: { type: 'string', description: 'Starting code snapshot id for a workflow run' },
83
91
  evidence: { type: 'string', description: 'Comma-separated evidence ids selected for a workflow run' },
92
+ review: { type: 'string', description: 'Claim review verdict: approved or rejected' },
93
+ detail: { type: 'string', description: 'Evidence check performed for a claim review' },
84
94
  json: { type: 'boolean', description: 'Emit machine-readable JSON output' },
85
95
  },
86
96
  run: async ({ args }) => {
@@ -120,6 +130,36 @@ export default defineCommand({
120
130
  const workspaceStore = new KnowledgeWorkspaceStore();
121
131
  await workspaceStore.init(dataDir);
122
132
 
133
+ if (action === 'claims') {
134
+ const claims = claimStore.listClaims(project.id, { limit: 100 });
135
+ emitResult(
136
+ { project, workspace, claims },
137
+ 'Claims: ' + claims.length + ' total, ' + claims.filter(claim => claim.reviewState === 'needs-review').length + ' awaiting review.',
138
+ asJson,
139
+ );
140
+ return;
141
+ }
142
+
143
+ if (action === 'review') {
144
+ const claimId = requiredText(args.id, 'claim id');
145
+ const existing = claimStore.getClaim(claimId);
146
+ if (!existing || existing.projectId !== project.id) {
147
+ emitError('Claim was not found for this knowledge workspace.', asJson);
148
+ return;
149
+ }
150
+ const claim = reviewClaim(claimStore, {
151
+ claimId,
152
+ reviewState: claimReviewState(args.review),
153
+ detail: requiredText(args.detail, 'review detail'),
154
+ });
155
+ emitResult(
156
+ { project, workspace, claim },
157
+ 'Claim ' + claim.id + ' marked ' + claim.reviewState + '.',
158
+ asJson,
159
+ );
160
+ return;
161
+ }
162
+
123
163
  if (action === 'workflow') {
124
164
  const workflowAction = (positional[1] || 'list').toLowerCase();
125
165
  const workflowStore = new WorkflowStore();
@@ -1,96 +1,97 @@
1
- import { defineCommand } from 'citty';
2
- import { emitError, emitResult, getCliProjectContext, shortId } from './operator-shared.js';
3
-
4
- export default defineCommand({
5
- meta: {
6
- name: 'lock',
7
- description: 'Acquire, release, and inspect advisory team file locks',
8
- },
9
- args: {
10
- file: { type: 'string', description: 'File path to lock or inspect' },
11
- agentId: { type: 'string', description: 'Agent ID acquiring or releasing the lock' },
12
- json: { type: 'boolean', description: 'Emit machine-readable JSON output' },
13
- },
14
- run: async ({ args }) => {
15
- const action = (args._ as string[])?.[0] || '';
16
- const asJson = !!args.json;
17
-
18
- try {
19
- const { project, teamStore } = await getCliProjectContext();
20
-
21
- switch (action) {
22
- case 'lock': {
23
- if (!args.file || !args.agentId) {
24
- emitError('file and agentId are required for "memorix lock lock"', asJson);
1
+ import { defineCommand } from 'citty';
2
+ import { emitError, emitResult, getCliProjectContext, resolveCliActorId, shortId } from './operator-shared.js';
3
+
4
+ export default defineCommand({
5
+ meta: {
6
+ name: 'lock',
7
+ description: 'Acquire, release, and inspect advisory team file locks',
8
+ },
9
+ args: {
10
+ file: { type: 'string', description: 'File path to lock or inspect' },
11
+ agentId: { type: 'string', description: 'Agent ID acquiring or releasing the lock' },
12
+ json: { type: 'boolean', description: 'Emit machine-readable JSON output' },
13
+ },
14
+ run: async ({ args }) => {
15
+ const action = (args._ as string[])?.[0] || '';
16
+ const asJson = !!args.json;
17
+
18
+ try {
19
+ const { project, teamStore, identity } = await getCliProjectContext();
20
+ const agentId = resolveCliActorId(args.agentId, identity);
21
+
22
+ switch (action) {
23
+ case 'lock': {
24
+ if (!args.file || !agentId) {
25
+ emitError('file and an active CLI identity or agentId are required for "memorix lock lock"', asJson);
25
26
  return;
26
27
  }
27
- const result = teamStore.acquireLock(project.id, args.file as string, args.agentId as string);
28
- if (!result.success) {
29
- emitError(`File is already locked by ${shortId(result.lockedBy)}`, asJson);
28
+ const result = teamStore.acquireLock(project.id, args.file as string, agentId);
29
+ if (!result.success) {
30
+ emitError(`File is already locked by ${shortId(result.lockedBy)}`, asJson);
31
+ return;
32
+ }
33
+ emitResult(
34
+ { project, file: args.file, lockedBy: agentId },
35
+ `Locked: ${args.file as string}`,
36
+ asJson,
37
+ );
38
+ return;
39
+ }
40
+
41
+ case 'unlock': {
42
+ if (!args.file || !agentId) {
43
+ emitError('file and an active CLI identity or agentId are required for "memorix lock unlock"', asJson);
30
44
  return;
31
45
  }
32
- emitResult(
33
- { project, file: args.file, lockedBy: args.agentId },
34
- `Locked: ${args.file as string}`,
35
- asJson,
36
- );
37
- return;
38
- }
39
-
40
- case 'unlock': {
41
- if (!args.file || !args.agentId) {
42
- emitError('file and agentId are required for "memorix lock unlock"', asJson);
43
- return;
44
- }
45
- const released = teamStore.releaseLock(project.id, args.file as string, args.agentId as string);
46
- if (!released) {
47
- emitError('Cannot unlock: not the owner or the file is not locked', asJson);
48
- return;
49
- }
50
- emitResult(
51
- { project, file: args.file, released: true },
52
- `Unlocked: ${args.file as string}`,
53
- asJson,
54
- );
55
- return;
56
- }
57
-
58
- case 'status': {
59
- if (args.file) {
60
- const lock = teamStore.getLockStatus(project.id, args.file as string);
61
- emitResult(
62
- { project, lock },
63
- lock
64
- ? `Locked by ${teamStore.getAgent(lock.locked_by)?.name ?? shortId(lock.locked_by)}: ${args.file as string}`
65
- : `Unlocked: ${args.file as string}`,
66
- asJson,
67
- );
68
- return;
69
- }
70
-
71
- const locks = teamStore.listLocks(project.id, args.agentId as string | undefined);
72
- emitResult(
73
- { project, locks },
74
- locks.length === 0
75
- ? 'No active file locks.'
76
- : locks
77
- .map((lock) => `- ${lock.file} -> ${teamStore.getAgent(lock.locked_by)?.name ?? shortId(lock.locked_by)}`)
78
- .join('\n'),
79
- asJson,
80
- );
81
- return;
82
- }
83
-
84
- default:
85
- console.log('Memorix Lock Commands');
86
- console.log('');
87
- console.log('Usage:');
88
- console.log(' memorix lock lock --file src/app.ts --agentId <id>');
89
- console.log(' memorix lock unlock --file src/app.ts --agentId <id>');
90
- console.log(' memorix lock status [--file src/app.ts] [--agentId <id>]');
91
- }
92
- } catch (error) {
93
- emitError(error instanceof Error ? error.message : String(error), asJson);
94
- }
95
- },
96
- });
46
+ const released = teamStore.releaseLock(project.id, args.file as string, agentId);
47
+ if (!released) {
48
+ emitError('Cannot unlock: not the owner or the file is not locked', asJson);
49
+ return;
50
+ }
51
+ emitResult(
52
+ { project, file: args.file, released: true },
53
+ `Unlocked: ${args.file as string}`,
54
+ asJson,
55
+ );
56
+ return;
57
+ }
58
+
59
+ case 'status': {
60
+ if (args.file) {
61
+ const lock = teamStore.getLockStatus(project.id, args.file as string);
62
+ emitResult(
63
+ { project, lock },
64
+ lock
65
+ ? `Locked by ${teamStore.getAgent(lock.locked_by)?.name ?? shortId(lock.locked_by)}: ${args.file as string}`
66
+ : `Unlocked: ${args.file as string}`,
67
+ asJson,
68
+ );
69
+ return;
70
+ }
71
+
72
+ const locks = teamStore.listLocks(project.id, agentId);
73
+ emitResult(
74
+ { project, locks },
75
+ locks.length === 0
76
+ ? 'No active file locks.'
77
+ : locks
78
+ .map((lock) => `- ${lock.file} -> ${teamStore.getAgent(lock.locked_by)?.name ?? shortId(lock.locked_by)}`)
79
+ .join('\n'),
80
+ asJson,
81
+ );
82
+ return;
83
+ }
84
+
85
+ default:
86
+ console.log('Memorix Lock Commands');
87
+ console.log('');
88
+ console.log('Usage:');
89
+ console.log(' memorix lock lock --file src/app.ts --agentId <id>');
90
+ console.log(' memorix lock unlock --file src/app.ts --agentId <id>');
91
+ console.log(' memorix lock status [--file src/app.ts] [--agentId <id>]');
92
+ }
93
+ } catch (error) {
94
+ emitError(error instanceof Error ? error.message : String(error), asJson);
95
+ }
96
+ },
97
+ });
@@ -1,9 +1,19 @@
1
1
  import { defineCommand } from 'citty';
2
2
  import { compactDetail, compactSearch, compactTimeline } from '../../compact/engine.js';
3
3
  import { withFreshIndex } from '../../memory/freshness.js';
4
- import { getAllObservations, getProjectObservations, resolveObservations, storeObservation, suggestTopicKey } from '../../memory/observations.js';
4
+ import { getAllObservations, getObservation, getProjectObservations, resolveObservations, storeObservation, suggestTopicKey } from '../../memory/observations.js';
5
5
  import { buildGraphContextPacket, formatGraphContextPrompt } from '../../memory/graph-context.js';
6
- import { emitError, emitResult, getCliProjectContext, parseCsvList, parsePositiveInt, coerceObservationStatus, coerceObservationType } from './operator-shared.js';
6
+ import { canManageObservation, filterReadableObservations, resolveObservationVisibility } from '../../memory/visibility.js';
7
+ import {
8
+ coerceObservationStatus,
9
+ coerceObservationType,
10
+ emitError,
11
+ emitResult,
12
+ getCliProjectContext,
13
+ parseCsvList,
14
+ parsePositiveInt,
15
+ resolveCliWriteScope,
16
+ } from './operator-shared.js';
7
17
 
8
18
  export default defineCommand({
9
19
  meta: {
@@ -19,6 +29,7 @@ export default defineCommand({
19
29
  facts: { type: 'string', description: 'Comma-separated facts' },
20
30
  files: { type: 'string', description: 'Comma-separated file list' },
21
31
  concepts: { type: 'string', description: 'Comma-separated concept list' },
32
+ visibility: { type: 'string', description: 'Memory visibility: project (default), personal, or team' },
22
33
  ids: { type: 'string', description: 'Comma-separated observation IDs' },
23
34
  id: { type: 'string', description: 'Single observation ID' },
24
35
  status: { type: 'string', description: 'Resolved or archived' },
@@ -32,6 +43,7 @@ export default defineCommand({
32
43
  after: { type: 'string', description: 'Timeline depth after anchor' },
33
44
  threshold: { type: 'string', description: 'Similarity threshold for consolidate' },
34
45
  dryRun: { type: 'boolean', description: 'Preview changes without mutating data' },
46
+ 'dry-run': { type: 'boolean', description: 'Kebab-case alias for --dryRun' },
35
47
  trigger: { type: 'string', description: 'Custom trigger text for promoted mini-skills' },
36
48
  instruction: { type: 'string', description: 'Custom instruction for promoted mini-skills' },
37
49
  tags: { type: 'string', description: 'Comma-separated extra tags for promoted mini-skills' },
@@ -44,7 +56,7 @@ export default defineCommand({
44
56
  const asJson = !!args.json;
45
57
 
46
58
  try {
47
- const { project } = await getCliProjectContext({ searchIndex: true });
59
+ const { project, dataDir, reader, identity } = await getCliProjectContext({ searchIndex: true });
48
60
 
49
61
  switch (action) {
50
62
  case 'search': {
@@ -54,7 +66,7 @@ export default defineCommand({
54
66
  return;
55
67
  }
56
68
  const limit = parsePositiveInt(args.limit as string | undefined, 10);
57
- const result = await compactSearch({ query, limit, projectId: project.id });
69
+ const result = await compactSearch({ query, limit, projectId: project.id, reader });
58
70
  emitResult({ project, entries: result.entries }, result.formatted, asJson);
59
71
  return;
60
72
  }
@@ -67,7 +79,7 @@ export default defineCommand({
67
79
  emitError('query is required for "memorix memory graph-context"', asJson);
68
80
  return;
69
81
  }
70
- const observations = getAllObservations();
82
+ const observations = filterReadableObservations(getAllObservations(), reader);
71
83
  const packet = buildGraphContextPacket(observations, {
72
84
  projectId: project.id,
73
85
  query,
@@ -92,7 +104,7 @@ export default defineCommand({
92
104
 
93
105
  case 'recent': {
94
106
  const limit = parsePositiveInt(args.limit as string | undefined, 10);
95
- const observations = getProjectObservations(project.id)
107
+ const observations = filterReadableObservations(getProjectObservations(project.id), reader)
96
108
  .filter((obs) => (obs.status ?? 'active') === 'active')
97
109
  .slice(-limit)
98
110
  .reverse();
@@ -118,6 +130,10 @@ export default defineCommand({
118
130
  (args.topicKey as string | undefined)?.trim() ||
119
131
  suggestTopicKey(type, title) ||
120
132
  undefined;
133
+ const writeScope = resolveCliWriteScope(
134
+ { reader, identity },
135
+ args.visibility as string | undefined,
136
+ );
121
137
  const result = await storeObservation({
122
138
  entityName: (args.entity as string | undefined)?.trim() || 'general',
123
139
  type,
@@ -129,6 +145,7 @@ export default defineCommand({
129
145
  projectId: project.id,
130
146
  topicKey,
131
147
  source: 'manual',
148
+ ...writeScope,
132
149
  });
133
150
  emitResult(
134
151
  { project, observation: result.observation, upserted: result.upserted },
@@ -166,7 +183,7 @@ export default defineCommand({
166
183
  ? `obs:${numericId}@${project.id}`
167
184
  : ref;
168
185
  });
169
- const result = await compactDetail(scopedRefs);
186
+ const result = await compactDetail(scopedRefs, { reader });
170
187
  emitResult({ project, documents: result.documents }, result.formatted, asJson);
171
188
  return;
172
189
  }
@@ -182,6 +199,7 @@ export default defineCommand({
182
199
  project.id,
183
200
  parsePositiveInt(args.before as string | undefined, 3),
184
201
  parsePositiveInt(args.after as string | undefined, 3),
202
+ reader,
185
203
  );
186
204
  emitResult({ project, timeline: result.timeline }, result.formatted, asJson);
187
205
  return;
@@ -196,7 +214,15 @@ export default defineCommand({
196
214
  return;
197
215
  }
198
216
  const status = coerceObservationStatus(args.status as string | undefined);
199
- const result = await resolveObservations(ids, status);
217
+ const authorizedIds = ids.filter((id) => {
218
+ const observation = getObservation(id, project.id);
219
+ return observation ? canManageObservation(observation, reader) : false;
220
+ });
221
+ if (authorizedIds.length === 0) {
222
+ emitError('No requested observations are manageable with the active CLI scope.', asJson);
223
+ return;
224
+ }
225
+ const result = await resolveObservations(authorizedIds, status);
200
226
  emitResult(
201
227
  { project, result, status },
202
228
  `Resolved ${result.resolved.length} observation(s) to ${status}${result.notFound.length > 0 ? `; not found: ${result.notFound.join(', ')}` : ''}`,
@@ -207,7 +233,7 @@ export default defineCommand({
207
233
 
208
234
  case 'deduplicate': {
209
235
  const query = (args.query as string | undefined)?.trim();
210
- const dryRun = !!args.dryRun;
236
+ const dryRun = !!args.dryRun || !!args['dry-run'];
211
237
  const { isLLMEnabled } = await import('../../llm/provider.js');
212
238
  if (!isLLMEnabled()) {
213
239
  emitResult(
@@ -220,7 +246,10 @@ export default defineCommand({
220
246
 
221
247
  const { deduplicateMemory } = await import('../../llm/memory-manager.js');
222
248
  const allObs = await withFreshIndex(() =>
223
- getAllObservations().filter((obs) => (obs.status ?? 'active') === 'active' && obs.projectId === project.id),
249
+ filterReadableObservations(
250
+ getAllObservations().filter((obs) => (obs.status ?? 'active') === 'active' && obs.projectId === project.id),
251
+ reader,
252
+ ),
224
253
  );
225
254
 
226
255
  if (allObs.length < 2) {
@@ -230,7 +259,7 @@ export default defineCommand({
230
259
 
231
260
  let candidates = allObs;
232
261
  if (query) {
233
- const searchResult = await compactSearch({ query, limit: 20, projectId: project.id, status: 'active' });
262
+ const searchResult = await compactSearch({ query, limit: 20, projectId: project.id, status: 'active', reader });
234
263
  const ids = new Set(searchResult.entries.map((entry) => entry.id));
235
264
  candidates = allObs.filter((obs) => ids.has(obs.id));
236
265
  } else {
@@ -277,7 +306,13 @@ export default defineCommand({
277
306
  return;
278
307
  }
279
308
 
280
- const result = await resolveObservations([...new Set(toResolve)], 'resolved');
309
+ const result = await resolveObservations(
310
+ [...new Set(toResolve)].filter((id) => {
311
+ const observation = getObservation(id, project.id);
312
+ return observation ? canManageObservation(observation, reader) : false;
313
+ }),
314
+ 'resolved',
315
+ );
281
316
  emitResult(
282
317
  { project, actions, resolved: result.resolved, notFound: result.notFound, dryRun: false },
283
318
  `Resolved ${result.resolved.length} duplicate observation(s).`,
@@ -292,7 +327,7 @@ export default defineCommand({
292
327
  const { findConsolidationCandidates, executeConsolidation } = await import('../../memory/consolidation.js');
293
328
 
294
329
  if (consolidationAction === 'preview') {
295
- const clusters = await findConsolidationCandidates(process.cwd(), project.id, { threshold });
330
+ const clusters = await findConsolidationCandidates(project.rootPath, project.id, { threshold });
296
331
  emitResult(
297
332
  { project, clusters, action: consolidationAction },
298
333
  clusters.length === 0
@@ -306,7 +341,7 @@ export default defineCommand({
306
341
  }
307
342
 
308
343
  if (consolidationAction === 'execute') {
309
- const result = await executeConsolidation(process.cwd(), project.id, { threshold });
344
+ const result = await executeConsolidation(project.rootPath, project.id, { threshold });
310
345
  emitResult(
311
346
  { project, action: consolidationAction, ...result },
312
347
  result.clustersFound === 0
@@ -325,11 +360,10 @@ export default defineCommand({
325
360
  const promoteAction = (args.action as string | undefined) || 'promote';
326
361
  const { promoteToMiniSkill, loadAllMiniSkills, deleteMiniSkill } = await import('../../skills/mini-skills.js');
327
362
  const { initMiniSkillStore } = await import('../../store/mini-skill-store.js');
328
- const { dataDir } = await getCliProjectContext();
329
363
  await initMiniSkillStore(dataDir);
330
364
 
331
365
  if (promoteAction === 'list') {
332
- const skills = await loadAllMiniSkills(process.cwd());
366
+ const skills = await loadAllMiniSkills(project.rootPath);
333
367
  emitResult(
334
368
  { project, skills, action: promoteAction },
335
369
  skills.length === 0 ? 'No mini-skills found.' : skills.map((skill) => `- #${skill.id} ${skill.title}`).join('\n'),
@@ -344,7 +378,7 @@ export default defineCommand({
344
378
  emitError('skillId is required for "memorix memory promote --action delete"', asJson);
345
379
  return;
346
380
  }
347
- const deleted = await deleteMiniSkill(process.cwd(), skillId);
381
+ const deleted = await deleteMiniSkill(project.rootPath, skillId);
348
382
  if (!deleted) {
349
383
  emitError(`Mini-skill #${skillId} not found`, asJson);
350
384
  return;
@@ -361,12 +395,15 @@ export default defineCommand({
361
395
  return;
362
396
  }
363
397
  const observations = await withFreshIndex(() => getAllObservations());
364
- const matched = observations.filter((obs) => ids.includes(obs.id));
398
+ const matched = filterReadableObservations(
399
+ observations.filter((obs) => obs.projectId === project.id && ids.includes(obs.id)),
400
+ reader,
401
+ ).filter((observation) => resolveObservationVisibility(observation) === 'project');
365
402
  if (matched.length === 0) {
366
- emitError(`No observations found for IDs: ${ids.join(', ')}`, asJson);
403
+ emitError(`No project-visible observations found for IDs: ${ids.join(', ')}. Private and team records cannot be promoted into shared skills.`, asJson);
367
404
  return;
368
405
  }
369
- const skill = await promoteToMiniSkill(process.cwd(), project.id, matched, {
406
+ const skill = await promoteToMiniSkill(project.rootPath, project.id, matched, {
370
407
  trigger: args.trigger as string | undefined,
371
408
  instruction: args.instruction as string | undefined,
372
409
  tags: parseCsvList(args.tags as string | undefined),
@@ -385,7 +422,7 @@ export default defineCommand({
385
422
  console.log('Usage:');
386
423
  console.log(' memorix memory search --query "timeout bug" [--limit 10]');
387
424
  console.log(' memorix memory recent [--limit 10]');
388
- console.log(' memorix memory store --text "..." [--title "..."] [--type discovery]');
425
+ console.log(' memorix memory store --text "..." [--title "..."] [--type discovery] [--visibility project|personal|team]');
389
426
  console.log(' memorix memory suggest-topic-key --type decision --title "..."');
390
427
  console.log(' memorix memory detail --id 42');
391
428
  console.log(' memorix memory detail obs:42@org/project');