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,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
+