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,192 +1,201 @@
1
- import { defineCommand } from 'citty';
2
- import { emitError, emitResult, getCliProjectContext, parseCsvList, parseOptionalJsonObject, shortId } from './operator-shared.js';
3
-
4
- export default defineCommand({
5
- meta: {
6
- name: 'task',
7
- description: 'Create, claim, complete, and inspect team tasks',
8
- },
9
- args: {
10
- description: { type: 'string', description: 'Task description (for create)' },
11
- deps: { type: 'string', description: 'Comma-separated dependency task IDs' },
12
- taskId: { type: 'string', description: 'Task ID for claim, complete, fail, or release' },
13
- agentId: { type: 'string', description: 'Agent ID for claim, complete, fail, release, or agent-aware list' },
14
- result: { type: 'string', description: 'Completion or failure summary' },
15
- status: { type: 'string', description: 'Filter by status for list' },
16
- available: { type: 'boolean', description: 'Show only claimable tasks' },
17
- metadata: { type: 'string', description: 'JSON metadata object for create' },
18
- requiredRole: { type: 'string', description: 'Required role for this task' },
19
- preferredRole: { type: 'string', description: 'Preferred role for this task' },
20
- json: { type: 'boolean', description: 'Emit machine-readable JSON output' },
21
- },
22
- run: async ({ args }) => {
23
- const action = (args._ as string[])?.[0] || '';
24
- const asJson = !!args.json;
25
-
26
- try {
27
- const { project, teamStore } = await getCliProjectContext();
28
-
29
- switch (action) {
30
- case 'create': {
31
- if (!args.description) {
32
- emitError('description is required for "memorix task create"', asJson);
1
+ import { defineCommand } from 'citty';
2
+ import {
3
+ emitError,
4
+ emitResult,
5
+ getCliProjectContext,
6
+ parseCsvList,
7
+ parseOptionalJsonObject,
8
+ resolveCliActorId,
9
+ shortId,
10
+ } from './operator-shared.js';
11
+
12
+ export default defineCommand({
13
+ meta: {
14
+ name: 'task',
15
+ description: 'Create, claim, complete, and inspect team tasks',
16
+ },
17
+ args: {
18
+ description: { type: 'string', description: 'Task description (for create)' },
19
+ deps: { type: 'string', description: 'Comma-separated dependency task IDs' },
20
+ taskId: { type: 'string', description: 'Task ID for claim, complete, fail, or release' },
21
+ agentId: { type: 'string', description: 'Agent ID for claim, complete, fail, release, or agent-aware list' },
22
+ result: { type: 'string', description: 'Completion or failure summary' },
23
+ status: { type: 'string', description: 'Filter by status for list' },
24
+ available: { type: 'boolean', description: 'Show only claimable tasks' },
25
+ metadata: { type: 'string', description: 'JSON metadata object for create' },
26
+ requiredRole: { type: 'string', description: 'Required role for this task' },
27
+ preferredRole: { type: 'string', description: 'Preferred role for this task' },
28
+ json: { type: 'boolean', description: 'Emit machine-readable JSON output' },
29
+ },
30
+ run: async ({ args }) => {
31
+ const action = (args._ as string[])?.[0] || '';
32
+ const asJson = !!args.json;
33
+
34
+ try {
35
+ const { project, teamStore, identity } = await getCliProjectContext();
36
+ const agentId = resolveCliActorId(args.agentId, identity);
37
+
38
+ switch (action) {
39
+ case 'create': {
40
+ if (!args.description) {
41
+ emitError('description is required for "memorix task create"', asJson);
42
+ return;
43
+ }
44
+
45
+ const metadata = parseOptionalJsonObject(args.metadata as string | undefined, 'metadata');
46
+ if (metadata) {
47
+ const { checkPipelineGuards } = await import('../../orchestrate/planner.js');
48
+ const guard = checkPipelineGuards({
49
+ existingTasks: teamStore.listTasks(project.id),
50
+ newTaskMeta: metadata,
51
+ });
52
+ if (!guard.allowed) {
53
+ emitError(guard.reason, asJson);
54
+ return;
55
+ }
56
+ }
57
+
58
+ const task = teamStore.createTask({
59
+ projectId: project.id,
60
+ description: args.description as string,
61
+ deps: parseCsvList(args.deps as string | undefined),
62
+ metadata,
63
+ createdBy: agentId,
64
+ requiredRole: args.requiredRole as string | undefined,
65
+ preferredRole: args.preferredRole as string | undefined,
66
+ });
67
+ const deps = teamStore.getTaskDeps(task.task_id);
68
+ emitResult(
69
+ { project, task, deps },
70
+ `Task created: ${shortId(task.task_id)} "${task.description}"${deps.length > 0 ? ` (${deps.length} dep)` : ''}`,
71
+ asJson,
72
+ );
73
+ return;
74
+ }
75
+
76
+ case 'claim': {
77
+ if (!args.taskId || !agentId) {
78
+ emitError('taskId and an active CLI identity or agentId are required for "memorix task claim"', asJson);
33
79
  return;
34
80
  }
35
-
36
- const metadata = parseOptionalJsonObject(args.metadata as string | undefined, 'metadata');
37
- if (metadata) {
38
- const { checkPipelineGuards } = await import('../../orchestrate/planner.js');
39
- const guard = checkPipelineGuards({
40
- existingTasks: teamStore.listTasks(project.id),
41
- newTaskMeta: metadata,
42
- });
43
- if (!guard.allowed) {
44
- emitError(guard.reason, asJson);
45
- return;
46
- }
47
- }
48
-
49
- const task = teamStore.createTask({
50
- projectId: project.id,
51
- description: args.description as string,
52
- deps: parseCsvList(args.deps as string | undefined),
53
- metadata,
54
- createdBy: args.agentId as string | undefined,
55
- requiredRole: args.requiredRole as string | undefined,
56
- preferredRole: args.preferredRole as string | undefined,
57
- });
58
- const deps = teamStore.getTaskDeps(task.task_id);
59
- emitResult(
60
- { project, task, deps },
61
- `Task created: ${shortId(task.task_id)} "${task.description}"${deps.length > 0 ? ` (${deps.length} dep)` : ''}`,
62
- asJson,
63
- );
64
- return;
65
- }
66
-
67
- case 'claim': {
68
- if (!args.taskId || !args.agentId) {
69
- emitError('taskId and agentId are required for "memorix task claim"', asJson);
70
- return;
71
- }
72
- const result = teamStore.claimTask(args.taskId as string, args.agentId as string);
73
- if (!result.success) {
74
- emitError(result.reason ?? 'Unable to claim task', asJson);
75
- return;
76
- }
77
- emitResult(
78
- { project, result },
79
- `Task claimed: ${result.task?.description ?? args.taskId}${result.hint ? `\nHint: ${result.hint}` : ''}`,
80
- asJson,
81
- );
82
- return;
83
- }
84
-
85
- case 'complete': {
86
- if (!args.taskId || !args.agentId || !args.result) {
87
- emitError('taskId, agentId, and result are required for "memorix task complete"', asJson);
88
- return;
89
- }
90
- const result = teamStore.completeTask(args.taskId as string, args.agentId as string, args.result as string);
91
- if (!result.success) {
92
- emitError(result.reason ?? 'Unable to complete task', asJson);
93
- return;
94
- }
95
- emitResult(
96
- { project, task: teamStore.getTask(args.taskId as string), result: args.result },
97
- `Task completed: ${shortId(args.taskId as string)}`,
98
- asJson,
99
- );
100
- return;
101
- }
102
-
103
- case 'fail': {
104
- if (!args.taskId || !args.agentId || !args.result) {
105
- emitError('taskId, agentId, and result are required for "memorix task fail"', asJson);
106
- return;
107
- }
108
- const result = teamStore.failTask(args.taskId as string, args.agentId as string, args.result as string);
109
- if (!result.success) {
110
- emitError(result.reason ?? 'Unable to fail task', asJson);
81
+ const result = teamStore.claimTask(args.taskId as string, agentId);
82
+ if (!result.success) {
83
+ emitError(result.reason ?? 'Unable to claim task', asJson);
84
+ return;
85
+ }
86
+ emitResult(
87
+ { project, result },
88
+ `Task claimed: ${result.task?.description ?? args.taskId}${result.hint ? `\nHint: ${result.hint}` : ''}`,
89
+ asJson,
90
+ );
91
+ return;
92
+ }
93
+
94
+ case 'complete': {
95
+ if (!args.taskId || !agentId || !args.result) {
96
+ emitError('taskId, result, and an active CLI identity or agentId are required for "memorix task complete"', asJson);
111
97
  return;
112
98
  }
113
- emitResult(
114
- { project, task: teamStore.getTask(args.taskId as string), result: args.result },
115
- `Task failed: ${shortId(args.taskId as string)}`,
116
- asJson,
117
- );
118
- return;
119
- }
120
-
121
- case 'release': {
122
- if (!args.taskId || !args.agentId) {
123
- emitError('taskId and agentId are required for "memorix task release"', asJson);
99
+ const result = teamStore.completeTask(args.taskId as string, agentId, args.result as string);
100
+ if (!result.success) {
101
+ emitError(result.reason ?? 'Unable to complete task', asJson);
102
+ return;
103
+ }
104
+ emitResult(
105
+ { project, task: teamStore.getTask(args.taskId as string), result: args.result },
106
+ `Task completed: ${shortId(args.taskId as string)}`,
107
+ asJson,
108
+ );
109
+ return;
110
+ }
111
+
112
+ case 'fail': {
113
+ if (!args.taskId || !agentId || !args.result) {
114
+ emitError('taskId, result, and an active CLI identity or agentId are required for "memorix task fail"', asJson);
124
115
  return;
125
116
  }
126
- const result = teamStore.releaseTask(args.taskId as string, args.agentId as string);
127
- if (!result.success) {
128
- emitError(result.reason ?? 'Unable to release task', asJson);
117
+ const result = teamStore.failTask(args.taskId as string, agentId, args.result as string);
118
+ if (!result.success) {
119
+ emitError(result.reason ?? 'Unable to fail task', asJson);
120
+ return;
121
+ }
122
+ emitResult(
123
+ { project, task: teamStore.getTask(args.taskId as string), result: args.result },
124
+ `Task failed: ${shortId(args.taskId as string)}`,
125
+ asJson,
126
+ );
127
+ return;
128
+ }
129
+
130
+ case 'release': {
131
+ if (!args.taskId || !agentId) {
132
+ emitError('taskId and an active CLI identity or agentId are required for "memorix task release"', asJson);
129
133
  return;
130
134
  }
131
- emitResult(
132
- { project, task: teamStore.getTask(args.taskId as string) },
133
- `Task released: ${shortId(args.taskId as string)}`,
134
- asJson,
135
- );
136
- return;
137
- }
138
-
139
- case 'list': {
140
- const tasks =
141
- args.available && args.agentId
142
- ? teamStore.listTasksForAgent(project.id, args.agentId as string)
143
- : teamStore.listTasks(
144
- project.id,
145
- args.available
146
- ? { available: true }
147
- : args.status
148
- ? { status: args.status as string }
149
- : undefined,
150
- );
151
- const payload = tasks.map((task) => ({
152
- ...task,
153
- deps: teamStore.getTaskDeps(task.task_id),
154
- }));
155
- emitResult(
156
- { project, tasks: payload },
157
- tasks.length === 0
158
- ? 'No tasks found.'
159
- : payload
160
- .map((task) => {
161
- const assignee = task.assignee_agent_id
162
- ? teamStore.getAgent(task.assignee_agent_id)?.name ?? shortId(task.assignee_agent_id)
163
- : 'unassigned';
164
- const roleTag = task.required_role
165
- ? `[${task.required_role}${task.preferred_role && task.preferred_role !== task.required_role ? ` -> ${task.preferred_role}` : ''}]`
166
- : task.preferred_role
167
- ? `[~${task.preferred_role}]`
168
- : '';
169
- return `- ${task.status}: ${shortId(task.task_id)} "${task.description}" - ${assignee} ${roleTag}`.trim();
170
- })
171
- .join('\n'),
172
- asJson,
173
- );
174
- return;
175
- }
176
-
177
- default:
178
- console.log('Memorix Task Commands');
179
- console.log('');
180
- console.log('Usage:');
181
- console.log(' memorix task create --description "..." [--agentId <id>] [--deps id1,id2]');
182
- console.log(' memorix task claim --taskId <id> --agentId <id>');
183
- console.log(' memorix task complete --taskId <id> --agentId <id> --result "..."');
184
- console.log(' memorix task fail --taskId <id> --agentId <id> --result "..."');
185
- console.log(' memorix task release --taskId <id> --agentId <id>');
186
- console.log(' memorix task list [--status pending|in_progress|completed|failed] [--available --agentId <id>]');
187
- }
188
- } catch (error) {
189
- emitError(error instanceof Error ? error.message : String(error), asJson);
190
- }
191
- },
192
- });
135
+ const result = teamStore.releaseTask(args.taskId as string, agentId);
136
+ if (!result.success) {
137
+ emitError(result.reason ?? 'Unable to release task', asJson);
138
+ return;
139
+ }
140
+ emitResult(
141
+ { project, task: teamStore.getTask(args.taskId as string) },
142
+ `Task released: ${shortId(args.taskId as string)}`,
143
+ asJson,
144
+ );
145
+ return;
146
+ }
147
+
148
+ case 'list': {
149
+ const tasks =
150
+ args.available && agentId
151
+ ? teamStore.listTasksForAgent(project.id, agentId)
152
+ : teamStore.listTasks(
153
+ project.id,
154
+ args.available
155
+ ? { available: true }
156
+ : args.status
157
+ ? { status: args.status as string }
158
+ : undefined,
159
+ );
160
+ const payload = tasks.map((task) => ({
161
+ ...task,
162
+ deps: teamStore.getTaskDeps(task.task_id),
163
+ }));
164
+ emitResult(
165
+ { project, tasks: payload },
166
+ tasks.length === 0
167
+ ? 'No tasks found.'
168
+ : payload
169
+ .map((task) => {
170
+ const assignee = task.assignee_agent_id
171
+ ? teamStore.getAgent(task.assignee_agent_id)?.name ?? shortId(task.assignee_agent_id)
172
+ : 'unassigned';
173
+ const roleTag = task.required_role
174
+ ? `[${task.required_role}${task.preferred_role && task.preferred_role !== task.required_role ? ` -> ${task.preferred_role}` : ''}]`
175
+ : task.preferred_role
176
+ ? `[~${task.preferred_role}]`
177
+ : '';
178
+ return `- ${task.status}: ${shortId(task.task_id)} "${task.description}" - ${assignee} ${roleTag}`.trim();
179
+ })
180
+ .join('\n'),
181
+ asJson,
182
+ );
183
+ return;
184
+ }
185
+
186
+ default:
187
+ console.log('Memorix Task Commands');
188
+ console.log('');
189
+ console.log('Usage:');
190
+ console.log(' memorix task create --description "..." [--agentId <id>] [--deps id1,id2]');
191
+ console.log(' memorix task claim --taskId <id> --agentId <id>');
192
+ console.log(' memorix task complete --taskId <id> --agentId <id> --result "..."');
193
+ console.log(' memorix task fail --taskId <id> --agentId <id> --result "..."');
194
+ console.log(' memorix task release --taskId <id> --agentId <id>');
195
+ console.log(' memorix task list [--status pending|in_progress|completed|failed] [--available --agentId <id>]');
196
+ }
197
+ } catch (error) {
198
+ emitError(error instanceof Error ? error.message : String(error), asJson);
199
+ }
200
+ },
201
+ });
@@ -1,6 +1,7 @@
1
1
  import { defineCommand } from 'citty';
2
2
  import { AGENT_TYPE_ROLE_MAP } from '../../team/team-store.js';
3
- import { emitError, emitResult, getCliProjectContext, parseCsvList, parsePositiveInt, shortId } from './operator-shared.js';
3
+ import { clearCliIdentity } from '../identity.js';
4
+ import { emitError, emitResult, getCliProjectContext, parseCsvList, parsePositiveInt, resolveCliActorId, shortId } from './operator-shared.js';
4
5
 
5
6
  export default defineCommand({
6
7
  meta: {
@@ -27,7 +28,8 @@ export default defineCommand({
27
28
  const asJson = !!args.json;
28
29
 
29
30
  try {
30
- const { project, teamStore } = await getCliProjectContext();
31
+ const { project, dataDir, teamStore, identity } = await getCliProjectContext();
32
+ const selectedAgentId = resolveCliActorId(args.agentId, identity);
31
33
 
32
34
  switch (action) {
33
35
  case 'join': {
@@ -53,20 +55,22 @@ export default defineCommand({
53
55
  }
54
56
 
55
57
  case 'leave': {
56
- const agentId = args.agentId as string | undefined;
57
- if (!agentId) {
58
- emitError('agentId is required for "memorix team leave"', asJson);
58
+ if (!selectedAgentId) {
59
+ emitError('an active CLI identity or agentId is required for "memorix team leave"', asJson);
59
60
  return;
60
61
  }
61
- const left = teamStore.leaveAgent(agentId);
62
- const releasedLocks = teamStore.releaseAllLocks(agentId);
63
- const releasedTasks = teamStore.releaseTasksByAgent(agentId);
62
+ const left = teamStore.leaveAgent(selectedAgentId);
63
+ const releasedLocks = teamStore.releaseAllLocks(selectedAgentId);
64
+ const releasedTasks = teamStore.releaseTasksByAgent(selectedAgentId);
64
65
  if (!left) {
65
- emitError(`Agent "${agentId}" not found`, asJson);
66
+ emitError(`Agent "${selectedAgentId}" not found`, asJson);
66
67
  return;
67
68
  }
69
+ if (identity?.agentId === selectedAgentId) {
70
+ await clearCliIdentity(dataDir);
71
+ }
68
72
  emitResult(
69
- { project, agentId, releasedLocks, releasedTasks },
73
+ { project, agentId: selectedAgentId, releasedLocks, releasedTasks },
70
74
  `Left team: released ${releasedLocks} lock(s), ${releasedTasks} task(s)`,
71
75
  asJson,
72
76
  );
@@ -1,73 +1,126 @@
1
1
  import { defineCommand } from 'citty';
2
+ import { promises as fs } from 'node:fs';
3
+ import path from 'node:path';
2
4
  import { exportAsJson, exportAsMarkdown, importFromJson } from '../../memory/export-import.js';
3
5
  import { emitError, emitResult, getCliProjectContext } from './operator-shared.js';
4
6
 
5
- export default defineCommand({
6
- meta: {
7
- name: 'transfer',
8
- description: 'Export or import project memory snapshots',
9
- },
10
- args: {
11
- format: { type: 'string', description: 'Export format: json or markdown' },
12
- data: { type: 'string', description: 'JSON payload from a previous export' },
13
- json: { type: 'boolean', description: 'Emit machine-readable JSON output' },
14
- },
15
- run: async ({ args }) => {
16
- const action = (args._ as string[])?.[0] || '';
17
- const asJson = !!args.json;
7
+ async function readStandardInput(): Promise<string> {
8
+ return new Promise((resolve, reject) => {
9
+ let content = '';
10
+ process.stdin.setEncoding('utf8');
11
+ process.stdin.on('data', (chunk) => {
12
+ content += chunk;
13
+ });
14
+ process.stdin.on('end', () => resolve(content));
15
+ process.stdin.on('error', reject);
16
+ });
17
+ }
18
18
 
19
- try {
20
- const { project, dataDir } = await getCliProjectContext();
19
+ async function readImportPayload(args: Record<string, unknown>, projectRoot: string): Promise<string> {
20
+ const data = typeof args.data === 'string' ? args.data.trim() : '';
21
+ const file = typeof args.file === 'string' ? args.file.trim() : '';
22
+ const readFromStdin = args.stdin === true || data === '-';
23
+ const sources = Number(Boolean(data && data !== '-')) + Number(Boolean(file)) + Number(readFromStdin);
24
+ if (sources !== 1) {
25
+ throw new Error('Choose exactly one import source: --data <json>, --file <path>, or --stdin.');
26
+ }
27
+ if (readFromStdin) return readStandardInput();
28
+ if (file) return fs.readFile(path.resolve(projectRoot, file), 'utf8');
29
+ return data;
30
+ }
21
31
 
22
- switch (action) {
32
+ async function writeExportPayload(projectRoot: string, output: string, payload: string): Promise<string> {
33
+ const outputPath = path.resolve(projectRoot, output);
34
+ await fs.mkdir(path.dirname(outputPath), { recursive: true });
35
+ await fs.writeFile(outputPath, payload, 'utf8');
36
+ return outputPath;
37
+ }
38
+
39
+ export default defineCommand({
40
+ meta: {
41
+ name: 'transfer',
42
+ description: 'Export or import project memory snapshots',
43
+ },
44
+ args: {
45
+ format: { type: 'string', description: 'Export format: json or markdown' },
46
+ data: { type: 'string', description: 'JSON payload from a previous export' },
47
+ file: { type: 'string', description: 'File containing a JSON payload to import' },
48
+ stdin: { type: 'boolean', description: 'Read a JSON import payload from standard input' },
49
+ out: { type: 'string', description: 'Write the export payload to this file instead of stdout' },
50
+ json: { type: 'boolean', description: 'Emit machine-readable JSON output' },
51
+ },
52
+ run: async ({ args }) => {
53
+ const action = (args._ as string[])?.[0] || '';
54
+ const asJson = !!args.json;
55
+
56
+ try {
57
+ const { project, dataDir, reader } = await getCliProjectContext();
58
+
59
+ switch (action) {
23
60
  case 'export': {
24
61
  const format = (args.format as string | undefined) === 'markdown' ? 'markdown' : 'json';
25
62
  if (format === 'markdown') {
26
- const markdown = await exportAsMarkdown(dataDir, project.id);
63
+ const markdown = await exportAsMarkdown(dataDir, project.id, reader);
64
+ const output = (args.out as string | undefined)?.trim();
65
+ if (output) {
66
+ const outputPath = await writeExportPayload(project.rootPath, output, markdown);
67
+ emitResult({ project, format, outputPath }, `Export written: ${outputPath}`, asJson);
68
+ return;
69
+ }
27
70
  emitResult({ project, format, markdown }, markdown, asJson);
28
71
  return;
29
72
  }
30
- const exported = await exportAsJson(dataDir, project.id);
31
- emitResult(
32
- { project, format, export: exported },
33
- JSON.stringify(exported, null, 2),
34
- asJson,
35
- );
36
- return;
37
- }
38
-
39
- case 'import': {
40
- const raw = (args.data as string | undefined)?.trim();
41
- if (!raw) {
42
- emitError('data is required for "memorix transfer import"', asJson);
43
- return;
44
- }
45
- let parsed: Parameters<typeof importFromJson>[1];
46
- try {
47
- parsed = JSON.parse(raw);
48
- } catch (error) {
49
- emitError(`Invalid JSON import payload: ${error instanceof Error ? error.message : String(error)}`, asJson);
73
+ const exported = await exportAsJson(dataDir, project.id, reader);
74
+ const serialized = JSON.stringify(exported, null, 2);
75
+ const output = (args.out as string | undefined)?.trim();
76
+ if (output) {
77
+ const outputPath = await writeExportPayload(project.rootPath, output, `${serialized}\n`);
78
+ emitResult(
79
+ { project, format, outputPath, stats: exported.stats },
80
+ `Export written: ${outputPath}`,
81
+ asJson,
82
+ );
50
83
  return;
51
84
  }
52
- const result = await importFromJson(dataDir, parsed);
53
85
  emitResult(
54
- { project, result },
55
- `Import complete: ${result.observationsImported} observation(s), ${result.sessionsImported} session(s), ${result.skipped} skipped.`,
86
+ { project, format, export: exported },
87
+ serialized,
56
88
  asJson,
57
89
  );
58
- return;
59
- }
60
-
61
- default:
62
- console.log('Memorix Transfer Commands');
63
- console.log('');
64
- console.log('Usage:');
65
- console.log(' memorix transfer export [--format json|markdown]');
90
+ return;
91
+ }
92
+
93
+ case 'import': {
94
+ const raw = (await readImportPayload(args as Record<string, unknown>, project.rootPath)).trim();
95
+ if (!raw) throw new Error('Import payload is empty.');
96
+ let parsed: Parameters<typeof importFromJson>[1];
97
+ try {
98
+ parsed = JSON.parse(raw);
99
+ } catch (error) {
100
+ emitError(`Invalid JSON import payload: ${error instanceof Error ? error.message : String(error)}`, asJson);
101
+ return;
102
+ }
103
+ const result = await importFromJson(dataDir, parsed);
104
+ emitResult(
105
+ { project, result },
106
+ `Import complete: ${result.observationsImported} observation(s), ${result.sessionsImported} session(s), ${result.skipped} skipped.`,
107
+ asJson,
108
+ );
109
+ return;
110
+ }
111
+
112
+ default:
113
+ console.log('Memorix Transfer Commands');
114
+ console.log('');
115
+ console.log('Usage:');
116
+ console.log(' memorix transfer export [--format json|markdown] [--out ./.memorix-export.json]');
117
+ console.log(' memorix transfer import --file ./.memorix-export.json');
118
+ console.log(' memorix transfer import --stdin');
66
119
  console.log(' memorix transfer import --data "<json>"');
67
- }
68
- } catch (error) {
69
- emitError(error instanceof Error ? error.message : String(error), asJson);
70
- }
71
- },
72
- });
73
-
120
+ }
121
+ } catch (error) {
122
+ emitError(error instanceof Error ? error.message : String(error), asJson);
123
+ }
124
+ },
125
+ });
126
+