memorix 1.2.1 → 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 (199) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +14 -2
  3. package/README.zh-CN.md +14 -2
  4. package/TEAM.md +86 -86
  5. package/dist/cli/index.js +15407 -13779
  6. package/dist/cli/index.js.map +1 -1
  7. package/dist/index.js +1321 -529
  8. package/dist/index.js.map +1 -1
  9. package/dist/maintenance-runner.d.ts +1 -1
  10. package/dist/maintenance-runner.js +8458 -8087
  11. package/dist/maintenance-runner.js.map +1 -1
  12. package/dist/memcode-runtime/CHANGELOG.md +16 -0
  13. package/dist/sdk.d.ts +7 -2
  14. package/dist/sdk.js +1349 -535
  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 +24 -4
  21. package/docs/DESIGN_DECISIONS.md +357 -357
  22. package/docs/README.md +1 -1
  23. package/docs/dev-log/progress.txt +91 -11
  24. package/package.json +1 -1
  25. package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -1
  26. package/src/audit/index.ts +156 -156
  27. package/src/cli/command-guide.ts +192 -0
  28. package/src/cli/commands/audit-list.ts +89 -89
  29. package/src/cli/commands/audit.ts +9 -4
  30. package/src/cli/commands/background.ts +659 -659
  31. package/src/cli/commands/cleanup.ts +5 -1
  32. package/src/cli/commands/codegraph.ts +15 -5
  33. package/src/cli/commands/context.ts +3 -2
  34. package/src/cli/commands/doctor.ts +4 -2
  35. package/src/cli/commands/explain.ts +9 -3
  36. package/src/cli/commands/formation.ts +48 -48
  37. package/src/cli/commands/git-hook-install.ts +111 -111
  38. package/src/cli/commands/handoff.ts +75 -61
  39. package/src/cli/commands/hooks-status.ts +63 -63
  40. package/src/cli/commands/identity.ts +116 -0
  41. package/src/cli/commands/ingest-commit.ts +153 -153
  42. package/src/cli/commands/ingest-image.ts +71 -69
  43. package/src/cli/commands/ingest-log.ts +180 -180
  44. package/src/cli/commands/ingest.ts +44 -44
  45. package/src/cli/commands/integrate-shared.ts +15 -15
  46. package/src/cli/commands/lock.ts +93 -92
  47. package/src/cli/commands/memory.ts +58 -21
  48. package/src/cli/commands/message.ts +123 -118
  49. package/src/cli/commands/operator-shared.ts +98 -3
  50. package/src/cli/commands/poll.ts +74 -64
  51. package/src/cli/commands/purge-all-memory.ts +85 -85
  52. package/src/cli/commands/purge-project-memory.ts +83 -83
  53. package/src/cli/commands/reasoning.ts +135 -121
  54. package/src/cli/commands/retention.ts +9 -4
  55. package/src/cli/commands/serve-http.ts +8 -2
  56. package/src/cli/commands/serve-shared.ts +118 -118
  57. package/src/cli/commands/session.ts +29 -3
  58. package/src/cli/commands/skills.ts +124 -119
  59. package/src/cli/commands/status.ts +4 -3
  60. package/src/cli/commands/task.ts +193 -184
  61. package/src/cli/commands/team.ts +14 -10
  62. package/src/cli/commands/transfer.ts +108 -55
  63. package/src/cli/commands/uninstall-project-artifacts.ts +85 -85
  64. package/src/cli/identity.ts +89 -0
  65. package/src/cli/index.ts +96 -19
  66. package/src/cli/invocation.ts +115 -0
  67. package/src/cli/tui/ChatView.tsx +234 -234
  68. package/src/cli/tui/CommandBar.tsx +312 -312
  69. package/src/cli/tui/ContextRail.tsx +118 -118
  70. package/src/cli/tui/HeaderBar.tsx +72 -72
  71. package/src/cli/tui/LogoBanner.tsx +51 -51
  72. package/src/cli/tui/Sidebar.tsx +179 -179
  73. package/src/cli/tui/chat-service.ts +41 -18
  74. package/src/cli/tui/data.ts +23 -44
  75. package/src/cli/tui/index.ts +41 -41
  76. package/src/cli/tui/markdown-render.tsx +371 -371
  77. package/src/cli/tui/operator-context.ts +60 -0
  78. package/src/cli/tui/use-mouse.ts +157 -157
  79. package/src/cli/tui/useNavigation.ts +56 -56
  80. package/src/cli/tui/views/MemoryView.tsx +10 -8
  81. package/src/cli/update-checker.ts +211 -211
  82. package/src/cli/version.ts +7 -7
  83. package/src/cli/workbench.ts +1 -1
  84. package/src/codegraph/auto-context.ts +31 -2
  85. package/src/codegraph/context-pack.ts +1 -0
  86. package/src/codegraph/project-context.ts +2 -0
  87. package/src/compact/engine.ts +26 -10
  88. package/src/compact/index-format.ts +25 -2
  89. package/src/compact/token-budget.ts +74 -74
  90. package/src/dashboard/project-classification.ts +64 -64
  91. package/src/dashboard/server.ts +46 -9
  92. package/src/embedding/fastembed-provider.ts +142 -142
  93. package/src/embedding/transformers-provider.ts +111 -111
  94. package/src/git/extractor.ts +209 -209
  95. package/src/git/hooks-path.ts +85 -85
  96. package/src/hooks/admission.ts +117 -0
  97. package/src/hooks/handler.ts +98 -91
  98. package/src/hooks/pattern-detector.ts +173 -173
  99. package/src/hooks/significance-filter.ts +250 -250
  100. package/src/knowledge/context-assembly.ts +97 -0
  101. package/src/knowledge/workset.ts +179 -10
  102. package/src/llm/memory-manager.ts +328 -328
  103. package/src/llm/provider.ts +885 -885
  104. package/src/llm/quality.ts +248 -248
  105. package/src/memory/admission.ts +57 -0
  106. package/src/memory/attribution-guard.ts +249 -249
  107. package/src/memory/consolidation.ts +13 -2
  108. package/src/memory/disclosure-policy.ts +140 -135
  109. package/src/memory/entity-extractor.ts +197 -197
  110. package/src/memory/export-import.ts +11 -3
  111. package/src/memory/formation/evaluate.ts +217 -217
  112. package/src/memory/formation/extract.ts +361 -361
  113. package/src/memory/formation/index.ts +417 -417
  114. package/src/memory/formation/resolve.ts +344 -344
  115. package/src/memory/formation/types.ts +315 -315
  116. package/src/memory/freshness.ts +122 -122
  117. package/src/memory/graph-context.ts +8 -2
  118. package/src/memory/graph.ts +197 -197
  119. package/src/memory/observations.ts +162 -4
  120. package/src/memory/quality-audit.ts +2 -0
  121. package/src/memory/refs.ts +94 -94
  122. package/src/memory/retention.ts +22 -2
  123. package/src/memory/secret-filter.ts +79 -79
  124. package/src/memory/session.ts +5 -2
  125. package/src/memory/visibility.ts +80 -0
  126. package/src/multimodal/image-loader.ts +143 -143
  127. package/src/orchestrate/adapters/claude-stream.ts +192 -192
  128. package/src/orchestrate/adapters/claude.ts +111 -111
  129. package/src/orchestrate/adapters/codex-stream.ts +134 -134
  130. package/src/orchestrate/adapters/codex.ts +41 -41
  131. package/src/orchestrate/adapters/gemini-stream.ts +166 -166
  132. package/src/orchestrate/adapters/gemini.ts +42 -42
  133. package/src/orchestrate/adapters/index.ts +73 -73
  134. package/src/orchestrate/adapters/opencode-stream.ts +143 -143
  135. package/src/orchestrate/adapters/opencode.ts +47 -47
  136. package/src/orchestrate/adapters/spawn-helper.ts +286 -286
  137. package/src/orchestrate/adapters/types.ts +77 -77
  138. package/src/orchestrate/capability-router.ts +284 -284
  139. package/src/orchestrate/context-compact.ts +188 -188
  140. package/src/orchestrate/cost-tracker.ts +219 -219
  141. package/src/orchestrate/error-recovery.ts +191 -191
  142. package/src/orchestrate/evidence.ts +140 -140
  143. package/src/orchestrate/ledger.ts +110 -110
  144. package/src/orchestrate/memorix-bridge.ts +378 -340
  145. package/src/orchestrate/output-budget.ts +80 -80
  146. package/src/orchestrate/permission.ts +152 -152
  147. package/src/orchestrate/pipeline-trace.ts +131 -131
  148. package/src/orchestrate/prompt-builder.ts +155 -155
  149. package/src/orchestrate/ring-buffer.ts +37 -37
  150. package/src/orchestrate/task-graph.ts +389 -389
  151. package/src/orchestrate/worktree.ts +232 -232
  152. package/src/project/aliases.ts +374 -374
  153. package/src/project/detector.ts +268 -268
  154. package/src/rules/adapters/claude-code.ts +99 -99
  155. package/src/rules/adapters/codex.ts +97 -97
  156. package/src/rules/adapters/copilot.ts +124 -124
  157. package/src/rules/adapters/cursor.ts +114 -114
  158. package/src/rules/adapters/kiro.ts +126 -126
  159. package/src/rules/adapters/trae.ts +56 -56
  160. package/src/rules/adapters/windsurf.ts +83 -83
  161. package/src/rules/syncer.ts +235 -235
  162. package/src/runtime/control-plane-maintenance.ts +1 -0
  163. package/src/runtime/isolated-maintenance.ts +1 -0
  164. package/src/runtime/lifecycle.ts +18 -0
  165. package/src/runtime/maintenance-jobs.ts +1 -0
  166. package/src/runtime/maintenance-runner.ts +2 -0
  167. package/src/runtime/project-maintenance.ts +89 -0
  168. package/src/sdk.ts +334 -304
  169. package/src/search/intent-detector.ts +289 -289
  170. package/src/search/query-expansion.ts +52 -52
  171. package/src/server/formation-timeout.ts +27 -27
  172. package/src/server.ts +260 -81
  173. package/src/skills/mini-skills.ts +386 -386
  174. package/src/store/chat-store.ts +119 -119
  175. package/src/store/graph-store.ts +249 -249
  176. package/src/store/mini-skill-store.ts +349 -349
  177. package/src/store/orama-store.ts +61 -6
  178. package/src/store/persistence-json.ts +212 -212
  179. package/src/store/persistence.ts +291 -291
  180. package/src/store/project-affinity.ts +195 -195
  181. package/src/store/sqlite-db.ts +23 -1
  182. package/src/store/sqlite-store.ts +12 -2
  183. package/src/team/event-bus.ts +76 -76
  184. package/src/team/file-locks.ts +173 -173
  185. package/src/team/handoff.ts +168 -161
  186. package/src/team/messages.ts +203 -203
  187. package/src/team/poll.ts +132 -132
  188. package/src/team/tasks.ts +211 -211
  189. package/src/types.ts +51 -0
  190. package/src/wiki/generator.ts +2 -0
  191. package/src/workspace/mcp-adapters/codex.ts +191 -191
  192. package/src/workspace/mcp-adapters/copilot.ts +105 -105
  193. package/src/workspace/mcp-adapters/cursor.ts +53 -53
  194. package/src/workspace/mcp-adapters/kiro.ts +64 -64
  195. package/src/workspace/mcp-adapters/opencode.ts +123 -123
  196. package/src/workspace/mcp-adapters/trae.ts +134 -134
  197. package/src/workspace/mcp-adapters/windsurf.ts +91 -91
  198. package/src/workspace/sanitizer.ts +60 -60
  199. package/src/workspace/workflow-sync.ts +131 -131
@@ -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');