@aopslabs/aops 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +19 -0
- package/NOTICE +7 -0
- package/README.md +203 -0
- package/assets/agent-assets/core/SKILL.md +49 -0
- package/assets/agent-assets/core/references/agentspace/SKILL.md +78 -0
- package/assets/agent-assets/core/references/aops-cli-core/SKILL.md +108 -0
- package/assets/agent-assets/core/references/chatv3/SKILL.md +48 -0
- package/assets/agent-assets/core/references/collaborative-work/SKILL.md +85 -0
- package/assets/agent-assets/core/references/discuss/SKILL.md +66 -0
- package/assets/agent-assets/core/references/docman/SKILL.md +69 -0
- package/assets/agent-assets/core/references/hosted-chat/SKILL.md +64 -0
- package/assets/agent-assets/core/references/mission/SKILL.md +54 -0
- package/assets/agent-assets/core/references/projectman/SKILL.md +86 -0
- package/assets/agent-assets/core/references/sys/SKILL.md +36 -0
- package/assets/agent-assets/core/references/view/SKILL.md +58 -0
- package/assets/agent-assets/core/references/working-disciplines/SKILL.md +69 -0
- package/assets/agent-assets/core/user-guides/agent-assets.md +81 -0
- package/assets/agent-assets/core/user-guides/agentspace.md +1098 -0
- package/assets/agent-assets/core/user-guides/aops-cli.md +1107 -0
- package/assets/agent-assets/core/user-guides/aops-system.md +63 -0
- package/assets/agent-assets/core/user-guides/chatv3.md +113 -0
- package/assets/agent-assets/core/user-guides/docman.md +162 -0
- package/assets/agent-assets/core/user-guides/projectman.md +212 -0
- package/assets/agent-assets/core/user-guides/sys.md +94 -0
- package/assets/agent-assets/core/user-guides/working-disciplines.md +588 -0
- package/assets/agent-assets/gateway/aops/SKILL.md +14 -0
- package/assets/r6-remote-session-baseline.json +19 -0
- package/assets/skills/aops-install/SKILL.md +231 -0
- package/assets/skills/aops-install/agents/openai.yaml +4 -0
- package/dist/commands/activity.js +352 -0
- package/dist/commands/agent-profile.js +653 -0
- package/dist/commands/agent.js +710 -0
- package/dist/commands/agents-md.js +125 -0
- package/dist/commands/api.js +84 -0
- package/dist/commands/archive.js +873 -0
- package/dist/commands/artifact.js +535 -0
- package/dist/commands/assets.js +1414 -0
- package/dist/commands/auth/device-login.js +1 -0
- package/dist/commands/auth/legacy-export.js +177 -0
- package/dist/commands/auth/login.js +426 -0
- package/dist/commands/chat.js +1363 -0
- package/dist/commands/chatv3.js +1684 -0
- package/dist/commands/checkpoint.js +549 -0
- package/dist/commands/commercial-artifact.js +475 -0
- package/dist/commands/commercial-license.js +594 -0
- package/dist/commands/community-auth.js +62 -0
- package/dist/commands/community-cockpit.js +185 -0
- package/dist/commands/community-console.js +109 -0
- package/dist/commands/community-doctor.js +335 -0
- package/dist/commands/community-server.js +2062 -0
- package/dist/commands/community-setup.js +284 -0
- package/dist/commands/discuss.js +1448 -0
- package/dist/commands/doc.js +5219 -0
- package/dist/commands/experience.js +922 -0
- package/dist/commands/host.js +1127 -0
- package/dist/commands/init.js +118 -0
- package/dist/commands/memory.js +3129 -0
- package/dist/commands/mission.js +562 -0
- package/dist/commands/official-catalog.js +134 -0
- package/dist/commands/plan.js +84 -0
- package/dist/commands/playbook.js +415 -0
- package/dist/commands/pm/context.js +6 -0
- package/dist/commands/pm/index.js +701 -0
- package/dist/commands/pm/projectman.js +6266 -0
- package/dist/commands/project.js +1080 -0
- package/dist/commands/prompt.js +1024 -0
- package/dist/commands/repo-sync.js +1907 -0
- package/dist/commands/resource.js +527 -0
- package/dist/commands/skill.js +1211 -0
- package/dist/commands/start-disciplines.js +510 -0
- package/dist/commands/start.js +1343 -0
- package/dist/commands/target.js +309 -0
- package/dist/commands/version.js +42 -0
- package/dist/commands/view.js +1852 -0
- package/dist/launchers/cockpit.js +28 -0
- package/dist/launchers/options.js +74 -0
- package/dist/launchers/server.js +41 -0
- package/dist/lib/agent-assets/envelope.js +83 -0
- package/dist/lib/agent-assets/gateway.js +15 -0
- package/dist/lib/agent-assets/guards.js +23 -0
- package/dist/lib/agent-assets/hosted-discovery.js +148 -0
- package/dist/lib/agent-assets/hosted-package-input.js +154 -0
- package/dist/lib/agent-assets/legacy-pointer-migration.js +677 -0
- package/dist/lib/agent-assets/native-fs.js +589 -0
- package/dist/lib/agent-assets/package-manifest.js +324 -0
- package/dist/lib/agent-assets/portable-path.js +67 -0
- package/dist/lib/agent-assets/release-input.js +177 -0
- package/dist/lib/agent-assets/roots.js +45 -0
- package/dist/lib/agent-assets/runtime-binding-reader.js +545 -0
- package/dist/lib/agent-assets/runtime-targets.js +50 -0
- package/dist/lib/agent-assets/store-reader.js +1212 -0
- package/dist/lib/agent-assets/store-types.js +1 -0
- package/dist/lib/agent-assets/store-writer.js +1484 -0
- package/dist/lib/agent-assets/types.js +1 -0
- package/dist/lib/agent-assets/unicode-case-folding-v15-1.generated.js +1548 -0
- package/dist/lib/commercial-final-readiness.js +504 -0
- package/dist/lib/commercial-lifecycle-smoke-isolation.js +236 -0
- package/dist/lib/commercial-managed-postgres-docker-adapter.js +274 -0
- package/dist/lib/commercial-setup-readiness-adapter.js +179 -0
- package/dist/lib/community-client-contract.js +164 -0
- package/dist/lib/community-cockpit-lifecycle.js +601 -0
- package/dist/lib/community-command-abort.js +59 -0
- package/dist/lib/community-diagnostic.js +237 -0
- package/dist/lib/community-docker-adapter.js +498 -0
- package/dist/lib/community-home.js +31 -0
- package/dist/lib/community-instance-contract.js +148 -0
- package/dist/lib/community-instance-layout.js +97 -0
- package/dist/lib/community-lifecycle.js +1631 -0
- package/dist/lib/community-migration-snapshot.js +770 -0
- package/dist/lib/community-native-application-recovery.js +425 -0
- package/dist/lib/community-native-child.js +240 -0
- package/dist/lib/community-native-database-recovery.js +456 -0
- package/dist/lib/community-native-lifecycle.js +2458 -0
- package/dist/lib/community-native-migration.js +339 -0
- package/dist/lib/community-native-postgres.js +596 -0
- package/dist/lib/community-operation-journal.js +773 -0
- package/dist/lib/community-operation-lock.js +596 -0
- package/dist/lib/community-release-resolver.js +291 -0
- package/dist/lib/community-release-verifier.js +475 -0
- package/dist/lib/community-repo-discovery.js +252 -0
- package/dist/lib/community-setup-server-env.js +203 -0
- package/dist/lib/external-url.js +110 -0
- package/dist/lib/official-catalog-gateway.js +108 -0
- package/dist/lib/official-catalog.js +364 -0
- package/dist/lib/server-bootstrap.js +240 -0
- package/dist/lib/setup-agent-assets-bridge.js +176 -0
- package/dist/lib/setup-agent-assets-release.js +52 -0
- package/dist/lib/setup-external-postgres.js +108 -0
- package/dist/lib/setup-init-orchestrator.js +990 -0
- package/dist/lib/setup-install-guide.js +52 -0
- package/dist/lib/setup-installer-release.js +47 -0
- package/dist/lib/setup-local-postgres.js +329 -0
- package/dist/lib/setup-official-catalog-bridge.js +148 -0
- package/dist/lib/setup-readiness.js +888 -0
- package/dist/lib/tui-launcher.js +59 -0
- package/dist/main.js +150 -0
- package/dist/seeds/agents-md.js +54 -0
- package/dist/utils/agent-gateway.js +275 -0
- package/dist/utils/agents-md.js +281 -0
- package/dist/utils/api.js +255 -0
- package/dist/utils/chatv3-session-store.js +185 -0
- package/dist/utils/command.js +67 -0
- package/dist/utils/community-host-runtime.js +376 -0
- package/dist/utils/config.js +1015 -0
- package/dist/utils/discussion-workspace.js +552 -0
- package/dist/utils/experience-workspace.js +326 -0
- package/dist/utils/guide-paths.js +67 -0
- package/dist/utils/hosted-sugar.js +56 -0
- package/dist/utils/hosted-workspace.js +229 -0
- package/dist/utils/memory-workspace.js +537 -0
- package/dist/utils/operator-runtime.js +199 -0
- package/dist/utils/password-crypto.js +39 -0
- package/dist/utils/playbook-workspace.js +479 -0
- package/dist/utils/project-context.js +255 -0
- package/dist/utils/prompts.js +70 -0
- package/dist/utils/refresh-race.js +28 -0
- package/dist/utils/repo-config.js +109 -0
- package/dist/utils/repo-first-projectman.js +194 -0
- package/dist/utils/repo-first-storage.js +13 -0
- package/dist/utils/repo-first-view.js +305 -0
- package/dist/utils/session-guidance.js +278 -0
- package/dist/utils/session-state.js +137 -0
- package/dist/utils/table.js +39 -0
- package/dist/utils/transient-fs.js +49 -0
- package/dist/utils/workspace-root.js +1 -0
- package/launchers/aops-cockpit.ps1 +15 -0
- package/launchers/aops-cockpit.sh +16 -0
- package/launchers/aops-server.ps1 +15 -0
- package/launchers/aops-server.sh +16 -0
- package/native/tui/win32-x64/aops-tui.exe +0 -0
- package/package.json +81 -0
|
@@ -0,0 +1,3129 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import { logError, logSuccess } from '@aopslabs/xf-cli-ui';
|
|
4
|
+
import { applyCommonOptions, compactPayload } from '../utils/command.js';
|
|
5
|
+
import { buildAgentContextHeaders, invokeHostedToolWithApiState, requireApiState, unwrapHostedToolResult, } from '../utils/agent-gateway.js';
|
|
6
|
+
import { preferProjectNameBinding, resolveOwnerScopeIdFromBinding, resolveOwnerScopeIdFromProjectRecord, resolveProjectBindingContext, } from '../utils/project-context.js';
|
|
7
|
+
import { normalizeNonEmpty } from '../utils/command.js';
|
|
8
|
+
import { buildOperatorCookbook } from '../utils/hosted-sugar.js';
|
|
9
|
+
import { GUIDE_PATHS } from '../utils/guide-paths.js';
|
|
10
|
+
import { readLocalMemoryEntries, resolveMemoryWorkspacePaths, } from '../utils/memory-workspace.js';
|
|
11
|
+
const MEMORY_MODES = new Set(['kickoff', 'resume', 'decision', 'blocker', 'closeout', 'rule']);
|
|
12
|
+
const MEMORY_SUBJECTS = {
|
|
13
|
+
project: {
|
|
14
|
+
key: 'project',
|
|
15
|
+
subjectType: 'projectman.plan',
|
|
16
|
+
defaultQuery: 'resume project context',
|
|
17
|
+
defaultSourceTypes: ['projectman.plan'],
|
|
18
|
+
},
|
|
19
|
+
ktask: {
|
|
20
|
+
key: 'ktask',
|
|
21
|
+
subjectType: 'projectman.kanban-task',
|
|
22
|
+
defaultQuery: 'resume implementation handoff',
|
|
23
|
+
defaultSourceTypes: ['projectman.kanban-task', 'projectman.sprint', 'projectman.phase', 'projectman.microtask'],
|
|
24
|
+
},
|
|
25
|
+
sprint: {
|
|
26
|
+
key: 'sprint',
|
|
27
|
+
subjectType: 'projectman.sprint',
|
|
28
|
+
defaultQuery: 'resume active sprint context',
|
|
29
|
+
defaultSourceTypes: ['projectman.sprint', 'projectman.phase', 'projectman.microtask', 'projectman.kanban-task'],
|
|
30
|
+
},
|
|
31
|
+
phase: {
|
|
32
|
+
key: 'phase',
|
|
33
|
+
subjectType: 'projectman.phase',
|
|
34
|
+
defaultQuery: 'resume implementation handoff',
|
|
35
|
+
defaultSourceTypes: ['projectman.phase', 'projectman.microtask', 'projectman.sprint', 'projectman.kanban-task'],
|
|
36
|
+
},
|
|
37
|
+
utask: {
|
|
38
|
+
key: 'utask',
|
|
39
|
+
subjectType: 'projectman.microtask',
|
|
40
|
+
defaultQuery: 'resume implementation handoff',
|
|
41
|
+
defaultSourceTypes: ['projectman.microtask', 'projectman.phase', 'projectman.sprint', 'projectman.kanban-task'],
|
|
42
|
+
},
|
|
43
|
+
issue: {
|
|
44
|
+
key: 'issue',
|
|
45
|
+
subjectType: 'projectman.issue',
|
|
46
|
+
defaultQuery: 'resume open issue context',
|
|
47
|
+
defaultSourceTypes: ['projectman.issue', 'projectman.kanban-task', 'projectman.sprint'],
|
|
48
|
+
},
|
|
49
|
+
feedback: {
|
|
50
|
+
key: 'feedback',
|
|
51
|
+
subjectType: 'projectman.feedback',
|
|
52
|
+
defaultQuery: 'resume feedback triage context',
|
|
53
|
+
defaultSourceTypes: ['projectman.feedback', 'projectman.kanban-task', 'projectman.sprint'],
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
const MEMORY_MODE_DEFAULTS = {
|
|
57
|
+
kickoff: { kind: 'kickoff', durability: 'short', tags: ['phase:kickoff'] },
|
|
58
|
+
resume: { kind: 'resume', durability: 'short', tags: ['phase:resume'] },
|
|
59
|
+
decision: { kind: 'decision', durability: 'short', tags: ['phase:decision'] },
|
|
60
|
+
blocker: { kind: 'constraint', durability: 'short', tags: ['phase:blocker'] },
|
|
61
|
+
closeout: { kind: 'closeout', durability: 'short', tags: ['phase:closeout'] },
|
|
62
|
+
rule: { kind: 'rule', durability: 'sticky', tags: ['phase:memory'] },
|
|
63
|
+
};
|
|
64
|
+
function collectRepeatedOption(value, previous = []) {
|
|
65
|
+
const normalized = normalizeNonEmpty(value);
|
|
66
|
+
return normalized ? [...previous, normalized] : previous;
|
|
67
|
+
}
|
|
68
|
+
function toStringArray(values) {
|
|
69
|
+
return Array.isArray(values)
|
|
70
|
+
? values.map((entry) => normalizeNonEmpty(entry)).filter((entry) => Boolean(entry))
|
|
71
|
+
: [];
|
|
72
|
+
}
|
|
73
|
+
function uniqueStrings(values) {
|
|
74
|
+
const seen = new Set();
|
|
75
|
+
const result = [];
|
|
76
|
+
for (const value of values) {
|
|
77
|
+
const normalized = normalizeNonEmpty(value);
|
|
78
|
+
if (!normalized)
|
|
79
|
+
continue;
|
|
80
|
+
const key = normalized.toLowerCase();
|
|
81
|
+
if (seen.has(key))
|
|
82
|
+
continue;
|
|
83
|
+
seen.add(key);
|
|
84
|
+
result.push(normalized);
|
|
85
|
+
}
|
|
86
|
+
return result;
|
|
87
|
+
}
|
|
88
|
+
function normalizeDecisionMemoryShape(params) {
|
|
89
|
+
const normalizedKind = normalizeNonEmpty(params.kind) || undefined;
|
|
90
|
+
const durability = params.durability;
|
|
91
|
+
const tags = uniqueStrings(params.tags ?? []);
|
|
92
|
+
if (normalizedKind === 'decision') {
|
|
93
|
+
return {
|
|
94
|
+
kind: 'decision',
|
|
95
|
+
durability,
|
|
96
|
+
tags: uniqueStrings([...tags, 'phase:decision']),
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
return { kind: normalizedKind, durability, tags };
|
|
100
|
+
}
|
|
101
|
+
function normalizeTagToken(value) {
|
|
102
|
+
const normalized = String(normalizeNonEmpty(value) ?? '').toLowerCase();
|
|
103
|
+
if (!normalized)
|
|
104
|
+
return undefined;
|
|
105
|
+
const token = normalized
|
|
106
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
107
|
+
.replace(/^-+|-+$/g, '');
|
|
108
|
+
return token || undefined;
|
|
109
|
+
}
|
|
110
|
+
function buildScopedTags(prefix, values) {
|
|
111
|
+
return uniqueStrings(values.map((value) => {
|
|
112
|
+
const token = normalizeTagToken(value);
|
|
113
|
+
return token ? `${prefix}:${token}` : undefined;
|
|
114
|
+
}));
|
|
115
|
+
}
|
|
116
|
+
function buildClassificationTags(options) {
|
|
117
|
+
return uniqueStrings([
|
|
118
|
+
...buildScopedTags('purpose', toStringArray(options.purpose)),
|
|
119
|
+
...buildScopedTags('area', toStringArray(options.area)),
|
|
120
|
+
...buildScopedTags('status', toStringArray(options.status)),
|
|
121
|
+
]);
|
|
122
|
+
}
|
|
123
|
+
function toNonNegativeInteger(value, label) {
|
|
124
|
+
if (value === undefined || value === null || value === '')
|
|
125
|
+
return undefined;
|
|
126
|
+
const parsed = typeof value === 'number' ? value : Number.parseInt(String(value), 10);
|
|
127
|
+
if (!Number.isInteger(parsed) || parsed < 0) {
|
|
128
|
+
throw new Error(`${label} must be a non-negative integer.`);
|
|
129
|
+
}
|
|
130
|
+
return parsed;
|
|
131
|
+
}
|
|
132
|
+
function normalizeIsoDateTime(value, label) {
|
|
133
|
+
const normalized = normalizeNonEmpty(value);
|
|
134
|
+
if (!normalized)
|
|
135
|
+
return undefined;
|
|
136
|
+
const parsed = new Date(normalized);
|
|
137
|
+
if (Number.isNaN(parsed.getTime())) {
|
|
138
|
+
throw new Error(`${label} must be a valid ISO date/time.`);
|
|
139
|
+
}
|
|
140
|
+
return parsed.toISOString();
|
|
141
|
+
}
|
|
142
|
+
function parseRefLikeRecord(value, label) {
|
|
143
|
+
if (isRecord(value))
|
|
144
|
+
return value;
|
|
145
|
+
const normalized = normalizeNonEmpty(value);
|
|
146
|
+
if (normalized)
|
|
147
|
+
return { ref: normalized };
|
|
148
|
+
throw new Error(`${label} entries must be strings, JSON objects, JSON arrays, or @file inputs.`);
|
|
149
|
+
}
|
|
150
|
+
function stripLooseRefToken(value) {
|
|
151
|
+
const trimmed = value.trim();
|
|
152
|
+
if (trimmed.length >= 2) {
|
|
153
|
+
const first = trimmed[0];
|
|
154
|
+
const last = trimmed[trimmed.length - 1];
|
|
155
|
+
if ((first === '"' && last === '"') || (first === "'" && last === "'")) {
|
|
156
|
+
return trimmed.slice(1, -1);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return trimmed;
|
|
160
|
+
}
|
|
161
|
+
function parseLooseRefObject(value) {
|
|
162
|
+
const trimmed = value.trim();
|
|
163
|
+
if (!trimmed.startsWith('{') || !trimmed.endsWith('}'))
|
|
164
|
+
return undefined;
|
|
165
|
+
const inner = trimmed.slice(1, -1).trim();
|
|
166
|
+
if (!inner)
|
|
167
|
+
return {};
|
|
168
|
+
const result = {};
|
|
169
|
+
for (const part of inner.split(/\s*,\s*/)) {
|
|
170
|
+
if (!part)
|
|
171
|
+
continue;
|
|
172
|
+
const separatorIndex = part.indexOf(':');
|
|
173
|
+
if (separatorIndex <= 0)
|
|
174
|
+
return undefined;
|
|
175
|
+
const key = stripLooseRefToken(part.slice(0, separatorIndex));
|
|
176
|
+
const rawValue = stripLooseRefToken(part.slice(separatorIndex + 1));
|
|
177
|
+
if (!key || !rawValue)
|
|
178
|
+
return undefined;
|
|
179
|
+
result[key] = rawValue;
|
|
180
|
+
}
|
|
181
|
+
return Object.keys(result).length > 0 ? result : undefined;
|
|
182
|
+
}
|
|
183
|
+
function expandAtFileContent(value) {
|
|
184
|
+
if (typeof value === 'string' && value.startsWith('@')) {
|
|
185
|
+
return readFileSync(value.slice(1).trim(), 'utf8');
|
|
186
|
+
}
|
|
187
|
+
return value;
|
|
188
|
+
}
|
|
189
|
+
function parseRefLikeEntries(value, label) {
|
|
190
|
+
const trimmed = value.trim();
|
|
191
|
+
if (!trimmed)
|
|
192
|
+
return [];
|
|
193
|
+
const raw = trimmed.startsWith('@')
|
|
194
|
+
? readFileSync(trimmed.slice(1).trim(), 'utf8')
|
|
195
|
+
: trimmed;
|
|
196
|
+
const normalizedRaw = raw.trim();
|
|
197
|
+
if (!normalizedRaw)
|
|
198
|
+
return [];
|
|
199
|
+
if ((normalizedRaw.startsWith('{') && normalizedRaw.endsWith('}')) || (normalizedRaw.startsWith('[') && normalizedRaw.endsWith(']'))) {
|
|
200
|
+
try {
|
|
201
|
+
const parsed = JSON.parse(normalizedRaw);
|
|
202
|
+
if (Array.isArray(parsed)) {
|
|
203
|
+
return parsed.map((entry) => parseRefLikeRecord(entry, label));
|
|
204
|
+
}
|
|
205
|
+
return [parseRefLikeRecord(parsed, label)];
|
|
206
|
+
}
|
|
207
|
+
catch (error) {
|
|
208
|
+
const looseObject = parseLooseRefObject(normalizedRaw);
|
|
209
|
+
if (looseObject)
|
|
210
|
+
return [looseObject];
|
|
211
|
+
if (normalizedRaw.startsWith('{')) {
|
|
212
|
+
return [{ ref: normalizedRaw }];
|
|
213
|
+
}
|
|
214
|
+
throw error;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return [{ ref: normalizedRaw }];
|
|
218
|
+
}
|
|
219
|
+
function parseRefOptionValues(values, label) {
|
|
220
|
+
return toStringArray(values).flatMap((entry) => parseRefLikeEntries(entry, label));
|
|
221
|
+
}
|
|
222
|
+
function normalizeMemoryMode(value) {
|
|
223
|
+
const normalized = normalizeNonEmpty(value)?.toLowerCase();
|
|
224
|
+
if (!normalized)
|
|
225
|
+
return undefined;
|
|
226
|
+
if (!MEMORY_MODES.has(normalized)) {
|
|
227
|
+
throw new Error('Invalid --mode. Expected one of: kickoff, resume, decision, blocker, closeout, rule.');
|
|
228
|
+
}
|
|
229
|
+
return normalized;
|
|
230
|
+
}
|
|
231
|
+
function normalizeMemoryDurability(value) {
|
|
232
|
+
const normalized = normalizeNonEmpty(value)?.toLowerCase();
|
|
233
|
+
if (!normalized)
|
|
234
|
+
return undefined;
|
|
235
|
+
if (normalized === 'short' || normalized === 'durable' || normalized === 'sticky') {
|
|
236
|
+
return normalized;
|
|
237
|
+
}
|
|
238
|
+
throw new Error('Invalid --durability. Expected one of: short, durable, sticky.');
|
|
239
|
+
}
|
|
240
|
+
function normalizeMemorySubject(value) {
|
|
241
|
+
const normalized = normalizeNonEmpty(value)?.toLowerCase();
|
|
242
|
+
if (!normalized)
|
|
243
|
+
return undefined;
|
|
244
|
+
const alias = normalized === 'task' ? 'ktask' : normalized === 'microtask' ? 'utask' : normalized;
|
|
245
|
+
const config = MEMORY_SUBJECTS[alias];
|
|
246
|
+
if (!config) {
|
|
247
|
+
throw new Error('Invalid --subject. Expected one of: project, ktask, sprint, phase, utask, issue, feedback.');
|
|
248
|
+
}
|
|
249
|
+
return config;
|
|
250
|
+
}
|
|
251
|
+
function resolveAliasValue(primary, alias, primaryFlag, aliasFlag) {
|
|
252
|
+
const primaryValue = normalizeNonEmpty(primary);
|
|
253
|
+
const aliasValue = normalizeNonEmpty(alias);
|
|
254
|
+
if (primaryValue && aliasValue && primaryValue !== aliasValue) {
|
|
255
|
+
throw new Error(`Conflicting values for ${primaryFlag} and ${aliasFlag}.`);
|
|
256
|
+
}
|
|
257
|
+
return primaryValue ?? aliasValue;
|
|
258
|
+
}
|
|
259
|
+
function buildLinkedIds(options) {
|
|
260
|
+
return {
|
|
261
|
+
kanbanTaskId: resolveAliasValue(options.kanbanTaskId, options.taskId, '--kanban-task-id', '--task-id'),
|
|
262
|
+
sprintId: normalizeNonEmpty(options.sprintId),
|
|
263
|
+
phaseId: normalizeNonEmpty(options.phaseId),
|
|
264
|
+
microtaskId: resolveAliasValue(options.microtaskId, options.utaskId, '--microtask-id', '--utask-id'),
|
|
265
|
+
issueId: normalizeNonEmpty(options.issueId),
|
|
266
|
+
feedbackId: normalizeNonEmpty(options.feedbackId),
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
function buildSubjectId(config, options, context) {
|
|
270
|
+
const genericId = normalizeNonEmpty(options.id);
|
|
271
|
+
const subjectId = normalizeNonEmpty(options.subjectId);
|
|
272
|
+
if (genericId && subjectId && genericId !== subjectId) {
|
|
273
|
+
throw new Error('Conflicting values for --id and --subject-id.');
|
|
274
|
+
}
|
|
275
|
+
const explicit = subjectId ?? genericId;
|
|
276
|
+
if (explicit)
|
|
277
|
+
return explicit;
|
|
278
|
+
if (config?.key === 'project') {
|
|
279
|
+
const projectId = normalizeNonEmpty(context.projectId);
|
|
280
|
+
if (!projectId) {
|
|
281
|
+
throw new Error('Project subject requires --project-id or repo-bound project context.');
|
|
282
|
+
}
|
|
283
|
+
return projectId;
|
|
284
|
+
}
|
|
285
|
+
if (config) {
|
|
286
|
+
throw new Error('Subject requires --id (or --subject-id).');
|
|
287
|
+
}
|
|
288
|
+
return undefined;
|
|
289
|
+
}
|
|
290
|
+
function buildSubjectLabel(options) {
|
|
291
|
+
const label = normalizeNonEmpty(options.label);
|
|
292
|
+
const subjectLabel = normalizeNonEmpty(options.subjectLabel);
|
|
293
|
+
const subjectTitle = normalizeNonEmpty(options.subjectTitle);
|
|
294
|
+
const values = [label, subjectLabel, subjectTitle].filter(Boolean);
|
|
295
|
+
const unique = uniqueStrings(values);
|
|
296
|
+
if (unique.length > 1) {
|
|
297
|
+
throw new Error('Conflicting values for --label, --subject-label, and --subject-title.');
|
|
298
|
+
}
|
|
299
|
+
return unique[0];
|
|
300
|
+
}
|
|
301
|
+
function applySubjectDefaults(config, subjectId, linked) {
|
|
302
|
+
if (!config || !subjectId) {
|
|
303
|
+
return { sourceTypes: [], sourceIds: [] };
|
|
304
|
+
}
|
|
305
|
+
const sourceTypes = config.defaultSourceTypes;
|
|
306
|
+
const sourceIds = uniqueStrings([
|
|
307
|
+
subjectId,
|
|
308
|
+
linked.kanbanTaskId,
|
|
309
|
+
linked.sprintId,
|
|
310
|
+
linked.phaseId,
|
|
311
|
+
linked.microtaskId,
|
|
312
|
+
linked.issueId,
|
|
313
|
+
linked.feedbackId,
|
|
314
|
+
]);
|
|
315
|
+
return {
|
|
316
|
+
subjectType: config.subjectType,
|
|
317
|
+
sourceType: config.subjectType,
|
|
318
|
+
sourceId: subjectId,
|
|
319
|
+
sourceTypes,
|
|
320
|
+
sourceIds,
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
function buildEnvelope(params) {
|
|
324
|
+
return compactPayload({
|
|
325
|
+
command: params.command,
|
|
326
|
+
toolId: params.toolId,
|
|
327
|
+
resolvedContext: params.resolvedContext,
|
|
328
|
+
input: params.input,
|
|
329
|
+
result: params.result,
|
|
330
|
+
diagnostics: params.diagnostics,
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
function arrayLength(value) {
|
|
334
|
+
return Array.isArray(value) ? value.length : 0;
|
|
335
|
+
}
|
|
336
|
+
function buildMemoryQualityDiagnostics(params) {
|
|
337
|
+
const kind = normalizeNonEmpty(params.kind)?.toLowerCase();
|
|
338
|
+
const durability = normalizeNonEmpty(params.durability)?.toLowerCase();
|
|
339
|
+
const content = normalizeNonEmpty(params.content) ?? '';
|
|
340
|
+
const tags = toStringArray(params.tags).map((tag) => tag.toLowerCase());
|
|
341
|
+
const hasLinkedPmRef = tags.some((tag) => tag.startsWith('kanban-task:')
|
|
342
|
+
|| tag.startsWith('sprint:')
|
|
343
|
+
|| tag.startsWith('phase:')
|
|
344
|
+
|| tag.startsWith('microtask:')
|
|
345
|
+
|| tag.startsWith('issue:')
|
|
346
|
+
|| tag.startsWith('feedback:'));
|
|
347
|
+
const hasSourceRefs = arrayLength(params.sourceRefs) > 0;
|
|
348
|
+
const hasNextReadRefs = arrayLength(params.nextReadRefs) > 0;
|
|
349
|
+
const warnings = [];
|
|
350
|
+
if ((kind === 'closeout' || kind === 'decision' || kind === 'blocker') && !normalizeNonEmpty(params.validationState)) {
|
|
351
|
+
warnings.push('Add --validation-state with test/review/status evidence for closeout, decision, and blocker memory.');
|
|
352
|
+
}
|
|
353
|
+
if ((kind === 'kickoff' || kind === 'resume' || kind === 'closeout') && !normalizeNonEmpty(params.nextAction)) {
|
|
354
|
+
warnings.push('Add --next-action so the next agent knows exactly where to continue or verify.');
|
|
355
|
+
}
|
|
356
|
+
if (!hasLinkedPmRef && !hasSourceRefs) {
|
|
357
|
+
warnings.push('Link concrete evidence with --task-id/--sprint-id/--issue-id or --source-ref; project-only memory without refs is hard to verify.');
|
|
358
|
+
}
|
|
359
|
+
if ((durability === 'durable' || kind === 'closeout' || kind === 'decision') && content.length < 180) {
|
|
360
|
+
warnings.push('Durable/decision memory content is very short; include request, work surface, outcome, refs, validation, and carry-forward.');
|
|
361
|
+
}
|
|
362
|
+
if (/\bPR\d+\b/i.test(content)) {
|
|
363
|
+
warnings.push('Expand PR1/PR2-style labels into concrete scope and changed behavior; future agents should not need prior chat context.');
|
|
364
|
+
}
|
|
365
|
+
if (!hasSourceRefs && !hasNextReadRefs) {
|
|
366
|
+
warnings.push('Add --source-ref or --next-read-ref for artifacts, sessions, docs, files, or PM records that prove the memory.');
|
|
367
|
+
}
|
|
368
|
+
return {
|
|
369
|
+
contract: 'agent-readable-evidence-pack',
|
|
370
|
+
status: warnings.length > 0 ? 'warn' : 'pass',
|
|
371
|
+
requiredContent: [
|
|
372
|
+
'request-or-purpose',
|
|
373
|
+
'work-surface: board/task/sprint/issue/session/doc/file refs',
|
|
374
|
+
'completed-outcome-and-current-status',
|
|
375
|
+
'validation-or-review-evidence',
|
|
376
|
+
'open-risks-and-next-action',
|
|
377
|
+
],
|
|
378
|
+
warnings,
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
function inferErrorCode(message) {
|
|
382
|
+
const normalized = message.toLowerCase();
|
|
383
|
+
if (normalized.includes('apply_required'))
|
|
384
|
+
return 'apply_required';
|
|
385
|
+
if (normalized.includes('confirmation_required'))
|
|
386
|
+
return 'confirmation_required';
|
|
387
|
+
if (normalized.includes('not_found'))
|
|
388
|
+
return 'not_found';
|
|
389
|
+
if (normalized.includes('provide --id'))
|
|
390
|
+
return 'missing_id';
|
|
391
|
+
if (normalized.includes('provide one content source'))
|
|
392
|
+
return 'missing_content_source';
|
|
393
|
+
return 'command_failed';
|
|
394
|
+
}
|
|
395
|
+
function parseApiErrorPayload(message) {
|
|
396
|
+
const match = /^API\s+(\d+):\s+([\s\S]+)$/i.exec(message.trim());
|
|
397
|
+
if (!match)
|
|
398
|
+
return {};
|
|
399
|
+
const status = Number.parseInt(match[1] ?? '', 10);
|
|
400
|
+
const rawPayload = match[2]?.trim();
|
|
401
|
+
if (!rawPayload)
|
|
402
|
+
return { status: Number.isInteger(status) ? status : undefined };
|
|
403
|
+
try {
|
|
404
|
+
const parsed = JSON.parse(rawPayload);
|
|
405
|
+
return {
|
|
406
|
+
status: Number.isInteger(status) ? status : undefined,
|
|
407
|
+
payload: isRecord(parsed) ? parsed : undefined,
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
catch {
|
|
411
|
+
return { status: Number.isInteger(status) ? status : undefined };
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
function augmentMemoryErrorMessage(toolId, message) {
|
|
415
|
+
const normalized = String(message ?? '');
|
|
416
|
+
const api = parseApiErrorPayload(normalized);
|
|
417
|
+
const payloadMessage = normalizeNonEmpty(api.payload?.message);
|
|
418
|
+
const payloadError = normalizeNonEmpty(api.payload?.error);
|
|
419
|
+
const baseMessage = payloadMessage ?? payloadError ?? normalized;
|
|
420
|
+
const code = inferErrorCode(`${normalized} ${payloadMessage ?? ''} ${payloadError ?? ''}`);
|
|
421
|
+
if (code === 'apply_required') {
|
|
422
|
+
return {
|
|
423
|
+
code,
|
|
424
|
+
displayMessage: `${baseMessage}\nRetry with --apply because ${toolId ?? 'this command'} writes durable memory.`,
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
if (code === 'confirmation_required') {
|
|
428
|
+
return {
|
|
429
|
+
code,
|
|
430
|
+
displayMessage: `${baseMessage}\nRetry with --apply --confirm because ${toolId ?? 'this command'} is destructive.`,
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
return { code, displayMessage: baseMessage };
|
|
434
|
+
}
|
|
435
|
+
function emitMemoryCommandError(params) {
|
|
436
|
+
const rawMessage = params.error instanceof Error ? params.error.message : String(params.error);
|
|
437
|
+
const api = parseApiErrorPayload(rawMessage);
|
|
438
|
+
const augmented = augmentMemoryErrorMessage(params.toolId, rawMessage);
|
|
439
|
+
if (params.options.json) {
|
|
440
|
+
console.log(JSON.stringify(compactPayload({
|
|
441
|
+
command: params.command,
|
|
442
|
+
toolId: params.toolId,
|
|
443
|
+
resolvedContext: params.resolvedContext ? buildResolvedContextRecord(params.resolvedContext) : undefined,
|
|
444
|
+
input: params.input,
|
|
445
|
+
error: compactPayload({
|
|
446
|
+
code: augmented.code,
|
|
447
|
+
message: augmented.displayMessage,
|
|
448
|
+
status: api.status,
|
|
449
|
+
detail: isRecord(api.payload) ? api.payload : undefined,
|
|
450
|
+
}),
|
|
451
|
+
}), null, 2));
|
|
452
|
+
}
|
|
453
|
+
else {
|
|
454
|
+
logError(augmented.displayMessage);
|
|
455
|
+
}
|
|
456
|
+
process.exitCode = 1;
|
|
457
|
+
}
|
|
458
|
+
function isRecord(value) {
|
|
459
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
460
|
+
}
|
|
461
|
+
function readRecordTags(value) {
|
|
462
|
+
return isRecord(value) ? toStringArray(value.tags).map((entry) => entry.toLowerCase()) : [];
|
|
463
|
+
}
|
|
464
|
+
function readRecordMeta(value) {
|
|
465
|
+
return isRecord(value) && isRecord(value.meta) ? value.meta : {};
|
|
466
|
+
}
|
|
467
|
+
function matchesClassificationFilters(record, options) {
|
|
468
|
+
const requiredTags = buildClassificationTags(options);
|
|
469
|
+
if (requiredTags.length === 0)
|
|
470
|
+
return true;
|
|
471
|
+
const tagSet = new Set(readRecordTags(record));
|
|
472
|
+
const meta = readRecordMeta(record);
|
|
473
|
+
const purposeValues = uniqueStrings([...toStringArray(meta.purpose), normalizeNonEmpty(meta.purpose)]);
|
|
474
|
+
const areaValues = uniqueStrings([...toStringArray(meta.area), ...toStringArray(meta.areas), normalizeNonEmpty(meta.area)]);
|
|
475
|
+
const statusValues = uniqueStrings([...toStringArray(meta.status), normalizeNonEmpty(meta.status)]);
|
|
476
|
+
const derivedTags = new Set([
|
|
477
|
+
...buildScopedTags('purpose', purposeValues),
|
|
478
|
+
...buildScopedTags('area', areaValues),
|
|
479
|
+
...buildScopedTags('status', statusValues),
|
|
480
|
+
]);
|
|
481
|
+
for (const tag of requiredTags) {
|
|
482
|
+
const normalizedTag = tag.toLowerCase();
|
|
483
|
+
if (!tagSet.has(normalizedTag) && !derivedTags.has(normalizedTag)) {
|
|
484
|
+
return false;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
return true;
|
|
488
|
+
}
|
|
489
|
+
function filterHostedMemoryResult(result, options) {
|
|
490
|
+
if (buildClassificationTags(options).length === 0)
|
|
491
|
+
return result;
|
|
492
|
+
if (Array.isArray(result)) {
|
|
493
|
+
return result.filter((entry) => matchesClassificationFilters(entry, options));
|
|
494
|
+
}
|
|
495
|
+
if (!isRecord(result) || !Array.isArray(result.data))
|
|
496
|
+
return result;
|
|
497
|
+
return {
|
|
498
|
+
...result,
|
|
499
|
+
data: result.data.filter((entry) => matchesClassificationFilters(entry, options)),
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
const REPO_FIRST_MEMORY_TOOL_ID = 'repo-first.memory';
|
|
503
|
+
const MEMORY_EVIDENCE_HELP_TEXT = [
|
|
504
|
+
'',
|
|
505
|
+
'Memory quality contract:',
|
|
506
|
+
' Memory is an agent-readable evidence pack, not a chat log or changelog headline.',
|
|
507
|
+
' Content must name the request/purpose, PM surface (board/task/sprint/issue), concrete outcome, validation/review evidence, open risks, and next action.',
|
|
508
|
+
' Use --task-id/--sprint-id/--issue-id plus --source-ref/--next-read-ref so a future agent can verify the claim without rereading the whole transcript.',
|
|
509
|
+
' Expand labels like PR1/PR2 into concrete behavior and files; raw phase labels alone are not durable context.',
|
|
510
|
+
].join('\n');
|
|
511
|
+
function buildFileBasedEntryTags(entry) {
|
|
512
|
+
const classification = buildClassificationTags({
|
|
513
|
+
purpose: entry.purpose,
|
|
514
|
+
area: entry.areas,
|
|
515
|
+
status: entry.status,
|
|
516
|
+
});
|
|
517
|
+
return new Set(uniqueStrings([...entry.tags, ...classification]).map((value) => value.toLowerCase()));
|
|
518
|
+
}
|
|
519
|
+
function localEntryMatchesClassification(entry, options) {
|
|
520
|
+
const requiredTags = buildClassificationTags(options);
|
|
521
|
+
if (requiredTags.length === 0)
|
|
522
|
+
return true;
|
|
523
|
+
const tagSet = buildFileBasedEntryTags(entry);
|
|
524
|
+
return requiredTags.every((tag) => tagSet.has(tag.toLowerCase()));
|
|
525
|
+
}
|
|
526
|
+
function normalizeLocalMemoryRecord(entry) {
|
|
527
|
+
return compactPayload({
|
|
528
|
+
id: entry.memoryId ?? entry.id,
|
|
529
|
+
kind: entry.kind,
|
|
530
|
+
durability: entry.durability,
|
|
531
|
+
content: entry.content,
|
|
532
|
+
tags: entry.tags,
|
|
533
|
+
createdAt: entry.createdAt,
|
|
534
|
+
updatedAt: entry.updatedAt,
|
|
535
|
+
importance: entry.importance,
|
|
536
|
+
sourceType: entry.subjectType,
|
|
537
|
+
sourceId: entry.subjectId,
|
|
538
|
+
storage: entry.storage ?? 'local-cache',
|
|
539
|
+
meta: compactPayload({
|
|
540
|
+
projectId: entry.projectId,
|
|
541
|
+
subjectType: entry.subjectType,
|
|
542
|
+
subjectId: entry.subjectId,
|
|
543
|
+
subjectTitle: entry.subjectTitle,
|
|
544
|
+
nextAction: entry.nextAction,
|
|
545
|
+
validationState: entry.validationState,
|
|
546
|
+
sourceRefs: entry.sourceRefs,
|
|
547
|
+
nextReadRefs: entry.nextReadRefs,
|
|
548
|
+
purpose: entry.purpose,
|
|
549
|
+
areas: entry.areas,
|
|
550
|
+
status: entry.status,
|
|
551
|
+
supersedes: normalizeNonEmpty(entry.raw?.supersedes),
|
|
552
|
+
checkpointAs: normalizeNonEmpty(entry.raw?.checkpointAs),
|
|
553
|
+
summaryType: normalizeNonEmpty(entry.raw?.summaryType),
|
|
554
|
+
}),
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
async function loadFileBasedEntries(context) {
|
|
558
|
+
const paths = resolveMemoryWorkspacePaths(context);
|
|
559
|
+
return readLocalMemoryEntries(paths.localItemsDir);
|
|
560
|
+
}
|
|
561
|
+
function buildFileBasedResultPayload(entries) {
|
|
562
|
+
return entries.map((entry) => normalizeLocalMemoryRecord(entry));
|
|
563
|
+
}
|
|
564
|
+
function filterFileBasedEntries(entries, options) {
|
|
565
|
+
const subjectConfig = normalizeMemorySubject(options.subject);
|
|
566
|
+
const subjectType = normalizeNonEmpty(options.subjectType) ?? subjectConfig?.subjectType;
|
|
567
|
+
const subjectId = normalizeNonEmpty(options.subjectId) ?? normalizeNonEmpty(options.id);
|
|
568
|
+
const sourceType = normalizeNonEmpty(options.sourceType);
|
|
569
|
+
const sourceId = normalizeNonEmpty(options.sourceId);
|
|
570
|
+
const linked = buildLinkedIds(options);
|
|
571
|
+
const kind = normalizeNonEmpty(options.kind)?.toLowerCase();
|
|
572
|
+
const durability = normalizeMemoryDurability(options.durability);
|
|
573
|
+
return entries.filter((entry) => {
|
|
574
|
+
if (kind && (entry.kind ?? '').toLowerCase() !== kind)
|
|
575
|
+
return false;
|
|
576
|
+
if (durability && entry.durability !== durability)
|
|
577
|
+
return false;
|
|
578
|
+
if (subjectType && entry.subjectType !== subjectType)
|
|
579
|
+
return false;
|
|
580
|
+
if (subjectId && entry.subjectId !== subjectId)
|
|
581
|
+
return false;
|
|
582
|
+
if (sourceType && entry.subjectType !== sourceType)
|
|
583
|
+
return false;
|
|
584
|
+
if (sourceId && entry.subjectId !== sourceId)
|
|
585
|
+
return false;
|
|
586
|
+
const tagSet = buildFileBasedEntryTags(entry);
|
|
587
|
+
if (linked.kanbanTaskId && !tagSet.has(`kanban-task:${linked.kanbanTaskId}`.toLowerCase()))
|
|
588
|
+
return false;
|
|
589
|
+
if (linked.sprintId && !tagSet.has(`sprint:${linked.sprintId}`.toLowerCase()))
|
|
590
|
+
return false;
|
|
591
|
+
if (linked.phaseId && !tagSet.has(`phase:${linked.phaseId}`.toLowerCase()))
|
|
592
|
+
return false;
|
|
593
|
+
if (linked.microtaskId && !tagSet.has(`microtask:${linked.microtaskId}`.toLowerCase()))
|
|
594
|
+
return false;
|
|
595
|
+
if (linked.issueId && !tagSet.has(`issue:${linked.issueId}`.toLowerCase()))
|
|
596
|
+
return false;
|
|
597
|
+
if (linked.feedbackId && !tagSet.has(`feedback:${linked.feedbackId}`.toLowerCase()))
|
|
598
|
+
return false;
|
|
599
|
+
if (options.strictClassification === true) {
|
|
600
|
+
return localEntryMatchesClassification(entry, options);
|
|
601
|
+
}
|
|
602
|
+
return true;
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
function computeFileBasedSearchScore(entry, query, context, options) {
|
|
606
|
+
const normalizedQuery = normalizeNonEmpty(query)?.toLowerCase();
|
|
607
|
+
const haystack = [
|
|
608
|
+
entry.content,
|
|
609
|
+
entry.subjectTitle,
|
|
610
|
+
entry.nextAction,
|
|
611
|
+
entry.validationState,
|
|
612
|
+
...entry.tags,
|
|
613
|
+
...entry.purpose,
|
|
614
|
+
...entry.areas,
|
|
615
|
+
...entry.status,
|
|
616
|
+
]
|
|
617
|
+
.filter((value) => Boolean(value))
|
|
618
|
+
.join(' ')
|
|
619
|
+
.toLowerCase();
|
|
620
|
+
const tagSet = buildFileBasedEntryTags(entry);
|
|
621
|
+
let score = 0;
|
|
622
|
+
if (options.subject && entry.subjectType === normalizeMemorySubject(options.subject)?.subjectType) {
|
|
623
|
+
score += 50;
|
|
624
|
+
}
|
|
625
|
+
const requestedId = normalizeNonEmpty(options.subjectId) ?? normalizeNonEmpty(options.id);
|
|
626
|
+
if (requestedId && entry.subjectId === requestedId) {
|
|
627
|
+
score += 80;
|
|
628
|
+
}
|
|
629
|
+
if (!requestedId && normalizeMemorySubject(options.subject)?.key === 'project' && entry.projectId === context.projectId) {
|
|
630
|
+
score += 40;
|
|
631
|
+
}
|
|
632
|
+
for (const tag of buildClassificationTags(options)) {
|
|
633
|
+
if (tagSet.has(tag.toLowerCase()))
|
|
634
|
+
score += 20;
|
|
635
|
+
}
|
|
636
|
+
if (normalizedQuery) {
|
|
637
|
+
if (haystack.includes(normalizedQuery))
|
|
638
|
+
score += 30;
|
|
639
|
+
normalizedQuery
|
|
640
|
+
.split(/\s+/)
|
|
641
|
+
.filter(Boolean)
|
|
642
|
+
.forEach((token) => {
|
|
643
|
+
if (haystack.includes(token))
|
|
644
|
+
score += 8;
|
|
645
|
+
});
|
|
646
|
+
}
|
|
647
|
+
const timestamp = entry.updatedAt ?? entry.createdAt;
|
|
648
|
+
if (timestamp) {
|
|
649
|
+
const ageMs = Math.max(0, Date.now() - new Date(timestamp).getTime());
|
|
650
|
+
const days = Math.floor(ageMs / 86_400_000);
|
|
651
|
+
score += Math.max(0, 15 - Math.min(days, 15));
|
|
652
|
+
}
|
|
653
|
+
return score;
|
|
654
|
+
}
|
|
655
|
+
function resolveMemoryQuery(options) {
|
|
656
|
+
return normalizeNonEmpty(options.query) ?? normalizeNonEmpty(options.q);
|
|
657
|
+
}
|
|
658
|
+
function summarizeFileBasedContent(content, maxLength = 120) {
|
|
659
|
+
const compact = content.replace(/\s+/g, ' ').trim();
|
|
660
|
+
if (compact.length <= maxLength)
|
|
661
|
+
return compact || 'No content.';
|
|
662
|
+
return `${compact.slice(0, Math.max(0, maxLength - 3)).trimEnd()}...`;
|
|
663
|
+
}
|
|
664
|
+
function memoryEntryId(entry) {
|
|
665
|
+
return normalizeNonEmpty(entry.memoryId) ?? normalizeNonEmpty(entry.id);
|
|
666
|
+
}
|
|
667
|
+
function memoryEntryRef(entry) {
|
|
668
|
+
const id = memoryEntryId(entry);
|
|
669
|
+
return id ? `memory:${id}` : undefined;
|
|
670
|
+
}
|
|
671
|
+
function buildSupersededMemoryIdSet(entries) {
|
|
672
|
+
const superseded = new Set();
|
|
673
|
+
for (const entry of entries) {
|
|
674
|
+
const target = normalizeNonEmpty(entry.raw?.supersedes);
|
|
675
|
+
if (target)
|
|
676
|
+
superseded.add(target);
|
|
677
|
+
}
|
|
678
|
+
return superseded;
|
|
679
|
+
}
|
|
680
|
+
function collapseSupersededFileBasedEntries(entries) {
|
|
681
|
+
const superseded = buildSupersededMemoryIdSet(entries);
|
|
682
|
+
if (superseded.size === 0)
|
|
683
|
+
return entries;
|
|
684
|
+
return entries.filter((entry) => {
|
|
685
|
+
const id = memoryEntryId(entry);
|
|
686
|
+
return !id || !superseded.has(id);
|
|
687
|
+
});
|
|
688
|
+
}
|
|
689
|
+
function buildFileBasedResumePack(entries, context, options) {
|
|
690
|
+
const filtered = collapseSupersededFileBasedEntries(filterFileBasedEntries(entries, options));
|
|
691
|
+
const ranked = filtered
|
|
692
|
+
.map((entry) => ({
|
|
693
|
+
entry,
|
|
694
|
+
score: computeFileBasedSearchScore(entry, resolveMemoryQuery(options) ?? options.goal, context, options),
|
|
695
|
+
}))
|
|
696
|
+
.sort((left, right) => right.score - left.score || (right.entry.updatedAt ?? right.entry.createdAt ?? '').localeCompare(left.entry.updatedAt ?? left.entry.createdAt ?? ''));
|
|
697
|
+
const summaryCandidates = ranked
|
|
698
|
+
.map(({ entry }) => entry)
|
|
699
|
+
.filter((entry) => ['resume', 'kickoff', 'closeout', 'decision'].includes((entry.kind ?? '').toLowerCase()))
|
|
700
|
+
.slice(0, 4);
|
|
701
|
+
const stickyRules = filtered
|
|
702
|
+
.filter((entry) => entry.durability === 'sticky' && (entry.kind ?? '').toLowerCase() === 'rule')
|
|
703
|
+
.map((entry) => entry.content);
|
|
704
|
+
const result = {
|
|
705
|
+
subject: compactPayload({
|
|
706
|
+
type: normalizeMemorySubject(options.subject)?.subjectType ?? 'projectman.plan',
|
|
707
|
+
id: normalizeNonEmpty(options.subjectId) ?? normalizeNonEmpty(options.id) ?? context.projectId,
|
|
708
|
+
}),
|
|
709
|
+
resumeSummary: summaryCandidates.length > 0
|
|
710
|
+
? summaryCandidates.map((entry) => summarizeFileBasedContent(entry.content, 100)).join(' | ')
|
|
711
|
+
: 'No hosted resume summary available.',
|
|
712
|
+
readStrategy: 'repo-first-deterministic',
|
|
713
|
+
bootstrapGuidance: stickyRules,
|
|
714
|
+
relatedMemory: buildFileBasedResultPayload(ranked.slice(0, toNonNegativeInteger(options.limit, 'limit') ?? 8).map(({ entry }) => entry)),
|
|
715
|
+
recommendedRefs: [],
|
|
716
|
+
depth: options.depth === 'deep' ? 'deep' : 'light',
|
|
717
|
+
};
|
|
718
|
+
return result;
|
|
719
|
+
}
|
|
720
|
+
function buildFileBasedSynopsis(entries, context, options) {
|
|
721
|
+
const filtered = collapseSupersededFileBasedEntries(filterFileBasedEntries(entries, options));
|
|
722
|
+
const ranked = filtered
|
|
723
|
+
.map((entry) => ({
|
|
724
|
+
entry,
|
|
725
|
+
score: computeFileBasedSearchScore(entry, resolveMemoryQuery(options) ?? options.goal, context, options),
|
|
726
|
+
}))
|
|
727
|
+
.sort((left, right) => right.score - left.score || (right.entry.updatedAt ?? right.entry.createdAt ?? '').localeCompare(left.entry.updatedAt ?? left.entry.createdAt ?? ''))
|
|
728
|
+
.map(({ entry }) => entry);
|
|
729
|
+
const currentFocus = ranked.find((entry) => ['resume', 'kickoff', 'decision'].includes((entry.kind ?? '').toLowerCase()))?.content;
|
|
730
|
+
const decisions = ranked
|
|
731
|
+
.filter((entry) => ['decision', 'note'].includes((entry.kind ?? '').toLowerCase()))
|
|
732
|
+
.slice(0, 5)
|
|
733
|
+
.map((entry) => summarizeFileBasedContent(entry.content, 120));
|
|
734
|
+
const openItems = ranked
|
|
735
|
+
.map((entry) => normalizeNonEmpty(entry.nextAction))
|
|
736
|
+
.filter((entry) => Boolean(entry))
|
|
737
|
+
.slice(0, 8);
|
|
738
|
+
const bootstrapGuidance = ranked
|
|
739
|
+
.filter((entry) => entry.durability === 'sticky' && (entry.kind ?? '').toLowerCase() === 'rule')
|
|
740
|
+
.slice(0, 8)
|
|
741
|
+
.map((entry) => summarizeFileBasedContent(entry.content, 140));
|
|
742
|
+
return {
|
|
743
|
+
projectId: context.projectId,
|
|
744
|
+
scopeId: context.scopeId,
|
|
745
|
+
summary: currentFocus ? summarizeFileBasedContent(currentFocus, 220) : 'No hosted synopsis available.',
|
|
746
|
+
decisions,
|
|
747
|
+
openItems,
|
|
748
|
+
bootstrapGuidance,
|
|
749
|
+
currentFocus: currentFocus ? summarizeFileBasedContent(currentFocus, 160) : undefined,
|
|
750
|
+
sourceMemoryIds: ranked.slice(0, 8).map((entry) => entry.memoryId ?? entry.id).filter((entry) => Boolean(entry)),
|
|
751
|
+
generatedAt: new Date().toISOString(),
|
|
752
|
+
readStrategy: 'repo-first-deterministic',
|
|
753
|
+
};
|
|
754
|
+
}
|
|
755
|
+
function rankFileBasedEntries(entries, context, options) {
|
|
756
|
+
return collapseSupersededFileBasedEntries(filterFileBasedEntries(entries, options))
|
|
757
|
+
.map((entry) => ({
|
|
758
|
+
entry,
|
|
759
|
+
score: computeFileBasedSearchScore(entry, resolveMemoryQuery(options) ?? options.goal, context, options),
|
|
760
|
+
}))
|
|
761
|
+
.sort((left, right) => right.score - left.score
|
|
762
|
+
|| (right.entry.updatedAt ?? right.entry.createdAt ?? '').localeCompare(left.entry.updatedAt ?? left.entry.createdAt ?? ''))
|
|
763
|
+
.map(({ entry }) => entry);
|
|
764
|
+
}
|
|
765
|
+
function uniqueRefRecords(refs) {
|
|
766
|
+
const seen = new Set();
|
|
767
|
+
const result = [];
|
|
768
|
+
for (const ref of refs) {
|
|
769
|
+
const record = parseRefLikeRecord(ref, 'memory ref');
|
|
770
|
+
const key = JSON.stringify(record);
|
|
771
|
+
if (seen.has(key))
|
|
772
|
+
continue;
|
|
773
|
+
seen.add(key);
|
|
774
|
+
result.push(record);
|
|
775
|
+
}
|
|
776
|
+
return result;
|
|
777
|
+
}
|
|
778
|
+
function readStickyRank(entry) {
|
|
779
|
+
const rawRank = entry.raw?.stickyRank;
|
|
780
|
+
if (typeof rawRank === 'number' && Number.isFinite(rawRank))
|
|
781
|
+
return rawRank;
|
|
782
|
+
if (typeof rawRank === 'string' && rawRank.trim()) {
|
|
783
|
+
const parsed = Number.parseInt(rawRank, 10);
|
|
784
|
+
if (Number.isFinite(parsed))
|
|
785
|
+
return parsed;
|
|
786
|
+
}
|
|
787
|
+
return entry.importance ?? 0;
|
|
788
|
+
}
|
|
789
|
+
function sortStickyRuleEntries(entries) {
|
|
790
|
+
return [...entries]
|
|
791
|
+
.filter((entry) => entry.durability === 'sticky' && (entry.kind ?? '').toLowerCase() === 'rule')
|
|
792
|
+
.sort((left, right) => readStickyRank(right) - readStickyRank(left)
|
|
793
|
+
|| (right.updatedAt ?? right.createdAt ?? '').localeCompare(left.updatedAt ?? left.createdAt ?? ''));
|
|
794
|
+
}
|
|
795
|
+
function buildMemoryPointer(entry, whySimilar) {
|
|
796
|
+
return compactPayload({
|
|
797
|
+
id: memoryEntryId(entry),
|
|
798
|
+
ref: memoryEntryRef(entry),
|
|
799
|
+
kind: entry.kind,
|
|
800
|
+
durability: entry.durability,
|
|
801
|
+
whySimilar,
|
|
802
|
+
updatedAt: entry.updatedAt ?? entry.createdAt,
|
|
803
|
+
summary: summarizeFileBasedContent(entry.content, 160),
|
|
804
|
+
});
|
|
805
|
+
}
|
|
806
|
+
function buildStickyRulePointer(entry) {
|
|
807
|
+
return compactPayload({
|
|
808
|
+
id: memoryEntryId(entry),
|
|
809
|
+
ref: memoryEntryRef(entry),
|
|
810
|
+
rank: readStickyRank(entry),
|
|
811
|
+
summary: summarizeFileBasedContent(entry.content, 140),
|
|
812
|
+
});
|
|
813
|
+
}
|
|
814
|
+
function buildWhySimilar(entry, options) {
|
|
815
|
+
const query = resolveMemoryQuery(options) ?? normalizeNonEmpty(options.goal);
|
|
816
|
+
const reasons = uniqueStrings([
|
|
817
|
+
query ? `matched query: ${query}` : undefined,
|
|
818
|
+
entry.kind ? `kind:${entry.kind}` : undefined,
|
|
819
|
+
entry.durability ? `durability:${entry.durability}` : undefined,
|
|
820
|
+
...entry.purpose.map((value) => `purpose:${value}`),
|
|
821
|
+
...entry.areas.map((value) => `area:${value}`),
|
|
822
|
+
]);
|
|
823
|
+
return reasons.slice(0, 3).join('; ') || 'ranked by subject, tags, and recency';
|
|
824
|
+
}
|
|
825
|
+
function applyLightBriefBudget(result, maxBytes = 12_000) {
|
|
826
|
+
const next = {
|
|
827
|
+
...result,
|
|
828
|
+
budget: {
|
|
829
|
+
mode: 'light',
|
|
830
|
+
maxBytes,
|
|
831
|
+
truncated: false,
|
|
832
|
+
},
|
|
833
|
+
};
|
|
834
|
+
const mutableSimilar = Array.isArray(next.similarWork) ? [...next.similarWork] : [];
|
|
835
|
+
const mutableSticky = Array.isArray(next.stickyRules) ? [...next.stickyRules] : [];
|
|
836
|
+
next.similarWork = mutableSimilar;
|
|
837
|
+
next.stickyRules = mutableSticky;
|
|
838
|
+
let bytes = Buffer.byteLength(JSON.stringify(next), 'utf8');
|
|
839
|
+
while (bytes > maxBytes && mutableSimilar.length > 1) {
|
|
840
|
+
mutableSimilar.pop();
|
|
841
|
+
next.budget.truncated = true;
|
|
842
|
+
bytes = Buffer.byteLength(JSON.stringify(next), 'utf8');
|
|
843
|
+
}
|
|
844
|
+
while (bytes > maxBytes && mutableSticky.length > 1) {
|
|
845
|
+
mutableSticky.pop();
|
|
846
|
+
next.budget.truncated = true;
|
|
847
|
+
bytes = Buffer.byteLength(JSON.stringify(next), 'utf8');
|
|
848
|
+
}
|
|
849
|
+
next.budget.bytes = bytes;
|
|
850
|
+
return next;
|
|
851
|
+
}
|
|
852
|
+
function buildMemoryGapHints(params) {
|
|
853
|
+
const gaps = [];
|
|
854
|
+
if (params.resumePack.bootstrapGuidance.length === 0) {
|
|
855
|
+
gaps.push('No sticky project guidance is available for bootstrap.');
|
|
856
|
+
}
|
|
857
|
+
if (params.ranked.length === 0) {
|
|
858
|
+
gaps.push('No matching hosted memory items were found for this subject/query.');
|
|
859
|
+
}
|
|
860
|
+
if (uniqueRefRecords(params.ranked.flatMap((entry) => entry.nextReadRefs ?? [])).length === 0) {
|
|
861
|
+
gaps.push('No next-read refs are attached to the selected memory set.');
|
|
862
|
+
}
|
|
863
|
+
if (Array.isArray(params.synopsis.openItems) && params.synopsis.openItems.length === 0) {
|
|
864
|
+
gaps.push('No explicit next actions are recorded in the selected memory set.');
|
|
865
|
+
}
|
|
866
|
+
return gaps;
|
|
867
|
+
}
|
|
868
|
+
function buildFileBasedBrief(entries, context, options) {
|
|
869
|
+
const limit = toNonNegativeInteger(options.limit, 'limit') ?? 6;
|
|
870
|
+
const deep = options.depth === 'deep';
|
|
871
|
+
const resumePack = buildFileBasedResumePack(entries, context, {
|
|
872
|
+
...options,
|
|
873
|
+
limit,
|
|
874
|
+
depth: options.depth ?? 'light',
|
|
875
|
+
});
|
|
876
|
+
const synopsis = buildFileBasedSynopsis(entries, context, { ...options, limit });
|
|
877
|
+
const ranked = rankFileBasedEntries(entries, context, options).slice(0, limit);
|
|
878
|
+
const nextActions = uniqueStrings(ranked.map((entry) => entry.nextAction)).slice(0, 5);
|
|
879
|
+
const nextReadRefs = uniqueRefRecords(ranked.flatMap((entry) => entry.nextReadRefs ?? [])).slice(0, 8);
|
|
880
|
+
const filtered = collapseSupersededFileBasedEntries(filterFileBasedEntries(entries, options));
|
|
881
|
+
const stickyRuleEntries = sortStickyRuleEntries(filtered);
|
|
882
|
+
const stickyCap = Math.min(3, Math.max(1, limit));
|
|
883
|
+
const selectedStickyRules = stickyRuleEntries.slice(0, deep ? stickyRuleEntries.length : stickyCap);
|
|
884
|
+
const baseResult = {
|
|
885
|
+
readStrategy: deep ? 'repo-first-brief-deep' : 'repo-first-brief',
|
|
886
|
+
generatedAt: new Date().toISOString(),
|
|
887
|
+
subject: resumePack.subject,
|
|
888
|
+
currentSynopsis: synopsis,
|
|
889
|
+
subjectResume: compactPayload({
|
|
890
|
+
resumeSummary: resumePack.resumeSummary,
|
|
891
|
+
depth: resumePack.depth,
|
|
892
|
+
relatedCount: resumePack.relatedMemory.length,
|
|
893
|
+
}),
|
|
894
|
+
stickyRules: deep
|
|
895
|
+
? selectedStickyRules.map((entry) => entry.content)
|
|
896
|
+
: selectedStickyRules.map((entry) => buildStickyRulePointer(entry)),
|
|
897
|
+
stickyRuleCount: stickyRuleEntries.length,
|
|
898
|
+
omittedStickyRuleCount: deep ? 0 : Math.max(0, stickyRuleEntries.length - selectedStickyRules.length),
|
|
899
|
+
similarWork: deep
|
|
900
|
+
? buildFileBasedResultPayload(ranked)
|
|
901
|
+
: ranked.map((entry) => buildMemoryPointer(entry, buildWhySimilar(entry, options))),
|
|
902
|
+
recommendedNextReads: nextReadRefs,
|
|
903
|
+
memoryGaps: buildMemoryGapHints({ resumePack, synopsis, ranked }),
|
|
904
|
+
nextMemoryAction: nextActions[0] ?? 'Use mem checkpoint --content <status> --apply --json after meaningful progress.',
|
|
905
|
+
sourceMemoryIds: ranked.map((entry) => memoryEntryId(entry)).filter((entry) => Boolean(entry)),
|
|
906
|
+
};
|
|
907
|
+
return deep ? baseResult : applyLightBriefBudget(baseResult);
|
|
908
|
+
}
|
|
909
|
+
function buildRepoFirstMemoryBriefEnvelope(entries, resolvedContext, options, params = {}) {
|
|
910
|
+
return buildEnvelope({
|
|
911
|
+
command: 'memory.brief',
|
|
912
|
+
toolId: REPO_FIRST_MEMORY_TOOL_ID,
|
|
913
|
+
resolvedContext: buildResolvedContextRecord(resolvedContext),
|
|
914
|
+
input: compactPayload({
|
|
915
|
+
subject: options.subject,
|
|
916
|
+
id: options.id,
|
|
917
|
+
query: resolveMemoryQuery(options),
|
|
918
|
+
repoFirst: true,
|
|
919
|
+
readOnly: params.readOnly === true ? true : undefined,
|
|
920
|
+
}),
|
|
921
|
+
result: buildFileBasedBrief(entries, resolvedContext, options),
|
|
922
|
+
});
|
|
923
|
+
}
|
|
924
|
+
export async function buildReadOnlyRepoFirstMemoryBrief(options = {}) {
|
|
925
|
+
const resolvedContext = await resolveMemoryContext(options, { requireScope: true });
|
|
926
|
+
const entries = await loadFileBasedEntries(resolvedContext);
|
|
927
|
+
return buildRepoFirstMemoryBriefEnvelope(entries, resolvedContext, { ...options, depth: options.depth ?? 'light' }, { readOnly: true });
|
|
928
|
+
}
|
|
929
|
+
export async function buildReadOnlyRepoFirstMemoryBriefFromContext(context, options = {}) {
|
|
930
|
+
const resolvedContext = context;
|
|
931
|
+
const entries = await loadFileBasedEntries(resolvedContext);
|
|
932
|
+
return buildRepoFirstMemoryBriefEnvelope(entries, resolvedContext, { ...options, depth: options.depth ?? 'light' }, { readOnly: true });
|
|
933
|
+
}
|
|
934
|
+
function buildBriefFromHostedPacks(params) {
|
|
935
|
+
const resume = isRecord(params.resumePack) && isRecord(params.resumePack.data)
|
|
936
|
+
? params.resumePack.data
|
|
937
|
+
: params.resumePack;
|
|
938
|
+
const synopsis = isRecord(params.synopsis) && isRecord(params.synopsis.data)
|
|
939
|
+
? params.synopsis.data
|
|
940
|
+
: params.synopsis;
|
|
941
|
+
const relatedMemory = isRecord(resume) && Array.isArray(resume.relatedMemory) ? resume.relatedMemory : [];
|
|
942
|
+
const bootstrapGuidance = isRecord(resume) && Array.isArray(resume.bootstrapGuidance) ? resume.bootstrapGuidance : [];
|
|
943
|
+
return {
|
|
944
|
+
readStrategy: 'hosted-brief',
|
|
945
|
+
generatedAt: new Date().toISOString(),
|
|
946
|
+
subject: isRecord(resume) ? resume.subject : undefined,
|
|
947
|
+
currentSynopsis: synopsis,
|
|
948
|
+
subjectResume: compactPayload({
|
|
949
|
+
resumeSummary: isRecord(resume) ? normalizeNonEmpty(resume.resumeSummary) : undefined,
|
|
950
|
+
depth: isRecord(resume) ? normalizeNonEmpty(resume.depth) : undefined,
|
|
951
|
+
relatedCount: relatedMemory.length,
|
|
952
|
+
}),
|
|
953
|
+
stickyRules: bootstrapGuidance,
|
|
954
|
+
similarWork: relatedMemory,
|
|
955
|
+
recommendedNextReads: [],
|
|
956
|
+
memoryGaps: relatedMemory.length === 0 ? ['No related hosted memory was returned for this subject/query.'] : [],
|
|
957
|
+
nextMemoryAction: 'Use mem checkpoint --content <status> --apply --json after meaningful progress.',
|
|
958
|
+
};
|
|
959
|
+
}
|
|
960
|
+
function unwrapResultData(result) {
|
|
961
|
+
if (isRecord(result) && Object.prototype.hasOwnProperty.call(result, 'data')) {
|
|
962
|
+
return result.data;
|
|
963
|
+
}
|
|
964
|
+
return result;
|
|
965
|
+
}
|
|
966
|
+
async function resolveMemoryContext(options, params = {}) {
|
|
967
|
+
const resolved = await resolveProjectBindingContext(options, {
|
|
968
|
+
requireProject: params.requireScope === true,
|
|
969
|
+
});
|
|
970
|
+
const scopeId = normalizeNonEmpty(options.scopeId) ?? resolveOwnerScopeIdFromBinding(resolved);
|
|
971
|
+
if (params.requireScope === true && !scopeId && !normalizeNonEmpty(resolved.projectId)) {
|
|
972
|
+
throw new Error('Memory context could not be resolved. Provide --project-id/--project-name. `--scope-id` remains a legacy/internal alias.');
|
|
973
|
+
}
|
|
974
|
+
return { ...resolved, scopeId };
|
|
975
|
+
}
|
|
976
|
+
function normalizeMemoryDocRef(value) {
|
|
977
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
978
|
+
return null;
|
|
979
|
+
const record = value;
|
|
980
|
+
const pageNumberRaw = record.pageNumber;
|
|
981
|
+
const pageNumber = typeof pageNumberRaw === 'number'
|
|
982
|
+
? Math.trunc(pageNumberRaw)
|
|
983
|
+
: typeof pageNumberRaw === 'string' && pageNumberRaw.trim()
|
|
984
|
+
? Number.parseInt(pageNumberRaw, 10)
|
|
985
|
+
: undefined;
|
|
986
|
+
const normalized = compactPayload({
|
|
987
|
+
kind: normalizeNonEmpty(record.kind),
|
|
988
|
+
uri: normalizeNonEmpty(record.uri),
|
|
989
|
+
resourceId: normalizeNonEmpty(record.resourceId),
|
|
990
|
+
ref: normalizeNonEmpty(record.ref),
|
|
991
|
+
documentVersionId: normalizeNonEmpty(record.documentVersionId),
|
|
992
|
+
sectionId: normalizeNonEmpty(record.sectionId),
|
|
993
|
+
pageVersionId: normalizeNonEmpty(record.pageVersionId),
|
|
994
|
+
pageNumber: Number.isInteger(pageNumber) ? pageNumber : undefined,
|
|
995
|
+
target: normalizeNonEmpty(record.target),
|
|
996
|
+
locale: normalizeNonEmpty(record.locale),
|
|
997
|
+
fallbackLocale: normalizeNonEmpty(record.fallbackLocale),
|
|
998
|
+
});
|
|
999
|
+
return Object.keys(normalized).length > 0 ? normalized : null;
|
|
1000
|
+
}
|
|
1001
|
+
function getRecommendedDocRefs(resumePack) {
|
|
1002
|
+
const payload = isRecord(resumePack) && isRecord(resumePack.data)
|
|
1003
|
+
? resumePack.data
|
|
1004
|
+
: resumePack;
|
|
1005
|
+
if (!payload || typeof payload !== 'object' || Array.isArray(payload))
|
|
1006
|
+
return [];
|
|
1007
|
+
const refs = Array.isArray(payload.recommendedRefs)
|
|
1008
|
+
? payload.recommendedRefs
|
|
1009
|
+
: [];
|
|
1010
|
+
return refs
|
|
1011
|
+
.map((entry) => normalizeMemoryDocRef(entry))
|
|
1012
|
+
.filter((entry) => Boolean(entry))
|
|
1013
|
+
.filter((entry) => entry.kind === 'doc' || Boolean(entry.documentVersionId) || Boolean(entry.uri));
|
|
1014
|
+
}
|
|
1015
|
+
function toPositiveInteger(value, label) {
|
|
1016
|
+
if (value === undefined || value === null || value === '')
|
|
1017
|
+
return undefined;
|
|
1018
|
+
const parsed = typeof value === 'number' ? value : Number.parseInt(String(value), 10);
|
|
1019
|
+
if (!Number.isInteger(parsed) || parsed <= 0) {
|
|
1020
|
+
throw new Error(`${label} must be a positive integer.`);
|
|
1021
|
+
}
|
|
1022
|
+
return parsed;
|
|
1023
|
+
}
|
|
1024
|
+
function selectRecommendedDocRef(refs, refIndex) {
|
|
1025
|
+
const index = toPositiveInteger(refIndex, 'refIndex') ?? 1;
|
|
1026
|
+
const selected = refs[index - 1];
|
|
1027
|
+
if (!selected) {
|
|
1028
|
+
throw new Error(`Recommended doc ref #${index} was not found.`);
|
|
1029
|
+
}
|
|
1030
|
+
return selected;
|
|
1031
|
+
}
|
|
1032
|
+
function resolveDocVersionIdFromRef(ref, explicit) {
|
|
1033
|
+
const documentVersionId = normalizeNonEmpty(explicit) ?? normalizeNonEmpty(ref.documentVersionId);
|
|
1034
|
+
if (!documentVersionId) {
|
|
1035
|
+
throw new Error('Selected recommended ref does not include documentVersionId. Write richer doc refs with documentVersionId to use memory -> docman shortcuts.');
|
|
1036
|
+
}
|
|
1037
|
+
return documentVersionId;
|
|
1038
|
+
}
|
|
1039
|
+
function appendQuery(path, query) {
|
|
1040
|
+
const params = new URLSearchParams();
|
|
1041
|
+
for (const [key, value] of Object.entries(query)) {
|
|
1042
|
+
if (value === undefined || value === null)
|
|
1043
|
+
continue;
|
|
1044
|
+
params.append(key, String(value));
|
|
1045
|
+
}
|
|
1046
|
+
const queryString = params.toString();
|
|
1047
|
+
return queryString ? `${path}?${queryString}` : path;
|
|
1048
|
+
}
|
|
1049
|
+
async function callDocmanVersionRouteFromMemory(apiState, options, resolvedContext, params) {
|
|
1050
|
+
const headers = await buildAgentContextHeaders(buildGatewayOptions(options, resolvedContext));
|
|
1051
|
+
let path = `/api/docman/document-versions/${encodeURIComponent(params.documentVersionId)}/${params.suffix}`;
|
|
1052
|
+
if (params.query)
|
|
1053
|
+
path = appendQuery(path, params.query);
|
|
1054
|
+
return apiState.client.fetchJson(path, {
|
|
1055
|
+
method: params.method,
|
|
1056
|
+
headers,
|
|
1057
|
+
body: params.method === 'GET' ? undefined : params.body,
|
|
1058
|
+
timeoutMs: options.timeoutMs,
|
|
1059
|
+
});
|
|
1060
|
+
}
|
|
1061
|
+
function normalizeEnsureMode(value) {
|
|
1062
|
+
const normalized = normalizeNonEmpty(value)?.toLowerCase();
|
|
1063
|
+
if (!normalized || normalized === 'none')
|
|
1064
|
+
return 'none';
|
|
1065
|
+
if (normalized === 'index' || normalized === 'summary')
|
|
1066
|
+
return normalized;
|
|
1067
|
+
throw new Error('Invalid --ensure. Expected one of: none, index, summary.');
|
|
1068
|
+
}
|
|
1069
|
+
async function maybeEnsureDocmanReadState(apiState, options, resolvedContext, params) {
|
|
1070
|
+
if (params.ensure === 'none')
|
|
1071
|
+
return;
|
|
1072
|
+
await callDocmanVersionRouteFromMemory(apiState, options, resolvedContext, {
|
|
1073
|
+
documentVersionId: params.documentVersionId,
|
|
1074
|
+
suffix: params.ensure === 'index' ? 'index' : 'summaries',
|
|
1075
|
+
method: 'POST',
|
|
1076
|
+
body: compactPayload({
|
|
1077
|
+
locale: normalizeNonEmpty(params.locale),
|
|
1078
|
+
fallbackLocale: normalizeNonEmpty(params.fallbackLocale),
|
|
1079
|
+
}),
|
|
1080
|
+
});
|
|
1081
|
+
}
|
|
1082
|
+
async function fetchMemoryResumePack(apiState, options, resolvedContext) {
|
|
1083
|
+
const input = compactPayload({
|
|
1084
|
+
filter: compactPayload({
|
|
1085
|
+
scopeId: resolvedContext.scopeId,
|
|
1086
|
+
scopeResolution: 'cascade',
|
|
1087
|
+
projectId: resolvedContext.projectId,
|
|
1088
|
+
}),
|
|
1089
|
+
retrieval: buildRetrievalInput(options, resolvedContext, {
|
|
1090
|
+
defaultQuery: normalizeMemorySubject(options.subject)?.defaultQuery,
|
|
1091
|
+
}),
|
|
1092
|
+
options: compactPayload({
|
|
1093
|
+
depth: options.depth === 'deep' ? 'deep' : 'light',
|
|
1094
|
+
limit: toNonNegativeInteger(options.limit, 'limit'),
|
|
1095
|
+
}),
|
|
1096
|
+
});
|
|
1097
|
+
const payload = await invokeHostedToolWithApiState(apiState, {
|
|
1098
|
+
...buildGatewayOptions(options, resolvedContext),
|
|
1099
|
+
tenantId: options.tenantId,
|
|
1100
|
+
locale: options.locale,
|
|
1101
|
+
fallbackLocale: options.fallbackLocale,
|
|
1102
|
+
timeoutMs: options.timeoutMs,
|
|
1103
|
+
apiBaseUrl: options.apiBaseUrl,
|
|
1104
|
+
accessToken: options.accessToken,
|
|
1105
|
+
refreshToken: options.refreshToken,
|
|
1106
|
+
toolId: 'agentspace.memory-item.build-resume-pack',
|
|
1107
|
+
input,
|
|
1108
|
+
});
|
|
1109
|
+
return { input, result: unwrapHostedToolResult(payload) };
|
|
1110
|
+
}
|
|
1111
|
+
async function fetchMemorySynopsis(apiState, options, resolvedContext) {
|
|
1112
|
+
const input = compactPayload({
|
|
1113
|
+
filter: compactPayload({
|
|
1114
|
+
scopeId: resolvedContext.scopeId,
|
|
1115
|
+
scopeResolution: 'cascade',
|
|
1116
|
+
projectId: resolvedContext.projectId,
|
|
1117
|
+
}),
|
|
1118
|
+
retrieval: buildRetrievalInput(options, resolvedContext, {
|
|
1119
|
+
defaultQuery: normalizeMemorySubject(options.subject)?.defaultQuery,
|
|
1120
|
+
}),
|
|
1121
|
+
options: compactPayload({
|
|
1122
|
+
limit: toNonNegativeInteger(options.limit, 'limit'),
|
|
1123
|
+
}),
|
|
1124
|
+
});
|
|
1125
|
+
const payload = await invokeHostedToolWithApiState(apiState, {
|
|
1126
|
+
...buildGatewayOptions(options, resolvedContext),
|
|
1127
|
+
tenantId: options.tenantId,
|
|
1128
|
+
locale: options.locale,
|
|
1129
|
+
fallbackLocale: options.fallbackLocale,
|
|
1130
|
+
timeoutMs: options.timeoutMs,
|
|
1131
|
+
apiBaseUrl: options.apiBaseUrl,
|
|
1132
|
+
accessToken: options.accessToken,
|
|
1133
|
+
refreshToken: options.refreshToken,
|
|
1134
|
+
toolId: 'agentspace.memory-item.build-synopsis',
|
|
1135
|
+
input,
|
|
1136
|
+
});
|
|
1137
|
+
return { input, result: unwrapHostedToolResult(payload) };
|
|
1138
|
+
}
|
|
1139
|
+
function buildGatewayOptions(options, resolvedContext) {
|
|
1140
|
+
return {
|
|
1141
|
+
...options,
|
|
1142
|
+
...preferProjectNameBinding(resolvedContext),
|
|
1143
|
+
};
|
|
1144
|
+
}
|
|
1145
|
+
async function hydrateProjectScopeContext(apiState, options, resolvedContext) {
|
|
1146
|
+
const projectId = normalizeNonEmpty(resolvedContext.projectId);
|
|
1147
|
+
if (!projectId) {
|
|
1148
|
+
return resolvedContext;
|
|
1149
|
+
}
|
|
1150
|
+
if (normalizeNonEmpty(resolvedContext.scopeId) === projectId && normalizeNonEmpty(resolvedContext.projectName)) {
|
|
1151
|
+
return resolvedContext;
|
|
1152
|
+
}
|
|
1153
|
+
const gatewayOptions = {
|
|
1154
|
+
...buildGatewayOptions(options, resolvedContext),
|
|
1155
|
+
toolId: 'agentspace.project.get-by-id',
|
|
1156
|
+
input: { id: projectId },
|
|
1157
|
+
};
|
|
1158
|
+
try {
|
|
1159
|
+
const payload = await invokeHostedToolWithApiState(apiState, gatewayOptions);
|
|
1160
|
+
const result = unwrapHostedToolResult(payload);
|
|
1161
|
+
const project = unwrapResultData(result);
|
|
1162
|
+
if (!isRecord(project)) {
|
|
1163
|
+
return resolvedContext;
|
|
1164
|
+
}
|
|
1165
|
+
const scopeId = resolveOwnerScopeIdFromProjectRecord(project, resolvedContext.scopeId ?? projectId);
|
|
1166
|
+
const projectName = normalizeNonEmpty(project.name) ?? resolvedContext.projectName;
|
|
1167
|
+
return {
|
|
1168
|
+
...resolvedContext,
|
|
1169
|
+
scopeId: scopeId ?? resolvedContext.scopeId,
|
|
1170
|
+
projectName,
|
|
1171
|
+
};
|
|
1172
|
+
}
|
|
1173
|
+
catch {
|
|
1174
|
+
return resolvedContext;
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
function buildResolvedContextRecord(context) {
|
|
1178
|
+
return compactPayload({
|
|
1179
|
+
repoRoot: context.repoRoot,
|
|
1180
|
+
configPath: context.configPath,
|
|
1181
|
+
configFound: context.configFound,
|
|
1182
|
+
scopeId: context.scopeId,
|
|
1183
|
+
projectId: context.projectId,
|
|
1184
|
+
projectName: context.projectName,
|
|
1185
|
+
});
|
|
1186
|
+
}
|
|
1187
|
+
function normalizeHostedCompatibleMemoryKind(kind) {
|
|
1188
|
+
const value = normalizeNonEmpty(kind);
|
|
1189
|
+
if (value === 'summary')
|
|
1190
|
+
return 'checkpoint';
|
|
1191
|
+
return value;
|
|
1192
|
+
}
|
|
1193
|
+
function normalizeCheckpointAsOption(value) {
|
|
1194
|
+
const normalized = normalizeNonEmpty(value)?.toLowerCase();
|
|
1195
|
+
if (!normalized || normalized === 'status')
|
|
1196
|
+
return 'status';
|
|
1197
|
+
if (normalized === 'decision' || normalized === 'blocker' || normalized === 'milestone')
|
|
1198
|
+
return normalized;
|
|
1199
|
+
throw new Error('Invalid --as. Expected one of: status, decision, blocker, milestone.');
|
|
1200
|
+
}
|
|
1201
|
+
function buildCheckpointWriteOptions(options) {
|
|
1202
|
+
const checkpointAs = normalizeCheckpointAsOption(options.as);
|
|
1203
|
+
const mode = checkpointAs === 'decision'
|
|
1204
|
+
? 'decision'
|
|
1205
|
+
: checkpointAs === 'blocker'
|
|
1206
|
+
? 'blocker'
|
|
1207
|
+
: checkpointAs === 'status'
|
|
1208
|
+
? 'resume'
|
|
1209
|
+
: undefined;
|
|
1210
|
+
const kind = checkpointAs === 'milestone' ? 'checkpoint' : undefined;
|
|
1211
|
+
return {
|
|
1212
|
+
...options,
|
|
1213
|
+
mode: options.mode ?? mode,
|
|
1214
|
+
kind: normalizeNonEmpty(options.kind) ?? kind,
|
|
1215
|
+
durability: normalizeMemoryDurability(options.durability) ?? 'short',
|
|
1216
|
+
subject: normalizeNonEmpty(options.subject) ?? 'project',
|
|
1217
|
+
purpose: uniqueStrings([...toStringArray(options.purpose), 'checkpoint', checkpointAs]),
|
|
1218
|
+
status: uniqueStrings([...toStringArray(options.status), 'active']),
|
|
1219
|
+
tag: uniqueStrings([...toStringArray(options.tag), 'memory:checkpoint', `checkpoint:${checkpointAs}`]),
|
|
1220
|
+
checkpointAs,
|
|
1221
|
+
envelopeCommand: 'memory.checkpoint',
|
|
1222
|
+
};
|
|
1223
|
+
}
|
|
1224
|
+
function buildSummaryWriteOptions(options) {
|
|
1225
|
+
const closeout = options.closeout === true;
|
|
1226
|
+
const targetDurability = normalizeMemoryDurability(options.durability) ?? 'short';
|
|
1227
|
+
const diagnosticWarning = closeout && targetDurability === 'short'
|
|
1228
|
+
? ['--closeout without --durability durable writes a short session-closeout summary. Use --closeout --durability durable --confirm for durable closeout memory.']
|
|
1229
|
+
: [];
|
|
1230
|
+
return {
|
|
1231
|
+
...options,
|
|
1232
|
+
mode: options.mode ?? (closeout ? 'closeout' : 'resume'),
|
|
1233
|
+
kind: normalizeHostedCompatibleMemoryKind(options.kind) ?? (closeout ? 'closeout' : 'resume'),
|
|
1234
|
+
durability: targetDurability,
|
|
1235
|
+
subject: normalizeNonEmpty(options.subject) ?? 'project',
|
|
1236
|
+
purpose: uniqueStrings([...toStringArray(options.purpose), 'summary', closeout ? 'closeout' : 'session']),
|
|
1237
|
+
status: uniqueStrings([...toStringArray(options.status), closeout ? 'closed' : 'active']),
|
|
1238
|
+
tag: uniqueStrings([...toStringArray(options.tag), 'memory:summary', closeout ? 'summary:closeout' : 'summary:session']),
|
|
1239
|
+
summaryType: closeout ? 'closeout' : 'session',
|
|
1240
|
+
diagnosticWarning: uniqueStrings([...toStringArray(options.diagnosticWarning), ...diagnosticWarning]),
|
|
1241
|
+
envelopeCommand: 'memory.summary',
|
|
1242
|
+
};
|
|
1243
|
+
}
|
|
1244
|
+
function assertSummaryDurabilityGuard(options, writeOptions) {
|
|
1245
|
+
const durability = normalizeMemoryDurability(writeOptions.durability);
|
|
1246
|
+
if (durability === 'durable' && (options.closeout !== true || options.confirm !== true)) {
|
|
1247
|
+
throw new Error('Durable summary requires --closeout --durability durable --confirm.');
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
function buildRetrievalInput(options, context, params = {}) {
|
|
1251
|
+
const subjectConfig = normalizeMemorySubject(options.subject);
|
|
1252
|
+
const linked = buildLinkedIds(options);
|
|
1253
|
+
const subjectId = buildSubjectId(subjectConfig, options, context);
|
|
1254
|
+
const subjectLabel = buildSubjectLabel(options);
|
|
1255
|
+
const defaults = applySubjectDefaults(subjectConfig, subjectId, linked);
|
|
1256
|
+
return compactPayload({
|
|
1257
|
+
query: resolveMemoryQuery(options) ?? params.defaultQuery,
|
|
1258
|
+
goal: normalizeNonEmpty(options.goal),
|
|
1259
|
+
runtimeProfile: normalizeNonEmpty(options.runtimeProfile),
|
|
1260
|
+
workflowId: normalizeNonEmpty(options.workflowId),
|
|
1261
|
+
stepId: normalizeNonEmpty(options.stepId),
|
|
1262
|
+
subject: compactPayload({
|
|
1263
|
+
type: normalizeNonEmpty(options.subjectType) ?? defaults.subjectType,
|
|
1264
|
+
id: normalizeNonEmpty(options.subjectId) ?? subjectId,
|
|
1265
|
+
label: normalizeNonEmpty(options.subjectLabel) ?? subjectLabel,
|
|
1266
|
+
}),
|
|
1267
|
+
tags: uniqueStrings([
|
|
1268
|
+
...toStringArray(options.tag),
|
|
1269
|
+
...buildClassificationTags(options),
|
|
1270
|
+
]),
|
|
1271
|
+
sourceTypes: toStringArray(options.sourceType).length > 0 ? toStringArray(options.sourceType) : defaults.sourceTypes,
|
|
1272
|
+
sourceIds: toStringArray(options.sourceId).length > 0 ? toStringArray(options.sourceId) : defaults.sourceIds,
|
|
1273
|
+
candidateLimit: toNonNegativeInteger(options.candidateLimit, 'candidateLimit'),
|
|
1274
|
+
});
|
|
1275
|
+
}
|
|
1276
|
+
export async function runMemorySearch(options = {}) {
|
|
1277
|
+
let resolvedContext;
|
|
1278
|
+
let apiState;
|
|
1279
|
+
try {
|
|
1280
|
+
apiState = await requireApiState(options);
|
|
1281
|
+
if (!apiState)
|
|
1282
|
+
return;
|
|
1283
|
+
resolvedContext = await resolveMemoryContext(options, { requireScope: true });
|
|
1284
|
+
resolvedContext = await hydrateProjectScopeContext(apiState, options, resolvedContext);
|
|
1285
|
+
}
|
|
1286
|
+
catch (error) {
|
|
1287
|
+
logError(error instanceof Error ? error.message : String(error));
|
|
1288
|
+
process.exitCode = 1;
|
|
1289
|
+
return;
|
|
1290
|
+
}
|
|
1291
|
+
try {
|
|
1292
|
+
const input = compactPayload({
|
|
1293
|
+
filter: compactPayload({
|
|
1294
|
+
scopeId: resolvedContext.scopeId,
|
|
1295
|
+
scopeResolution: 'cascade',
|
|
1296
|
+
projectId: resolvedContext.projectId,
|
|
1297
|
+
}),
|
|
1298
|
+
retrieval: buildRetrievalInput(options, resolvedContext),
|
|
1299
|
+
options: compactPayload({
|
|
1300
|
+
limit: toNonNegativeInteger(options.limit, 'limit'),
|
|
1301
|
+
}),
|
|
1302
|
+
});
|
|
1303
|
+
const payload = await invokeHostedToolWithApiState(apiState, {
|
|
1304
|
+
...buildGatewayOptions(options, resolvedContext),
|
|
1305
|
+
tenantId: options.tenantId,
|
|
1306
|
+
locale: options.locale,
|
|
1307
|
+
fallbackLocale: options.fallbackLocale,
|
|
1308
|
+
timeoutMs: options.timeoutMs,
|
|
1309
|
+
apiBaseUrl: options.apiBaseUrl,
|
|
1310
|
+
accessToken: options.accessToken,
|
|
1311
|
+
refreshToken: options.refreshToken,
|
|
1312
|
+
toolId: 'agentspace.memory-item.search-memory-items',
|
|
1313
|
+
input,
|
|
1314
|
+
});
|
|
1315
|
+
const result = unwrapHostedToolResult(payload);
|
|
1316
|
+
const filteredResult = options.strictClassification === true ? filterHostedMemoryResult(result, options) : result;
|
|
1317
|
+
if (options.json) {
|
|
1318
|
+
console.log(JSON.stringify(buildEnvelope({
|
|
1319
|
+
command: 'memory.search',
|
|
1320
|
+
toolId: 'agentspace.memory-item.search-memory-items',
|
|
1321
|
+
resolvedContext: buildResolvedContextRecord(resolvedContext),
|
|
1322
|
+
input,
|
|
1323
|
+
result: filteredResult,
|
|
1324
|
+
}), null, 2));
|
|
1325
|
+
return;
|
|
1326
|
+
}
|
|
1327
|
+
logSuccess('Memory search completed.');
|
|
1328
|
+
console.log(JSON.stringify(filteredResult, null, 2));
|
|
1329
|
+
}
|
|
1330
|
+
catch (error) {
|
|
1331
|
+
emitMemoryCommandError({
|
|
1332
|
+
options,
|
|
1333
|
+
command: 'memory.search',
|
|
1334
|
+
toolId: 'agentspace.memory-item.search-memory-items',
|
|
1335
|
+
resolvedContext,
|
|
1336
|
+
error,
|
|
1337
|
+
});
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
export async function runMemoryList(options = {}) {
|
|
1341
|
+
let resolvedContext;
|
|
1342
|
+
let apiState;
|
|
1343
|
+
try {
|
|
1344
|
+
apiState = await requireApiState(options);
|
|
1345
|
+
if (!apiState)
|
|
1346
|
+
return;
|
|
1347
|
+
resolvedContext = await resolveMemoryContext(options, { requireScope: true });
|
|
1348
|
+
resolvedContext = await hydrateProjectScopeContext(apiState, options, resolvedContext);
|
|
1349
|
+
}
|
|
1350
|
+
catch (error) {
|
|
1351
|
+
logError(error instanceof Error ? error.message : String(error));
|
|
1352
|
+
process.exitCode = 1;
|
|
1353
|
+
return;
|
|
1354
|
+
}
|
|
1355
|
+
try {
|
|
1356
|
+
const subjectConfig = normalizeMemorySubject(options.subject);
|
|
1357
|
+
const linked = buildLinkedIds(options);
|
|
1358
|
+
const subjectId = buildSubjectId(subjectConfig, options, resolvedContext);
|
|
1359
|
+
const defaults = applySubjectDefaults(subjectConfig, subjectId, linked);
|
|
1360
|
+
const input = compactPayload({
|
|
1361
|
+
filter: compactPayload({
|
|
1362
|
+
scopeId: resolvedContext.scopeId,
|
|
1363
|
+
scopeResolution: 'cascade',
|
|
1364
|
+
kind: normalizeNonEmpty(options.kind),
|
|
1365
|
+
durability: normalizeMemoryDurability(options.durability),
|
|
1366
|
+
sourceType: normalizeNonEmpty(options.sourceType) ?? normalizeNonEmpty(options.subjectType) ?? defaults.sourceType,
|
|
1367
|
+
sourceId: normalizeNonEmpty(options.sourceId) ?? normalizeNonEmpty(options.subjectId) ?? subjectId,
|
|
1368
|
+
}),
|
|
1369
|
+
options: compactPayload({
|
|
1370
|
+
limit: toNonNegativeInteger(options.limit, 'limit'),
|
|
1371
|
+
}),
|
|
1372
|
+
});
|
|
1373
|
+
const payload = await invokeHostedToolWithApiState(apiState, {
|
|
1374
|
+
...buildGatewayOptions(options, resolvedContext),
|
|
1375
|
+
tenantId: options.tenantId,
|
|
1376
|
+
locale: options.locale,
|
|
1377
|
+
fallbackLocale: options.fallbackLocale,
|
|
1378
|
+
timeoutMs: options.timeoutMs,
|
|
1379
|
+
apiBaseUrl: options.apiBaseUrl,
|
|
1380
|
+
accessToken: options.accessToken,
|
|
1381
|
+
refreshToken: options.refreshToken,
|
|
1382
|
+
toolId: 'agentspace.memory-item.list-memory-items',
|
|
1383
|
+
input,
|
|
1384
|
+
});
|
|
1385
|
+
const result = unwrapHostedToolResult(payload);
|
|
1386
|
+
const filteredResult = filterHostedMemoryResult(result, options);
|
|
1387
|
+
if (options.json) {
|
|
1388
|
+
console.log(JSON.stringify(buildEnvelope({
|
|
1389
|
+
command: 'memory.list',
|
|
1390
|
+
toolId: 'agentspace.memory-item.list-memory-items',
|
|
1391
|
+
resolvedContext: buildResolvedContextRecord(resolvedContext),
|
|
1392
|
+
input,
|
|
1393
|
+
result: filteredResult,
|
|
1394
|
+
}), null, 2));
|
|
1395
|
+
return;
|
|
1396
|
+
}
|
|
1397
|
+
logSuccess('Memory list completed.');
|
|
1398
|
+
console.log(JSON.stringify(filteredResult, null, 2));
|
|
1399
|
+
}
|
|
1400
|
+
catch (error) {
|
|
1401
|
+
logError(error instanceof Error ? error.message : String(error));
|
|
1402
|
+
process.exitCode = 1;
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
export async function runMemoryGet(options = {}) {
|
|
1406
|
+
let resolvedContext;
|
|
1407
|
+
let apiState;
|
|
1408
|
+
try {
|
|
1409
|
+
const id = normalizeNonEmpty(options.id);
|
|
1410
|
+
if (!id)
|
|
1411
|
+
throw new Error('Provide --id.');
|
|
1412
|
+
apiState = await requireApiState(options);
|
|
1413
|
+
if (!apiState)
|
|
1414
|
+
return;
|
|
1415
|
+
resolvedContext = await resolveMemoryContext(options, { requireScope: false });
|
|
1416
|
+
resolvedContext = await hydrateProjectScopeContext(apiState, options, resolvedContext);
|
|
1417
|
+
const input = { id };
|
|
1418
|
+
const payload = await invokeHostedToolWithApiState(apiState, {
|
|
1419
|
+
...buildGatewayOptions(options, resolvedContext),
|
|
1420
|
+
tenantId: options.tenantId,
|
|
1421
|
+
locale: options.locale,
|
|
1422
|
+
fallbackLocale: options.fallbackLocale,
|
|
1423
|
+
timeoutMs: options.timeoutMs,
|
|
1424
|
+
apiBaseUrl: options.apiBaseUrl,
|
|
1425
|
+
accessToken: options.accessToken,
|
|
1426
|
+
refreshToken: options.refreshToken,
|
|
1427
|
+
toolId: 'agentspace.memory-item.get-by-id',
|
|
1428
|
+
input,
|
|
1429
|
+
});
|
|
1430
|
+
const result = unwrapHostedToolResult(payload);
|
|
1431
|
+
if (options.json) {
|
|
1432
|
+
console.log(JSON.stringify(buildEnvelope({
|
|
1433
|
+
command: 'memory.get',
|
|
1434
|
+
toolId: 'agentspace.memory-item.get-by-id',
|
|
1435
|
+
resolvedContext: buildResolvedContextRecord(resolvedContext),
|
|
1436
|
+
input,
|
|
1437
|
+
result,
|
|
1438
|
+
}), null, 2));
|
|
1439
|
+
return;
|
|
1440
|
+
}
|
|
1441
|
+
logSuccess('Memory item loaded.');
|
|
1442
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1443
|
+
}
|
|
1444
|
+
catch (error) {
|
|
1445
|
+
logError(error instanceof Error ? error.message : String(error));
|
|
1446
|
+
process.exitCode = 1;
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1449
|
+
export async function runMemoryResume(options = {}) {
|
|
1450
|
+
let resolvedContext;
|
|
1451
|
+
let apiState;
|
|
1452
|
+
try {
|
|
1453
|
+
apiState = await requireApiState(options);
|
|
1454
|
+
if (!apiState)
|
|
1455
|
+
return;
|
|
1456
|
+
resolvedContext = await resolveMemoryContext(options, { requireScope: true });
|
|
1457
|
+
resolvedContext = await hydrateProjectScopeContext(apiState, options, resolvedContext);
|
|
1458
|
+
}
|
|
1459
|
+
catch (error) {
|
|
1460
|
+
logError(error instanceof Error ? error.message : String(error));
|
|
1461
|
+
process.exitCode = 1;
|
|
1462
|
+
return;
|
|
1463
|
+
}
|
|
1464
|
+
try {
|
|
1465
|
+
const { input, result } = await fetchMemoryResumePack(apiState, options, resolvedContext);
|
|
1466
|
+
if (options.json) {
|
|
1467
|
+
console.log(JSON.stringify(buildEnvelope({
|
|
1468
|
+
command: 'memory.resume',
|
|
1469
|
+
toolId: 'agentspace.memory-item.build-resume-pack',
|
|
1470
|
+
resolvedContext: buildResolvedContextRecord(resolvedContext),
|
|
1471
|
+
input,
|
|
1472
|
+
result,
|
|
1473
|
+
}), null, 2));
|
|
1474
|
+
return;
|
|
1475
|
+
}
|
|
1476
|
+
logSuccess('Resume pack built.');
|
|
1477
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1478
|
+
}
|
|
1479
|
+
catch (error) {
|
|
1480
|
+
logError(error instanceof Error ? error.message : String(error));
|
|
1481
|
+
process.exitCode = 1;
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
export async function runMemorySynopsis(options = {}) {
|
|
1485
|
+
let resolvedContext;
|
|
1486
|
+
let apiState;
|
|
1487
|
+
try {
|
|
1488
|
+
apiState = await requireApiState(options);
|
|
1489
|
+
if (!apiState)
|
|
1490
|
+
return;
|
|
1491
|
+
resolvedContext = await resolveMemoryContext(options, { requireScope: true });
|
|
1492
|
+
resolvedContext = await hydrateProjectScopeContext(apiState, options, resolvedContext);
|
|
1493
|
+
}
|
|
1494
|
+
catch (error) {
|
|
1495
|
+
logError(error instanceof Error ? error.message : String(error));
|
|
1496
|
+
process.exitCode = 1;
|
|
1497
|
+
return;
|
|
1498
|
+
}
|
|
1499
|
+
try {
|
|
1500
|
+
const { input, result } = await fetchMemorySynopsis(apiState, options, resolvedContext);
|
|
1501
|
+
if (options.json) {
|
|
1502
|
+
console.log(JSON.stringify(buildEnvelope({
|
|
1503
|
+
command: 'memory.synopsis',
|
|
1504
|
+
toolId: 'agentspace.memory-item.build-synopsis',
|
|
1505
|
+
resolvedContext: buildResolvedContextRecord(resolvedContext),
|
|
1506
|
+
input,
|
|
1507
|
+
result,
|
|
1508
|
+
}), null, 2));
|
|
1509
|
+
return;
|
|
1510
|
+
}
|
|
1511
|
+
logSuccess('Synopsis built.');
|
|
1512
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1513
|
+
}
|
|
1514
|
+
catch (error) {
|
|
1515
|
+
logError(error instanceof Error ? error.message : String(error));
|
|
1516
|
+
process.exitCode = 1;
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
export async function runMemoryBrief(options = {}) {
|
|
1520
|
+
let resolvedContext;
|
|
1521
|
+
let apiState;
|
|
1522
|
+
try {
|
|
1523
|
+
apiState = await requireApiState(options);
|
|
1524
|
+
if (!apiState)
|
|
1525
|
+
return;
|
|
1526
|
+
resolvedContext = await resolveMemoryContext(options, { requireScope: true });
|
|
1527
|
+
resolvedContext = await hydrateProjectScopeContext(apiState, options, resolvedContext);
|
|
1528
|
+
const resume = await fetchMemoryResumePack(apiState, options, resolvedContext);
|
|
1529
|
+
const synopsis = await fetchMemorySynopsis(apiState, options, resolvedContext);
|
|
1530
|
+
const result = buildBriefFromHostedPacks({
|
|
1531
|
+
resumePack: resume.result,
|
|
1532
|
+
synopsis: synopsis.result,
|
|
1533
|
+
});
|
|
1534
|
+
if (options.json) {
|
|
1535
|
+
console.log(JSON.stringify(buildEnvelope({
|
|
1536
|
+
command: 'memory.brief',
|
|
1537
|
+
toolId: 'agentspace.memory-item.build-resume-pack',
|
|
1538
|
+
resolvedContext: buildResolvedContextRecord(resolvedContext),
|
|
1539
|
+
input: compactPayload({
|
|
1540
|
+
resume: resume.input,
|
|
1541
|
+
synopsis: synopsis.input,
|
|
1542
|
+
}),
|
|
1543
|
+
result,
|
|
1544
|
+
}), null, 2));
|
|
1545
|
+
return;
|
|
1546
|
+
}
|
|
1547
|
+
logSuccess('Memory brief built.');
|
|
1548
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1549
|
+
}
|
|
1550
|
+
catch (error) {
|
|
1551
|
+
emitMemoryCommandError({
|
|
1552
|
+
options,
|
|
1553
|
+
command: 'memory.brief',
|
|
1554
|
+
toolId: 'agentspace.memory-item.build-resume-pack',
|
|
1555
|
+
resolvedContext,
|
|
1556
|
+
error,
|
|
1557
|
+
});
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
export async function runMemoryDocRefs(options = {}) {
|
|
1561
|
+
let resolvedContext;
|
|
1562
|
+
let apiState;
|
|
1563
|
+
try {
|
|
1564
|
+
apiState = await requireApiState(options);
|
|
1565
|
+
if (!apiState)
|
|
1566
|
+
return;
|
|
1567
|
+
resolvedContext = await resolveMemoryContext(options, { requireScope: true });
|
|
1568
|
+
resolvedContext = await hydrateProjectScopeContext(apiState, options, resolvedContext);
|
|
1569
|
+
}
|
|
1570
|
+
catch (error) {
|
|
1571
|
+
logError(error instanceof Error ? error.message : String(error));
|
|
1572
|
+
process.exitCode = 1;
|
|
1573
|
+
return;
|
|
1574
|
+
}
|
|
1575
|
+
try {
|
|
1576
|
+
const { input, result } = await fetchMemoryResumePack(apiState, options, resolvedContext);
|
|
1577
|
+
const refs = getRecommendedDocRefs(result);
|
|
1578
|
+
const response = {
|
|
1579
|
+
refCount: refs.length,
|
|
1580
|
+
refs: refs.map((ref, index) => compactPayload({ index: index + 1, ...ref })),
|
|
1581
|
+
readStrategy: isRecord(result) ? normalizeNonEmpty(result.readStrategy) : undefined,
|
|
1582
|
+
resumeSummary: isRecord(result) ? normalizeNonEmpty(result.resumeSummary) : undefined,
|
|
1583
|
+
};
|
|
1584
|
+
if (options.json) {
|
|
1585
|
+
console.log(JSON.stringify(buildEnvelope({
|
|
1586
|
+
command: 'memory.doc.refs',
|
|
1587
|
+
toolId: 'agentspace.memory-item.build-resume-pack',
|
|
1588
|
+
resolvedContext: buildResolvedContextRecord(resolvedContext),
|
|
1589
|
+
input,
|
|
1590
|
+
result: response,
|
|
1591
|
+
}), null, 2));
|
|
1592
|
+
return;
|
|
1593
|
+
}
|
|
1594
|
+
logSuccess('Recommended Docman refs loaded.');
|
|
1595
|
+
console.log(JSON.stringify(response, null, 2));
|
|
1596
|
+
}
|
|
1597
|
+
catch (error) {
|
|
1598
|
+
logError(error instanceof Error ? error.message : String(error));
|
|
1599
|
+
process.exitCode = 1;
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
export async function runMemoryDocAnswer(options = {}) {
|
|
1603
|
+
let resolvedContext;
|
|
1604
|
+
let apiState;
|
|
1605
|
+
try {
|
|
1606
|
+
const q = normalizeNonEmpty(options.q);
|
|
1607
|
+
if (!q)
|
|
1608
|
+
throw new Error('Provide --q.');
|
|
1609
|
+
apiState = await requireApiState(options);
|
|
1610
|
+
if (!apiState)
|
|
1611
|
+
return;
|
|
1612
|
+
resolvedContext = await resolveMemoryContext(options, { requireScope: true });
|
|
1613
|
+
resolvedContext = await hydrateProjectScopeContext(apiState, options, resolvedContext);
|
|
1614
|
+
}
|
|
1615
|
+
catch (error) {
|
|
1616
|
+
logError(error instanceof Error ? error.message : String(error));
|
|
1617
|
+
process.exitCode = 1;
|
|
1618
|
+
return;
|
|
1619
|
+
}
|
|
1620
|
+
try {
|
|
1621
|
+
const { input: resumeInput, result: resumePack } = await fetchMemoryResumePack(apiState, options, resolvedContext);
|
|
1622
|
+
const refs = getRecommendedDocRefs(resumePack);
|
|
1623
|
+
const selectedRef = selectRecommendedDocRef(refs, options.refIndex);
|
|
1624
|
+
const documentVersionId = resolveDocVersionIdFromRef(selectedRef, options.documentVersionId);
|
|
1625
|
+
const ensure = normalizeEnsureMode(options.ensure);
|
|
1626
|
+
const locale = normalizeNonEmpty(selectedRef.locale) ?? normalizeNonEmpty(options.locale);
|
|
1627
|
+
const fallbackLocale = normalizeNonEmpty(selectedRef.fallbackLocale) ?? normalizeNonEmpty(options.fallbackLocale);
|
|
1628
|
+
await maybeEnsureDocmanReadState(apiState, options, resolvedContext, {
|
|
1629
|
+
ensure,
|
|
1630
|
+
documentVersionId,
|
|
1631
|
+
locale,
|
|
1632
|
+
fallbackLocale,
|
|
1633
|
+
});
|
|
1634
|
+
const q = normalizeNonEmpty(options.q);
|
|
1635
|
+
const result = await callDocmanVersionRouteFromMemory(apiState, options, resolvedContext, {
|
|
1636
|
+
documentVersionId,
|
|
1637
|
+
suffix: 'answer-pack',
|
|
1638
|
+
method: 'GET',
|
|
1639
|
+
query: compactPayload({
|
|
1640
|
+
q,
|
|
1641
|
+
limit: toNonNegativeInteger(options.limit, 'limit'),
|
|
1642
|
+
retrievalStrategy: normalizeNonEmpty(options.retrievalStrategy),
|
|
1643
|
+
locale,
|
|
1644
|
+
fallbackLocale,
|
|
1645
|
+
}),
|
|
1646
|
+
});
|
|
1647
|
+
const envelopeResult = {
|
|
1648
|
+
selectedRef: compactPayload({ index: toPositiveInteger(options.refIndex, 'refIndex') ?? 1, ...selectedRef }),
|
|
1649
|
+
docman: result,
|
|
1650
|
+
};
|
|
1651
|
+
if (options.json) {
|
|
1652
|
+
console.log(JSON.stringify({
|
|
1653
|
+
command: 'memory.doc.answer',
|
|
1654
|
+
surface: '/api/docman/document-versions/:id/answer-pack',
|
|
1655
|
+
resolvedContext: buildResolvedContextRecord(resolvedContext),
|
|
1656
|
+
input: compactPayload({
|
|
1657
|
+
resume: resumeInput,
|
|
1658
|
+
selectedRef: compactPayload({ index: toPositiveInteger(options.refIndex, 'refIndex') ?? 1, ...selectedRef }),
|
|
1659
|
+
documentVersionId,
|
|
1660
|
+
q,
|
|
1661
|
+
limit: toNonNegativeInteger(options.limit, 'limit'),
|
|
1662
|
+
retrievalStrategy: normalizeNonEmpty(options.retrievalStrategy),
|
|
1663
|
+
locale,
|
|
1664
|
+
fallbackLocale,
|
|
1665
|
+
ensure,
|
|
1666
|
+
}),
|
|
1667
|
+
result: envelopeResult,
|
|
1668
|
+
}, null, 2));
|
|
1669
|
+
return;
|
|
1670
|
+
}
|
|
1671
|
+
logSuccess('Docman answer pack loaded from recommended memory refs.');
|
|
1672
|
+
console.log(JSON.stringify(envelopeResult, null, 2));
|
|
1673
|
+
}
|
|
1674
|
+
catch (error) {
|
|
1675
|
+
logError(error instanceof Error ? error.message : String(error));
|
|
1676
|
+
process.exitCode = 1;
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
export async function runMemoryDocSource(options = {}) {
|
|
1680
|
+
let resolvedContext;
|
|
1681
|
+
let apiState;
|
|
1682
|
+
try {
|
|
1683
|
+
apiState = await requireApiState(options);
|
|
1684
|
+
if (!apiState)
|
|
1685
|
+
return;
|
|
1686
|
+
resolvedContext = await resolveMemoryContext(options, { requireScope: true });
|
|
1687
|
+
resolvedContext = await hydrateProjectScopeContext(apiState, options, resolvedContext);
|
|
1688
|
+
}
|
|
1689
|
+
catch (error) {
|
|
1690
|
+
logError(error instanceof Error ? error.message : String(error));
|
|
1691
|
+
process.exitCode = 1;
|
|
1692
|
+
return;
|
|
1693
|
+
}
|
|
1694
|
+
try {
|
|
1695
|
+
const { input: resumeInput, result: resumePack } = await fetchMemoryResumePack(apiState, options, resolvedContext);
|
|
1696
|
+
const refs = getRecommendedDocRefs(resumePack);
|
|
1697
|
+
const selectedRef = selectRecommendedDocRef(refs, options.refIndex);
|
|
1698
|
+
const documentVersionId = resolveDocVersionIdFromRef(selectedRef, options.documentVersionId);
|
|
1699
|
+
const result = await callDocmanVersionRouteFromMemory(apiState, options, resolvedContext, {
|
|
1700
|
+
documentVersionId,
|
|
1701
|
+
suffix: 'compose-fetch',
|
|
1702
|
+
method: 'POST',
|
|
1703
|
+
body: compactPayload({
|
|
1704
|
+
documentVersionId,
|
|
1705
|
+
sectionId: normalizeNonEmpty(options.sectionId) ?? normalizeNonEmpty(selectedRef.sectionId),
|
|
1706
|
+
pageVersionId: normalizeNonEmpty(options.pageVersionId) ?? normalizeNonEmpty(selectedRef.pageVersionId),
|
|
1707
|
+
pageNumber: toPositiveInteger(options.pageNumber, 'pageNumber') ?? selectedRef.pageNumber,
|
|
1708
|
+
locale: normalizeNonEmpty(selectedRef.locale) ?? normalizeNonEmpty(options.locale),
|
|
1709
|
+
fallbackLocale: normalizeNonEmpty(selectedRef.fallbackLocale) ?? normalizeNonEmpty(options.fallbackLocale),
|
|
1710
|
+
}),
|
|
1711
|
+
});
|
|
1712
|
+
const envelopeResult = {
|
|
1713
|
+
selectedRef: compactPayload({ index: toPositiveInteger(options.refIndex, 'refIndex') ?? 1, ...selectedRef }),
|
|
1714
|
+
docman: result,
|
|
1715
|
+
};
|
|
1716
|
+
if (options.json) {
|
|
1717
|
+
console.log(JSON.stringify({
|
|
1718
|
+
command: 'memory.doc.source',
|
|
1719
|
+
surface: '/api/docman/document-versions/:id/compose-fetch',
|
|
1720
|
+
resolvedContext: buildResolvedContextRecord(resolvedContext),
|
|
1721
|
+
input: compactPayload({
|
|
1722
|
+
resume: resumeInput,
|
|
1723
|
+
selectedRef: compactPayload({ index: toPositiveInteger(options.refIndex, 'refIndex') ?? 1, ...selectedRef }),
|
|
1724
|
+
documentVersionId,
|
|
1725
|
+
sectionId: normalizeNonEmpty(options.sectionId) ?? normalizeNonEmpty(selectedRef.sectionId),
|
|
1726
|
+
pageVersionId: normalizeNonEmpty(options.pageVersionId) ?? normalizeNonEmpty(selectedRef.pageVersionId),
|
|
1727
|
+
pageNumber: toPositiveInteger(options.pageNumber, 'pageNumber') ?? selectedRef.pageNumber,
|
|
1728
|
+
locale: normalizeNonEmpty(selectedRef.locale) ?? normalizeNonEmpty(options.locale),
|
|
1729
|
+
fallbackLocale: normalizeNonEmpty(selectedRef.fallbackLocale) ?? normalizeNonEmpty(options.fallbackLocale),
|
|
1730
|
+
}),
|
|
1731
|
+
result: envelopeResult,
|
|
1732
|
+
}, null, 2));
|
|
1733
|
+
return;
|
|
1734
|
+
}
|
|
1735
|
+
logSuccess('Docman source loaded from recommended memory refs.');
|
|
1736
|
+
console.log(JSON.stringify(envelopeResult, null, 2));
|
|
1737
|
+
}
|
|
1738
|
+
catch (error) {
|
|
1739
|
+
logError(error instanceof Error ? error.message : String(error));
|
|
1740
|
+
process.exitCode = 1;
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1743
|
+
export async function runMemoryDocPublish(options = {}) {
|
|
1744
|
+
let resolvedContext;
|
|
1745
|
+
let apiState;
|
|
1746
|
+
try {
|
|
1747
|
+
apiState = await requireApiState(options);
|
|
1748
|
+
if (!apiState)
|
|
1749
|
+
return;
|
|
1750
|
+
resolvedContext = await resolveMemoryContext(options, { requireScope: true });
|
|
1751
|
+
resolvedContext = await hydrateProjectScopeContext(apiState, options, resolvedContext);
|
|
1752
|
+
}
|
|
1753
|
+
catch (error) {
|
|
1754
|
+
logError(error instanceof Error ? error.message : String(error));
|
|
1755
|
+
process.exitCode = 1;
|
|
1756
|
+
return;
|
|
1757
|
+
}
|
|
1758
|
+
try {
|
|
1759
|
+
const { input: resumeInput, result: resumePack } = await fetchMemoryResumePack(apiState, options, resolvedContext);
|
|
1760
|
+
const refs = getRecommendedDocRefs(resumePack);
|
|
1761
|
+
const selectedRef = selectRecommendedDocRef(refs, options.refIndex);
|
|
1762
|
+
const documentVersionId = resolveDocVersionIdFromRef(selectedRef, options.documentVersionId);
|
|
1763
|
+
const target = normalizeNonEmpty(options.target) ?? normalizeNonEmpty(selectedRef.target) ?? 'markdown';
|
|
1764
|
+
if (target !== 'markdown' && target !== 'html') {
|
|
1765
|
+
throw new Error('Target must be markdown or html.');
|
|
1766
|
+
}
|
|
1767
|
+
const result = await callDocmanVersionRouteFromMemory(apiState, options, resolvedContext, {
|
|
1768
|
+
documentVersionId,
|
|
1769
|
+
suffix: 'materialize',
|
|
1770
|
+
method: 'POST',
|
|
1771
|
+
body: compactPayload({
|
|
1772
|
+
documentVersionId,
|
|
1773
|
+
target,
|
|
1774
|
+
sectionId: normalizeNonEmpty(options.sectionId) ?? normalizeNonEmpty(selectedRef.sectionId),
|
|
1775
|
+
pageVersionId: normalizeNonEmpty(options.pageVersionId) ?? normalizeNonEmpty(selectedRef.pageVersionId),
|
|
1776
|
+
pageNumber: toPositiveInteger(options.pageNumber, 'pageNumber') ?? selectedRef.pageNumber,
|
|
1777
|
+
locale: normalizeNonEmpty(selectedRef.locale) ?? normalizeNonEmpty(options.locale),
|
|
1778
|
+
fallbackLocale: normalizeNonEmpty(selectedRef.fallbackLocale) ?? normalizeNonEmpty(options.fallbackLocale),
|
|
1779
|
+
}),
|
|
1780
|
+
});
|
|
1781
|
+
const envelopeResult = {
|
|
1782
|
+
selectedRef: compactPayload({ index: toPositiveInteger(options.refIndex, 'refIndex') ?? 1, ...selectedRef }),
|
|
1783
|
+
docman: result,
|
|
1784
|
+
};
|
|
1785
|
+
if (options.json) {
|
|
1786
|
+
console.log(JSON.stringify({
|
|
1787
|
+
command: 'memory.doc.publish',
|
|
1788
|
+
surface: '/api/docman/document-versions/:id/materialize',
|
|
1789
|
+
resolvedContext: buildResolvedContextRecord(resolvedContext),
|
|
1790
|
+
input: compactPayload({
|
|
1791
|
+
resume: resumeInput,
|
|
1792
|
+
selectedRef: compactPayload({ index: toPositiveInteger(options.refIndex, 'refIndex') ?? 1, ...selectedRef }),
|
|
1793
|
+
documentVersionId,
|
|
1794
|
+
target,
|
|
1795
|
+
sectionId: normalizeNonEmpty(options.sectionId) ?? normalizeNonEmpty(selectedRef.sectionId),
|
|
1796
|
+
pageVersionId: normalizeNonEmpty(options.pageVersionId) ?? normalizeNonEmpty(selectedRef.pageVersionId),
|
|
1797
|
+
pageNumber: toPositiveInteger(options.pageNumber, 'pageNumber') ?? selectedRef.pageNumber,
|
|
1798
|
+
locale: normalizeNonEmpty(selectedRef.locale) ?? normalizeNonEmpty(options.locale),
|
|
1799
|
+
fallbackLocale: normalizeNonEmpty(selectedRef.fallbackLocale) ?? normalizeNonEmpty(options.fallbackLocale),
|
|
1800
|
+
}),
|
|
1801
|
+
result: envelopeResult,
|
|
1802
|
+
}, null, 2));
|
|
1803
|
+
return;
|
|
1804
|
+
}
|
|
1805
|
+
logSuccess('Docman publish output loaded from recommended memory refs.');
|
|
1806
|
+
console.log(JSON.stringify(envelopeResult, null, 2));
|
|
1807
|
+
}
|
|
1808
|
+
catch (error) {
|
|
1809
|
+
logError(error instanceof Error ? error.message : String(error));
|
|
1810
|
+
process.exitCode = 1;
|
|
1811
|
+
}
|
|
1812
|
+
}
|
|
1813
|
+
function hostedRowId(row) {
|
|
1814
|
+
return normalizeNonEmpty(row.id) ?? normalizeNonEmpty(row.memoryId);
|
|
1815
|
+
}
|
|
1816
|
+
function hostedRowTags(row) {
|
|
1817
|
+
return new Set(toStringArray(row.tags).map((tag) => tag.toLowerCase()));
|
|
1818
|
+
}
|
|
1819
|
+
function hostedRowMeta(row) {
|
|
1820
|
+
return isRecord(row.meta) ? row.meta : {};
|
|
1821
|
+
}
|
|
1822
|
+
function hostedRowTimestamp(row) {
|
|
1823
|
+
const value = normalizeNonEmpty(row.updatedAt) ?? normalizeNonEmpty(row.createdAt);
|
|
1824
|
+
if (!value)
|
|
1825
|
+
return undefined;
|
|
1826
|
+
const timestamp = Date.parse(value);
|
|
1827
|
+
return Number.isFinite(timestamp) ? timestamp : undefined;
|
|
1828
|
+
}
|
|
1829
|
+
async function listHostedMemoryRows(apiState, options, resolvedContext, filters) {
|
|
1830
|
+
const subjectConfig = normalizeMemorySubject(filters.subject);
|
|
1831
|
+
const linked = buildLinkedIds(filters);
|
|
1832
|
+
const subjectId = buildSubjectId(subjectConfig, { id: filters.id, subjectId: filters.subjectId }, resolvedContext);
|
|
1833
|
+
const defaults = applySubjectDefaults(subjectConfig, subjectId, linked);
|
|
1834
|
+
const input = compactPayload({
|
|
1835
|
+
filter: compactPayload({
|
|
1836
|
+
scopeId: resolvedContext.scopeId,
|
|
1837
|
+
scopeResolution: 'cascade',
|
|
1838
|
+
durability: filters.durability,
|
|
1839
|
+
kind: normalizeNonEmpty(filters.kind),
|
|
1840
|
+
sourceType: normalizeNonEmpty(filters.subjectType) ?? defaults.sourceType,
|
|
1841
|
+
sourceId: normalizeNonEmpty(filters.subjectId) ?? subjectId,
|
|
1842
|
+
}),
|
|
1843
|
+
});
|
|
1844
|
+
const payload = await invokeHostedToolWithApiState(apiState, {
|
|
1845
|
+
...buildGatewayOptions(options, resolvedContext),
|
|
1846
|
+
tenantId: options.tenantId,
|
|
1847
|
+
locale: options.locale,
|
|
1848
|
+
fallbackLocale: options.fallbackLocale,
|
|
1849
|
+
timeoutMs: options.timeoutMs,
|
|
1850
|
+
apiBaseUrl: options.apiBaseUrl,
|
|
1851
|
+
accessToken: options.accessToken,
|
|
1852
|
+
refreshToken: options.refreshToken,
|
|
1853
|
+
toolId: 'agentspace.memory-item.list-memory-items',
|
|
1854
|
+
input,
|
|
1855
|
+
});
|
|
1856
|
+
const result = unwrapHostedToolResult(payload);
|
|
1857
|
+
const data = unwrapResultData(result);
|
|
1858
|
+
const rows = Array.isArray(data) ? data.filter(isRecord) : Array.isArray(result) ? result.filter(isRecord) : [];
|
|
1859
|
+
return rows.filter((row) => {
|
|
1860
|
+
if (filters.durability && normalizeNonEmpty(row.durability) !== filters.durability)
|
|
1861
|
+
return false;
|
|
1862
|
+
const tagSet = hostedRowTags(row);
|
|
1863
|
+
if (linked.kanbanTaskId && !tagSet.has(`kanban-task:${linked.kanbanTaskId}`.toLowerCase()))
|
|
1864
|
+
return false;
|
|
1865
|
+
if (linked.sprintId && !tagSet.has(`sprint:${linked.sprintId}`.toLowerCase()))
|
|
1866
|
+
return false;
|
|
1867
|
+
if (linked.phaseId && !tagSet.has(`phase:${linked.phaseId}`.toLowerCase()))
|
|
1868
|
+
return false;
|
|
1869
|
+
if (linked.microtaskId && !tagSet.has(`microtask:${linked.microtaskId}`.toLowerCase()))
|
|
1870
|
+
return false;
|
|
1871
|
+
if (linked.issueId && !tagSet.has(`issue:${linked.issueId}`.toLowerCase()))
|
|
1872
|
+
return false;
|
|
1873
|
+
if (linked.feedbackId && !tagSet.has(`feedback:${linked.feedbackId}`.toLowerCase()))
|
|
1874
|
+
return false;
|
|
1875
|
+
return true;
|
|
1876
|
+
});
|
|
1877
|
+
}
|
|
1878
|
+
async function addAutoCheckpointSupersedeHosted(writeOptions, original) {
|
|
1879
|
+
if (normalizeNonEmpty(writeOptions.supersede) || normalizeNonEmpty(writeOptions.checkpointAs) !== 'status') {
|
|
1880
|
+
return writeOptions;
|
|
1881
|
+
}
|
|
1882
|
+
try {
|
|
1883
|
+
const apiState = await requireApiState(original);
|
|
1884
|
+
if (!apiState)
|
|
1885
|
+
return writeOptions;
|
|
1886
|
+
let resolvedContext = await resolveMemoryContext(original, { requireScope: true });
|
|
1887
|
+
resolvedContext = await hydrateProjectScopeContext(apiState, original, resolvedContext);
|
|
1888
|
+
const rows = await listHostedMemoryRows(apiState, original, resolvedContext, {
|
|
1889
|
+
durability: 'short',
|
|
1890
|
+
kind: 'resume',
|
|
1891
|
+
subject: writeOptions.subject,
|
|
1892
|
+
subjectType: writeOptions.subjectType,
|
|
1893
|
+
subjectId: writeOptions.subjectId,
|
|
1894
|
+
id: writeOptions.id,
|
|
1895
|
+
});
|
|
1896
|
+
const previous = rows
|
|
1897
|
+
.filter((row) => {
|
|
1898
|
+
const tagSet = hostedRowTags(row);
|
|
1899
|
+
const meta = hostedRowMeta(row);
|
|
1900
|
+
return tagSet.has('memory:checkpoint') || normalizeNonEmpty(meta.checkpointAs) === 'status';
|
|
1901
|
+
})
|
|
1902
|
+
.sort((left, right) => (hostedRowTimestamp(right) ?? 0) - (hostedRowTimestamp(left) ?? 0))[0];
|
|
1903
|
+
const previousId = previous ? hostedRowId(previous) : undefined;
|
|
1904
|
+
return previousId ? { ...writeOptions, supersede: previousId } : writeOptions;
|
|
1905
|
+
}
|
|
1906
|
+
catch {
|
|
1907
|
+
return writeOptions;
|
|
1908
|
+
}
|
|
1909
|
+
}
|
|
1910
|
+
function hostedRowSummary(row, maxLength = 180) {
|
|
1911
|
+
const content = normalizeNonEmpty(row.content) ?? '';
|
|
1912
|
+
const compact = content.replace(/\s+/g, ' ').trim();
|
|
1913
|
+
if (compact.length <= maxLength)
|
|
1914
|
+
return compact || 'No content.';
|
|
1915
|
+
return `${compact.slice(0, Math.max(0, maxLength - 3)).trimEnd()}...`;
|
|
1916
|
+
}
|
|
1917
|
+
function buildHostedCompactDraftSummary(rows, context) {
|
|
1918
|
+
const lines = [
|
|
1919
|
+
'# Compacted Memory Summary',
|
|
1920
|
+
'',
|
|
1921
|
+
`Project: ${context.projectName ?? context.projectId ?? 'unknown'}`,
|
|
1922
|
+
`Source count: ${rows.length}`,
|
|
1923
|
+
'',
|
|
1924
|
+
'## High-Signal Notes',
|
|
1925
|
+
];
|
|
1926
|
+
if (rows.length === 0) {
|
|
1927
|
+
lines.push('- No source memories selected for compaction.');
|
|
1928
|
+
}
|
|
1929
|
+
else {
|
|
1930
|
+
for (const row of rows) {
|
|
1931
|
+
const meta = hostedRowMeta(row);
|
|
1932
|
+
const subject = [normalizeNonEmpty(row.sourceType) ?? normalizeNonEmpty(meta.subjectType), normalizeNonEmpty(meta.subjectTitle) ?? normalizeNonEmpty(row.sourceId)].filter(Boolean).join(' / ');
|
|
1933
|
+
lines.push(`- ${normalizeNonEmpty(row.kind) ?? 'memory'}${subject ? ` (${subject})` : ''}: ${hostedRowSummary(row, 180)}`);
|
|
1934
|
+
}
|
|
1935
|
+
}
|
|
1936
|
+
const nextActions = uniqueStrings(rows.map((row) => normalizeNonEmpty(hostedRowMeta(row).nextAction))).slice(0, 8);
|
|
1937
|
+
if (nextActions.length > 0) {
|
|
1938
|
+
lines.push('', '## Carry-Forward Actions');
|
|
1939
|
+
for (const action of nextActions)
|
|
1940
|
+
lines.push(`- ${action}`);
|
|
1941
|
+
}
|
|
1942
|
+
lines.push('', '## Source Memory IDs');
|
|
1943
|
+
for (const row of rows) {
|
|
1944
|
+
lines.push(`- ${hostedRowId(row) ?? 'unknown'}`);
|
|
1945
|
+
}
|
|
1946
|
+
lines.push('', '> Review this deterministic draft before treating it as durable project memory.');
|
|
1947
|
+
return `${lines.join('\n')}\n`;
|
|
1948
|
+
}
|
|
1949
|
+
export async function runMemoryCheckpoint(options = {}) {
|
|
1950
|
+
let resolvedContext;
|
|
1951
|
+
try {
|
|
1952
|
+
const writeOptions = buildCheckpointWriteOptions(options);
|
|
1953
|
+
const withSupersede = await addAutoCheckpointSupersedeHosted(writeOptions, options);
|
|
1954
|
+
await runMemoryWrite(withSupersede);
|
|
1955
|
+
}
|
|
1956
|
+
catch (error) {
|
|
1957
|
+
emitMemoryCommandError({
|
|
1958
|
+
options,
|
|
1959
|
+
command: 'memory.checkpoint',
|
|
1960
|
+
toolId: 'agentspace.memory-item.add-memory-item',
|
|
1961
|
+
resolvedContext,
|
|
1962
|
+
error,
|
|
1963
|
+
});
|
|
1964
|
+
}
|
|
1965
|
+
}
|
|
1966
|
+
export async function runMemorySummary(options = {}) {
|
|
1967
|
+
let resolvedContext;
|
|
1968
|
+
try {
|
|
1969
|
+
const writeOptions = buildSummaryWriteOptions(options);
|
|
1970
|
+
assertSummaryDurabilityGuard(options, writeOptions);
|
|
1971
|
+
await runMemoryWrite(writeOptions);
|
|
1972
|
+
}
|
|
1973
|
+
catch (error) {
|
|
1974
|
+
emitMemoryCommandError({
|
|
1975
|
+
options,
|
|
1976
|
+
command: 'memory.summary',
|
|
1977
|
+
toolId: 'agentspace.memory-item.add-memory-item',
|
|
1978
|
+
resolvedContext,
|
|
1979
|
+
error,
|
|
1980
|
+
});
|
|
1981
|
+
}
|
|
1982
|
+
}
|
|
1983
|
+
function buildMemoryUpdatePatch(options, resolvedContext) {
|
|
1984
|
+
const mode = normalizeMemoryMode(options.mode);
|
|
1985
|
+
const modeDefaults = mode ? MEMORY_MODE_DEFAULTS[mode] : undefined;
|
|
1986
|
+
const subjectConfig = normalizeMemorySubject(options.subject);
|
|
1987
|
+
const linked = buildLinkedIds(options);
|
|
1988
|
+
const subjectId = buildSubjectId(subjectConfig, { subjectId: options.subjectId }, resolvedContext);
|
|
1989
|
+
const subjectLabel = buildSubjectLabel(options);
|
|
1990
|
+
const subjectDefaults = applySubjectDefaults(subjectConfig, subjectId, linked);
|
|
1991
|
+
const durability = normalizeMemoryDurability(options.durability) ?? modeDefaults?.durability;
|
|
1992
|
+
const stickyScope = durability === 'sticky' ? 'project' : undefined;
|
|
1993
|
+
const tags = uniqueStrings([
|
|
1994
|
+
...(modeDefaults?.tags ?? []),
|
|
1995
|
+
...toStringArray(options.tag),
|
|
1996
|
+
...buildClassificationTags(options),
|
|
1997
|
+
resolvedContext.projectId ? `project:${resolvedContext.projectId}` : undefined,
|
|
1998
|
+
linked.kanbanTaskId ? `kanban-task:${linked.kanbanTaskId}` : undefined,
|
|
1999
|
+
linked.sprintId ? `sprint:${linked.sprintId}` : undefined,
|
|
2000
|
+
linked.phaseId ? `phase:${linked.phaseId}` : undefined,
|
|
2001
|
+
linked.microtaskId ? `microtask:${linked.microtaskId}` : undefined,
|
|
2002
|
+
normalizeNonEmpty(options.patternName) ? `pattern:${normalizeNonEmpty(options.patternName)}` : undefined,
|
|
2003
|
+
]);
|
|
2004
|
+
const normalizedMemoryShape = normalizeDecisionMemoryShape({
|
|
2005
|
+
kind: normalizeNonEmpty(options.kind) ?? modeDefaults?.kind,
|
|
2006
|
+
durability,
|
|
2007
|
+
tags,
|
|
2008
|
+
});
|
|
2009
|
+
const meta = compactPayload({
|
|
2010
|
+
purpose: toStringArray(options.purpose),
|
|
2011
|
+
areas: toStringArray(options.area),
|
|
2012
|
+
status: toStringArray(options.status),
|
|
2013
|
+
reviewAfterDays: toNonNegativeInteger(options.reviewAfterDays, 'reviewAfterDays'),
|
|
2014
|
+
expiresAt: normalizeIsoDateTime(options.expiresAt, 'expiresAt'),
|
|
2015
|
+
subjectType: normalizeNonEmpty(options.subjectType) ?? subjectDefaults.subjectType,
|
|
2016
|
+
subjectId: normalizeNonEmpty(options.subjectId) ?? subjectId,
|
|
2017
|
+
subjectTitle: normalizeNonEmpty(options.subjectTitle) ?? subjectLabel,
|
|
2018
|
+
projectId: resolvedContext.projectId,
|
|
2019
|
+
kanbanTaskId: linked.kanbanTaskId,
|
|
2020
|
+
sprintId: linked.sprintId,
|
|
2021
|
+
phaseId: linked.phaseId,
|
|
2022
|
+
microtaskId: linked.microtaskId,
|
|
2023
|
+
issueId: linked.issueId,
|
|
2024
|
+
feedbackId: linked.feedbackId,
|
|
2025
|
+
nextAction: normalizeNonEmpty(options.nextAction),
|
|
2026
|
+
nextReadRefs: parseRefOptionValues(options.nextReadRef, '--next-read-ref'),
|
|
2027
|
+
validationState: normalizeNonEmpty(options.validationState),
|
|
2028
|
+
sourceRefs: parseRefOptionValues(options.sourceRef, '--source-ref'),
|
|
2029
|
+
patternName: normalizeNonEmpty(options.patternName),
|
|
2030
|
+
patternWhen: normalizeNonEmpty(options.patternWhen),
|
|
2031
|
+
patternWhy: normalizeNonEmpty(options.patternWhy),
|
|
2032
|
+
patternEvidence: normalizeNonEmpty(options.patternEvidence),
|
|
2033
|
+
stickyScope,
|
|
2034
|
+
stickyRank: toNonNegativeInteger(options.stickyRank, 'stickyRank'),
|
|
2035
|
+
supersedes: normalizeNonEmpty(options.supersede),
|
|
2036
|
+
});
|
|
2037
|
+
return compactPayload({
|
|
2038
|
+
kind: normalizedMemoryShape.kind,
|
|
2039
|
+
durability: normalizedMemoryShape.durability,
|
|
2040
|
+
content: normalizeNonEmpty(options.content),
|
|
2041
|
+
tags: normalizedMemoryShape.tags.length > 0 ? normalizedMemoryShape.tags : undefined,
|
|
2042
|
+
importance: toNonNegativeInteger(options.importance, 'importance'),
|
|
2043
|
+
sourceType: normalizeNonEmpty(options.subjectType) ?? subjectDefaults.sourceType,
|
|
2044
|
+
sourceId: normalizeNonEmpty(options.subjectId) ?? subjectId,
|
|
2045
|
+
meta: Object.keys(meta).length > 0 ? meta : undefined,
|
|
2046
|
+
});
|
|
2047
|
+
}
|
|
2048
|
+
export async function runMemoryUpdate(options = {}) {
|
|
2049
|
+
let resolvedContext;
|
|
2050
|
+
let apiState;
|
|
2051
|
+
try {
|
|
2052
|
+
const id = normalizeNonEmpty(options.id);
|
|
2053
|
+
if (!id)
|
|
2054
|
+
throw new Error('Provide --id.');
|
|
2055
|
+
apiState = await requireApiState(options);
|
|
2056
|
+
if (!apiState)
|
|
2057
|
+
return;
|
|
2058
|
+
resolvedContext = await resolveMemoryContext(options, { requireScope: false });
|
|
2059
|
+
resolvedContext = await hydrateProjectScopeContext(apiState, options, resolvedContext);
|
|
2060
|
+
const patch = buildMemoryUpdatePatch(options, resolvedContext);
|
|
2061
|
+
if (Object.keys(patch).length === 0) {
|
|
2062
|
+
throw new Error('Provide at least one patch field such as --content, --kind, --tag, --next-action, or sticky metadata.');
|
|
2063
|
+
}
|
|
2064
|
+
const patchMeta = isRecord(patch.meta) ? patch.meta : {};
|
|
2065
|
+
const memoryQuality = buildMemoryQualityDiagnostics({
|
|
2066
|
+
kind: patch.kind,
|
|
2067
|
+
durability: patch.durability,
|
|
2068
|
+
content: patch.content,
|
|
2069
|
+
tags: patch.tags,
|
|
2070
|
+
subjectType: patch.sourceType,
|
|
2071
|
+
nextAction: patchMeta.nextAction,
|
|
2072
|
+
validationState: patchMeta.validationState,
|
|
2073
|
+
sourceRefs: patchMeta.sourceRefs,
|
|
2074
|
+
nextReadRefs: patchMeta.nextReadRefs,
|
|
2075
|
+
});
|
|
2076
|
+
const input = { id, patch };
|
|
2077
|
+
const payload = await invokeHostedToolWithApiState(apiState, {
|
|
2078
|
+
...buildGatewayOptions(options, resolvedContext),
|
|
2079
|
+
tenantId: options.tenantId,
|
|
2080
|
+
locale: options.locale,
|
|
2081
|
+
fallbackLocale: options.fallbackLocale,
|
|
2082
|
+
timeoutMs: options.timeoutMs,
|
|
2083
|
+
apiBaseUrl: options.apiBaseUrl,
|
|
2084
|
+
accessToken: options.accessToken,
|
|
2085
|
+
refreshToken: options.refreshToken,
|
|
2086
|
+
toolId: 'agentspace.memory-item.update-memory-item',
|
|
2087
|
+
input,
|
|
2088
|
+
preview: options.preview,
|
|
2089
|
+
apply: options.apply,
|
|
2090
|
+
confirm: options.confirm,
|
|
2091
|
+
idempotencyKey: options.idempotencyKey,
|
|
2092
|
+
});
|
|
2093
|
+
const result = unwrapHostedToolResult(payload);
|
|
2094
|
+
if (options.json) {
|
|
2095
|
+
console.log(JSON.stringify(buildEnvelope({
|
|
2096
|
+
command: options.envelopeCommand ?? 'memory.update',
|
|
2097
|
+
toolId: 'agentspace.memory-item.update-memory-item',
|
|
2098
|
+
resolvedContext: buildResolvedContextRecord(resolvedContext),
|
|
2099
|
+
input,
|
|
2100
|
+
result,
|
|
2101
|
+
diagnostics: compactPayload({ memoryQuality, warnings: toStringArray(options.diagnosticWarning) }),
|
|
2102
|
+
}), null, 2));
|
|
2103
|
+
return;
|
|
2104
|
+
}
|
|
2105
|
+
logSuccess('Memory item updated.');
|
|
2106
|
+
console.log(JSON.stringify(result, null, 2));
|
|
2107
|
+
}
|
|
2108
|
+
catch (error) {
|
|
2109
|
+
emitMemoryCommandError({
|
|
2110
|
+
options,
|
|
2111
|
+
command: options.envelopeCommand ?? 'memory.update',
|
|
2112
|
+
toolId: 'agentspace.memory-item.update-memory-item',
|
|
2113
|
+
resolvedContext,
|
|
2114
|
+
error,
|
|
2115
|
+
});
|
|
2116
|
+
}
|
|
2117
|
+
}
|
|
2118
|
+
export async function runMemoryWrite(options = {}) {
|
|
2119
|
+
let resolvedContext;
|
|
2120
|
+
let apiState;
|
|
2121
|
+
try {
|
|
2122
|
+
apiState = await requireApiState(options);
|
|
2123
|
+
if (!apiState)
|
|
2124
|
+
return;
|
|
2125
|
+
resolvedContext = await resolveMemoryContext(options, { requireScope: true });
|
|
2126
|
+
resolvedContext = await hydrateProjectScopeContext(apiState, options, resolvedContext);
|
|
2127
|
+
}
|
|
2128
|
+
catch (error) {
|
|
2129
|
+
logError(error instanceof Error ? error.message : String(error));
|
|
2130
|
+
process.exitCode = 1;
|
|
2131
|
+
return;
|
|
2132
|
+
}
|
|
2133
|
+
try {
|
|
2134
|
+
const mode = normalizeMemoryMode(options.mode);
|
|
2135
|
+
const modeDefaults = mode ? MEMORY_MODE_DEFAULTS[mode] : undefined;
|
|
2136
|
+
const subjectConfig = normalizeMemorySubject(options.subject);
|
|
2137
|
+
const linked = buildLinkedIds(options);
|
|
2138
|
+
const subjectId = buildSubjectId(subjectConfig, options, resolvedContext);
|
|
2139
|
+
const subjectLabel = buildSubjectLabel(options);
|
|
2140
|
+
const subjectDefaults = applySubjectDefaults(subjectConfig, subjectId, linked);
|
|
2141
|
+
const requestedKind = normalizeNonEmpty(options.kind) ?? modeDefaults?.kind;
|
|
2142
|
+
const durability = normalizeMemoryDurability(options.durability) ?? modeDefaults?.durability;
|
|
2143
|
+
const content = normalizeNonEmpty(expandAtFileContent(options.content));
|
|
2144
|
+
if (!requestedKind || !durability || !content) {
|
|
2145
|
+
logError('Missing required memory write fields. Provide --kind or --mode, --durability or --mode, and --content.');
|
|
2146
|
+
process.exitCode = 1;
|
|
2147
|
+
return;
|
|
2148
|
+
}
|
|
2149
|
+
const stickyScope = durability === 'sticky' ? 'project' : undefined;
|
|
2150
|
+
const tags = [
|
|
2151
|
+
...(modeDefaults?.tags ?? []),
|
|
2152
|
+
...toStringArray(options.tag),
|
|
2153
|
+
...buildClassificationTags(options),
|
|
2154
|
+
resolvedContext.projectId ? `project:${resolvedContext.projectId}` : undefined,
|
|
2155
|
+
linked.kanbanTaskId ? `kanban-task:${linked.kanbanTaskId}` : undefined,
|
|
2156
|
+
linked.sprintId ? `sprint:${linked.sprintId}` : undefined,
|
|
2157
|
+
linked.phaseId ? `phase:${linked.phaseId}` : undefined,
|
|
2158
|
+
linked.microtaskId ? `microtask:${linked.microtaskId}` : undefined,
|
|
2159
|
+
normalizeNonEmpty(options.patternName) ? `pattern:${normalizeNonEmpty(options.patternName)}` : undefined,
|
|
2160
|
+
];
|
|
2161
|
+
const normalizedMemoryShape = normalizeDecisionMemoryShape({
|
|
2162
|
+
kind: requestedKind,
|
|
2163
|
+
durability,
|
|
2164
|
+
tags: uniqueStrings(tags),
|
|
2165
|
+
});
|
|
2166
|
+
const meta = compactPayload({
|
|
2167
|
+
purpose: toStringArray(options.purpose),
|
|
2168
|
+
areas: toStringArray(options.area),
|
|
2169
|
+
status: toStringArray(options.status),
|
|
2170
|
+
reviewAfterDays: toNonNegativeInteger(options.reviewAfterDays, 'reviewAfterDays'),
|
|
2171
|
+
expiresAt: normalizeIsoDateTime(options.expiresAt, 'expiresAt'),
|
|
2172
|
+
subjectType: normalizeNonEmpty(options.subjectType) ?? subjectDefaults.subjectType,
|
|
2173
|
+
subjectId: normalizeNonEmpty(options.subjectId) ?? subjectId,
|
|
2174
|
+
subjectTitle: normalizeNonEmpty(options.subjectTitle) ?? subjectLabel,
|
|
2175
|
+
projectId: resolvedContext.projectId,
|
|
2176
|
+
kanbanTaskId: linked.kanbanTaskId,
|
|
2177
|
+
sprintId: linked.sprintId,
|
|
2178
|
+
phaseId: linked.phaseId,
|
|
2179
|
+
microtaskId: linked.microtaskId,
|
|
2180
|
+
issueId: linked.issueId,
|
|
2181
|
+
feedbackId: linked.feedbackId,
|
|
2182
|
+
nextAction: normalizeNonEmpty(options.nextAction),
|
|
2183
|
+
nextReadRefs: parseRefOptionValues(options.nextReadRef, '--next-read-ref'),
|
|
2184
|
+
validationState: normalizeNonEmpty(options.validationState),
|
|
2185
|
+
sourceRefs: parseRefOptionValues(options.sourceRef, '--source-ref'),
|
|
2186
|
+
patternName: normalizeNonEmpty(options.patternName),
|
|
2187
|
+
patternWhen: normalizeNonEmpty(options.patternWhen),
|
|
2188
|
+
patternWhy: normalizeNonEmpty(options.patternWhy),
|
|
2189
|
+
patternEvidence: normalizeNonEmpty(options.patternEvidence),
|
|
2190
|
+
stickyScope,
|
|
2191
|
+
stickyRank: toNonNegativeInteger(options.stickyRank, 'stickyRank'),
|
|
2192
|
+
supersedes: normalizeNonEmpty(options.supersede),
|
|
2193
|
+
checkpointAs: normalizeNonEmpty(options.checkpointAs),
|
|
2194
|
+
summaryType: normalizeNonEmpty(options.summaryType),
|
|
2195
|
+
});
|
|
2196
|
+
const input = {
|
|
2197
|
+
data: compactPayload({
|
|
2198
|
+
scopeId: resolvedContext.scopeId,
|
|
2199
|
+
kind: normalizedMemoryShape.kind,
|
|
2200
|
+
durability: normalizedMemoryShape.durability,
|
|
2201
|
+
content,
|
|
2202
|
+
tags: normalizedMemoryShape.tags.length > 0 ? normalizedMemoryShape.tags : undefined,
|
|
2203
|
+
importance: toNonNegativeInteger(options.importance, 'importance'),
|
|
2204
|
+
sourceType: normalizeNonEmpty(options.sourceType) ?? subjectDefaults.sourceType,
|
|
2205
|
+
sourceId: normalizeNonEmpty(options.sourceId) ?? subjectDefaults.sourceId,
|
|
2206
|
+
meta: Object.keys(meta).length > 0 ? meta : undefined,
|
|
2207
|
+
}),
|
|
2208
|
+
};
|
|
2209
|
+
const memoryQuality = buildMemoryQualityDiagnostics({
|
|
2210
|
+
kind: input.data.kind,
|
|
2211
|
+
durability: input.data.durability,
|
|
2212
|
+
content: input.data.content,
|
|
2213
|
+
tags: input.data.tags,
|
|
2214
|
+
subjectType: input.data.sourceType,
|
|
2215
|
+
nextAction: meta.nextAction,
|
|
2216
|
+
validationState: meta.validationState,
|
|
2217
|
+
sourceRefs: meta.sourceRefs,
|
|
2218
|
+
nextReadRefs: meta.nextReadRefs,
|
|
2219
|
+
});
|
|
2220
|
+
const payload = await invokeHostedToolWithApiState(apiState, {
|
|
2221
|
+
...buildGatewayOptions(options, resolvedContext),
|
|
2222
|
+
tenantId: options.tenantId,
|
|
2223
|
+
locale: options.locale,
|
|
2224
|
+
fallbackLocale: options.fallbackLocale,
|
|
2225
|
+
timeoutMs: options.timeoutMs,
|
|
2226
|
+
apiBaseUrl: options.apiBaseUrl,
|
|
2227
|
+
accessToken: options.accessToken,
|
|
2228
|
+
refreshToken: options.refreshToken,
|
|
2229
|
+
toolId: 'agentspace.memory-item.add-memory-item',
|
|
2230
|
+
input,
|
|
2231
|
+
preview: options.preview,
|
|
2232
|
+
apply: options.apply,
|
|
2233
|
+
confirm: options.confirm,
|
|
2234
|
+
idempotencyKey: options.idempotencyKey,
|
|
2235
|
+
});
|
|
2236
|
+
const result = unwrapHostedToolResult(payload);
|
|
2237
|
+
if (options.json) {
|
|
2238
|
+
console.log(JSON.stringify(buildEnvelope({
|
|
2239
|
+
command: options.envelopeCommand ?? 'memory.write',
|
|
2240
|
+
toolId: 'agentspace.memory-item.add-memory-item',
|
|
2241
|
+
resolvedContext: buildResolvedContextRecord(resolvedContext),
|
|
2242
|
+
input,
|
|
2243
|
+
result,
|
|
2244
|
+
diagnostics: compactPayload({ memoryQuality, warnings: toStringArray(options.diagnosticWarning) }),
|
|
2245
|
+
}), null, 2));
|
|
2246
|
+
return;
|
|
2247
|
+
}
|
|
2248
|
+
logSuccess('Memory item written.');
|
|
2249
|
+
console.log(JSON.stringify(result, null, 2));
|
|
2250
|
+
}
|
|
2251
|
+
catch (error) {
|
|
2252
|
+
emitMemoryCommandError({
|
|
2253
|
+
options,
|
|
2254
|
+
command: options.envelopeCommand ?? 'memory.write',
|
|
2255
|
+
toolId: 'agentspace.memory-item.add-memory-item',
|
|
2256
|
+
resolvedContext,
|
|
2257
|
+
input: undefined,
|
|
2258
|
+
error,
|
|
2259
|
+
});
|
|
2260
|
+
}
|
|
2261
|
+
}
|
|
2262
|
+
export async function runMemoryDelete(options = {}) {
|
|
2263
|
+
let resolvedContext;
|
|
2264
|
+
let apiState;
|
|
2265
|
+
try {
|
|
2266
|
+
const id = normalizeNonEmpty(options.id);
|
|
2267
|
+
if (!id)
|
|
2268
|
+
throw new Error('Provide --id.');
|
|
2269
|
+
if (options.apply !== true || options.confirm !== true) {
|
|
2270
|
+
throw new Error('This command deletes durable memory. Retry with --apply --confirm.');
|
|
2271
|
+
}
|
|
2272
|
+
apiState = await requireApiState(options);
|
|
2273
|
+
if (!apiState)
|
|
2274
|
+
return;
|
|
2275
|
+
resolvedContext = await resolveMemoryContext(options, { requireScope: false });
|
|
2276
|
+
resolvedContext = await hydrateProjectScopeContext(apiState, options, resolvedContext);
|
|
2277
|
+
const input = { id };
|
|
2278
|
+
const payload = await invokeHostedToolWithApiState(apiState, {
|
|
2279
|
+
...buildGatewayOptions(options, resolvedContext),
|
|
2280
|
+
tenantId: options.tenantId,
|
|
2281
|
+
locale: options.locale,
|
|
2282
|
+
fallbackLocale: options.fallbackLocale,
|
|
2283
|
+
timeoutMs: options.timeoutMs,
|
|
2284
|
+
apiBaseUrl: options.apiBaseUrl,
|
|
2285
|
+
accessToken: options.accessToken,
|
|
2286
|
+
refreshToken: options.refreshToken,
|
|
2287
|
+
toolId: 'agentspace.memory-item.remove-memory-item',
|
|
2288
|
+
input,
|
|
2289
|
+
preview: options.preview,
|
|
2290
|
+
apply: options.apply,
|
|
2291
|
+
confirm: options.confirm,
|
|
2292
|
+
idempotencyKey: options.idempotencyKey,
|
|
2293
|
+
});
|
|
2294
|
+
const result = unwrapHostedToolResult(payload);
|
|
2295
|
+
if (options.json) {
|
|
2296
|
+
console.log(JSON.stringify(buildEnvelope({
|
|
2297
|
+
command: 'memory.delete',
|
|
2298
|
+
toolId: 'agentspace.memory-item.remove-memory-item',
|
|
2299
|
+
resolvedContext: buildResolvedContextRecord(resolvedContext),
|
|
2300
|
+
input,
|
|
2301
|
+
result,
|
|
2302
|
+
}), null, 2));
|
|
2303
|
+
return;
|
|
2304
|
+
}
|
|
2305
|
+
logSuccess('Memory item deleted.');
|
|
2306
|
+
console.log(JSON.stringify(result, null, 2));
|
|
2307
|
+
}
|
|
2308
|
+
catch (error) {
|
|
2309
|
+
logError(error instanceof Error ? error.message : String(error));
|
|
2310
|
+
process.exitCode = 1;
|
|
2311
|
+
}
|
|
2312
|
+
}
|
|
2313
|
+
export async function runMemoryPrune(options = {}) {
|
|
2314
|
+
let resolvedContext;
|
|
2315
|
+
let apiState;
|
|
2316
|
+
try {
|
|
2317
|
+
apiState = await requireApiState(options);
|
|
2318
|
+
if (!apiState)
|
|
2319
|
+
return;
|
|
2320
|
+
resolvedContext = await resolveMemoryContext(options, { requireScope: true });
|
|
2321
|
+
resolvedContext = await hydrateProjectScopeContext(apiState, options, resolvedContext);
|
|
2322
|
+
const durability = normalizeMemoryDurability(options.durability) ?? 'short';
|
|
2323
|
+
if (durability !== 'short') {
|
|
2324
|
+
throw new Error('Memory prune only supports short memory. Delete durable/sticky memory explicitly with mem delete --id.');
|
|
2325
|
+
}
|
|
2326
|
+
const olderThanDays = toNonNegativeInteger(options.olderThanDays, 'olderThanDays') ?? 30;
|
|
2327
|
+
const keepLatest = toNonNegativeInteger(options.keepLatest, 'keepLatest') ?? 20;
|
|
2328
|
+
const maxDelete = toNonNegativeInteger(options.maxDelete, 'maxDelete');
|
|
2329
|
+
const dryRun = options.apply !== true || options.preview === true;
|
|
2330
|
+
if (options.apply === true && options.preview !== true && options.confirm !== true) {
|
|
2331
|
+
throw new Error('This command deletes hosted memory items. Retry with --apply --confirm (or use --preview).');
|
|
2332
|
+
}
|
|
2333
|
+
const rows = await listHostedMemoryRows(apiState, options, resolvedContext, {
|
|
2334
|
+
durability,
|
|
2335
|
+
kind: normalizeNonEmpty(options.kind),
|
|
2336
|
+
subject: options.subject,
|
|
2337
|
+
subjectType: options.subjectType,
|
|
2338
|
+
subjectId: options.subjectId,
|
|
2339
|
+
id: options.id,
|
|
2340
|
+
taskId: options.taskId,
|
|
2341
|
+
sprintId: options.sprintId,
|
|
2342
|
+
phaseId: options.phaseId,
|
|
2343
|
+
utaskId: options.utaskId,
|
|
2344
|
+
issueId: options.issueId,
|
|
2345
|
+
feedbackId: options.feedbackId,
|
|
2346
|
+
});
|
|
2347
|
+
const matched = rows
|
|
2348
|
+
.map((row) => ({ row, timestamp: hostedRowTimestamp(row) }))
|
|
2349
|
+
.sort((left, right) => (right.timestamp ?? 0) - (left.timestamp ?? 0));
|
|
2350
|
+
const retained = new Set(matched.slice(0, keepLatest).map(({ row }) => hostedRowId(row)).filter(Boolean));
|
|
2351
|
+
const cutoffMs = Date.now() - olderThanDays * 86_400_000;
|
|
2352
|
+
const stale = matched.filter(({ row, timestamp }) => {
|
|
2353
|
+
const id = hostedRowId(row);
|
|
2354
|
+
if (!id || retained.has(id))
|
|
2355
|
+
return false;
|
|
2356
|
+
return Boolean(timestamp && timestamp <= cutoffMs);
|
|
2357
|
+
});
|
|
2358
|
+
const selected = (maxDelete === undefined ? stale : stale.slice(0, maxDelete)).filter(({ row }) => Boolean(hostedRowId(row)));
|
|
2359
|
+
const deleted = [];
|
|
2360
|
+
if (!dryRun) {
|
|
2361
|
+
for (const { row } of selected) {
|
|
2362
|
+
const id = hostedRowId(row);
|
|
2363
|
+
if (!id)
|
|
2364
|
+
continue;
|
|
2365
|
+
await invokeHostedToolWithApiState(apiState, {
|
|
2366
|
+
...buildGatewayOptions(options, resolvedContext),
|
|
2367
|
+
tenantId: options.tenantId,
|
|
2368
|
+
locale: options.locale,
|
|
2369
|
+
fallbackLocale: options.fallbackLocale,
|
|
2370
|
+
timeoutMs: options.timeoutMs,
|
|
2371
|
+
apiBaseUrl: options.apiBaseUrl,
|
|
2372
|
+
accessToken: options.accessToken,
|
|
2373
|
+
refreshToken: options.refreshToken,
|
|
2374
|
+
toolId: 'agentspace.memory-item.remove-memory-item',
|
|
2375
|
+
input: { id },
|
|
2376
|
+
apply: true,
|
|
2377
|
+
confirm: true,
|
|
2378
|
+
});
|
|
2379
|
+
deleted.push(id);
|
|
2380
|
+
}
|
|
2381
|
+
}
|
|
2382
|
+
const result = compactPayload({
|
|
2383
|
+
dryRun,
|
|
2384
|
+
policy: compactPayload({ durability, olderThanDays, keepLatest, maxDelete }),
|
|
2385
|
+
matchedCount: matched.length,
|
|
2386
|
+
staleCount: stale.length,
|
|
2387
|
+
candidateCount: selected.length,
|
|
2388
|
+
prunedCount: dryRun ? 0 : deleted.length,
|
|
2389
|
+
prunedIds: deleted,
|
|
2390
|
+
candidates: selected.map(({ row }) => compactPayload({ id: hostedRowId(row), kind: normalizeNonEmpty(row.kind), durability: normalizeNonEmpty(row.durability) })),
|
|
2391
|
+
note: 'Hosted memory prune is server-first: candidates are read from list-memory-items and deleted via remove-memory-item. No local memory files are read or written.',
|
|
2392
|
+
});
|
|
2393
|
+
if (options.json) {
|
|
2394
|
+
console.log(JSON.stringify(buildEnvelope({
|
|
2395
|
+
command: 'memory.prune',
|
|
2396
|
+
toolId: 'agentspace.memory-item.remove-memory-item',
|
|
2397
|
+
resolvedContext: buildResolvedContextRecord(resolvedContext),
|
|
2398
|
+
input: compactPayload({ policy: result.policy, dryRun }),
|
|
2399
|
+
result,
|
|
2400
|
+
}), null, 2));
|
|
2401
|
+
return;
|
|
2402
|
+
}
|
|
2403
|
+
logSuccess(dryRun ? 'Hosted memory prune preview completed.' : 'Hosted memory prune completed.');
|
|
2404
|
+
console.log(JSON.stringify(result, null, 2));
|
|
2405
|
+
}
|
|
2406
|
+
catch (error) {
|
|
2407
|
+
emitMemoryCommandError({
|
|
2408
|
+
options,
|
|
2409
|
+
command: 'memory.prune',
|
|
2410
|
+
toolId: 'agentspace.memory-item.remove-memory-item',
|
|
2411
|
+
resolvedContext,
|
|
2412
|
+
error,
|
|
2413
|
+
});
|
|
2414
|
+
}
|
|
2415
|
+
}
|
|
2416
|
+
export async function runMemoryCompact(options = {}) {
|
|
2417
|
+
let resolvedContext;
|
|
2418
|
+
let apiState;
|
|
2419
|
+
try {
|
|
2420
|
+
apiState = await requireApiState(options);
|
|
2421
|
+
if (!apiState)
|
|
2422
|
+
return;
|
|
2423
|
+
resolvedContext = await resolveMemoryContext(options, { requireScope: true });
|
|
2424
|
+
resolvedContext = await hydrateProjectScopeContext(apiState, options, resolvedContext);
|
|
2425
|
+
if (options.apply === true && options.pruneSource === true && options.confirm !== true) {
|
|
2426
|
+
throw new Error('Compaction source pruning deletes hosted memory items. Retry with --apply --confirm.');
|
|
2427
|
+
}
|
|
2428
|
+
if (options.pruneSource === true && options.writeSummary !== true) {
|
|
2429
|
+
throw new Error('Use --write-summary with --prune-source so source deletion has a compacted destination.');
|
|
2430
|
+
}
|
|
2431
|
+
const durability = normalizeMemoryDurability(options.durability) ?? 'short';
|
|
2432
|
+
if (durability !== 'short') {
|
|
2433
|
+
throw new Error('Memory compact only compacts short source memory. Durable/sticky memory is long-term truth and is never a default compact source.');
|
|
2434
|
+
}
|
|
2435
|
+
const olderThanDays = toNonNegativeInteger(options.olderThanDays, 'olderThanDays') ?? 30;
|
|
2436
|
+
const keepLatest = toNonNegativeInteger(options.keepLatest, 'keepLatest') ?? 20;
|
|
2437
|
+
const maxItems = toNonNegativeInteger(options.maxItems, 'maxItems') ?? 20;
|
|
2438
|
+
const targetDurability = normalizeMemoryDurability(options.targetDurability) ?? 'durable';
|
|
2439
|
+
if (targetDurability === 'sticky') {
|
|
2440
|
+
throw new Error('Compaction writes durable or short summary memory only. Promote to sticky rule explicitly after review.');
|
|
2441
|
+
}
|
|
2442
|
+
const targetKind = normalizeHostedCompatibleMemoryKind(options.targetKind) ?? 'checkpoint';
|
|
2443
|
+
const rows = await listHostedMemoryRows(apiState, options, resolvedContext, {
|
|
2444
|
+
durability,
|
|
2445
|
+
kind: normalizeNonEmpty(options.kind),
|
|
2446
|
+
subject: options.subject,
|
|
2447
|
+
subjectType: options.subjectType,
|
|
2448
|
+
subjectId: options.subjectId,
|
|
2449
|
+
id: options.id,
|
|
2450
|
+
taskId: options.taskId,
|
|
2451
|
+
sprintId: options.sprintId,
|
|
2452
|
+
phaseId: options.phaseId,
|
|
2453
|
+
utaskId: options.utaskId,
|
|
2454
|
+
issueId: options.issueId,
|
|
2455
|
+
feedbackId: options.feedbackId,
|
|
2456
|
+
});
|
|
2457
|
+
const matched = rows
|
|
2458
|
+
.map((row) => ({ row, timestamp: hostedRowTimestamp(row) }))
|
|
2459
|
+
.sort((left, right) => (right.timestamp ?? 0) - (left.timestamp ?? 0));
|
|
2460
|
+
const retained = new Set(matched.slice(0, keepLatest).map(({ row }) => hostedRowId(row)).filter(Boolean));
|
|
2461
|
+
const cutoffMs = Date.now() - olderThanDays * 86_400_000;
|
|
2462
|
+
const sourceRows = matched
|
|
2463
|
+
.filter(({ row, timestamp }) => {
|
|
2464
|
+
const id = hostedRowId(row);
|
|
2465
|
+
if (!id || retained.has(id))
|
|
2466
|
+
return false;
|
|
2467
|
+
return Boolean(timestamp && timestamp <= cutoffMs);
|
|
2468
|
+
})
|
|
2469
|
+
.slice(0, maxItems)
|
|
2470
|
+
.map(({ row }) => row);
|
|
2471
|
+
const draftSummary = normalizeNonEmpty(options.content) ?? buildHostedCompactDraftSummary(sourceRows, resolvedContext);
|
|
2472
|
+
const sourceRefs = sourceRows.map((row) => compactPayload({
|
|
2473
|
+
kind: 'memory',
|
|
2474
|
+
memoryId: hostedRowId(row),
|
|
2475
|
+
sourceKind: normalizeNonEmpty(row.kind),
|
|
2476
|
+
durability: normalizeNonEmpty(row.durability),
|
|
2477
|
+
}));
|
|
2478
|
+
let writtenSummary;
|
|
2479
|
+
let summaryId;
|
|
2480
|
+
if (options.writeSummary === true) {
|
|
2481
|
+
if (options.apply !== true) {
|
|
2482
|
+
throw new Error('Retry with --apply because --write-summary writes a hosted memory item.');
|
|
2483
|
+
}
|
|
2484
|
+
const subjectConfig = normalizeMemorySubject(options.subject) ?? MEMORY_SUBJECTS.project;
|
|
2485
|
+
const linked = buildLinkedIds(options);
|
|
2486
|
+
const subjectId = buildSubjectId(subjectConfig, options, resolvedContext);
|
|
2487
|
+
const subjectDefaults = applySubjectDefaults(subjectConfig, subjectId, linked);
|
|
2488
|
+
const summaryMeta = compactPayload({
|
|
2489
|
+
subjectType: normalizeNonEmpty(options.subjectType) ?? subjectDefaults.subjectType,
|
|
2490
|
+
subjectId,
|
|
2491
|
+
subjectTitle: buildSubjectLabel(options) ?? 'Compacted memory summary',
|
|
2492
|
+
projectId: resolvedContext.projectId,
|
|
2493
|
+
sourceRefs,
|
|
2494
|
+
purpose: uniqueStrings([...toStringArray(options.purpose), 'compaction']),
|
|
2495
|
+
areas: toStringArray(options.area),
|
|
2496
|
+
status: uniqueStrings([...toStringArray(options.status), 'active']),
|
|
2497
|
+
});
|
|
2498
|
+
const summaryInput = {
|
|
2499
|
+
data: compactPayload({
|
|
2500
|
+
scopeId: resolvedContext.scopeId,
|
|
2501
|
+
kind: targetKind,
|
|
2502
|
+
durability: targetDurability,
|
|
2503
|
+
content: draftSummary,
|
|
2504
|
+
tags: uniqueStrings([
|
|
2505
|
+
'phase:compaction',
|
|
2506
|
+
'memory:compact',
|
|
2507
|
+
resolvedContext.projectId ? `project:${resolvedContext.projectId}` : undefined,
|
|
2508
|
+
...buildClassificationTags({ purpose: ['compaction'], area: options.area, status: options.status }),
|
|
2509
|
+
]),
|
|
2510
|
+
sourceType: normalizeNonEmpty(options.subjectType) ?? subjectDefaults.sourceType,
|
|
2511
|
+
sourceId: subjectId,
|
|
2512
|
+
meta: Object.keys(summaryMeta).length > 0 ? summaryMeta : undefined,
|
|
2513
|
+
}),
|
|
2514
|
+
};
|
|
2515
|
+
const summaryPayload = await invokeHostedToolWithApiState(apiState, {
|
|
2516
|
+
...buildGatewayOptions(options, resolvedContext),
|
|
2517
|
+
tenantId: options.tenantId,
|
|
2518
|
+
locale: options.locale,
|
|
2519
|
+
fallbackLocale: options.fallbackLocale,
|
|
2520
|
+
timeoutMs: options.timeoutMs,
|
|
2521
|
+
apiBaseUrl: options.apiBaseUrl,
|
|
2522
|
+
accessToken: options.accessToken,
|
|
2523
|
+
refreshToken: options.refreshToken,
|
|
2524
|
+
toolId: 'agentspace.memory-item.add-memory-item',
|
|
2525
|
+
input: summaryInput,
|
|
2526
|
+
apply: true,
|
|
2527
|
+
});
|
|
2528
|
+
writtenSummary = unwrapHostedToolResult(summaryPayload);
|
|
2529
|
+
summaryId = hostedRowId(unwrapResultData(writtenSummary) ?? {})
|
|
2530
|
+
?? hostedRowId(isRecord(writtenSummary) ? writtenSummary : {});
|
|
2531
|
+
if (options.pruneSource === true) {
|
|
2532
|
+
for (const row of sourceRows) {
|
|
2533
|
+
const id = hostedRowId(row);
|
|
2534
|
+
if (!id)
|
|
2535
|
+
continue;
|
|
2536
|
+
await invokeHostedToolWithApiState(apiState, {
|
|
2537
|
+
...buildGatewayOptions(options, resolvedContext),
|
|
2538
|
+
tenantId: options.tenantId,
|
|
2539
|
+
locale: options.locale,
|
|
2540
|
+
fallbackLocale: options.fallbackLocale,
|
|
2541
|
+
timeoutMs: options.timeoutMs,
|
|
2542
|
+
apiBaseUrl: options.apiBaseUrl,
|
|
2543
|
+
accessToken: options.accessToken,
|
|
2544
|
+
refreshToken: options.refreshToken,
|
|
2545
|
+
toolId: 'agentspace.memory-item.remove-memory-item',
|
|
2546
|
+
input: { id },
|
|
2547
|
+
apply: true,
|
|
2548
|
+
confirm: true,
|
|
2549
|
+
});
|
|
2550
|
+
}
|
|
2551
|
+
}
|
|
2552
|
+
}
|
|
2553
|
+
const result = compactPayload({
|
|
2554
|
+
readStrategy: 'hosted-deterministic-compact',
|
|
2555
|
+
writeSummary: options.writeSummary === true,
|
|
2556
|
+
pruneSource: options.pruneSource === true,
|
|
2557
|
+
sourceCount: sourceRows.length,
|
|
2558
|
+
target: compactPayload({ kind: targetKind, durability: targetDurability }),
|
|
2559
|
+
draftSummary,
|
|
2560
|
+
sourceRefs,
|
|
2561
|
+
writtenSummary,
|
|
2562
|
+
summaryId,
|
|
2563
|
+
note: 'Hosted memory compact is server-first: sources come from list-memory-items, the summary is written via add-memory-item, and sources are deleted via remove-memory-item. No local memory files are read or written.',
|
|
2564
|
+
});
|
|
2565
|
+
if (options.json) {
|
|
2566
|
+
console.log(JSON.stringify(buildEnvelope({
|
|
2567
|
+
command: 'memory.compact',
|
|
2568
|
+
toolId: 'agentspace.memory-item.add-memory-item',
|
|
2569
|
+
resolvedContext: buildResolvedContextRecord(resolvedContext),
|
|
2570
|
+
input: compactPayload({ olderThanDays, keepLatest, maxItems, writeSummary: options.writeSummary === true }),
|
|
2571
|
+
result,
|
|
2572
|
+
}), null, 2));
|
|
2573
|
+
return;
|
|
2574
|
+
}
|
|
2575
|
+
logSuccess(options.writeSummary === true ? 'Hosted memory compaction summary written.' : 'Hosted memory compact pack built.');
|
|
2576
|
+
console.log(JSON.stringify(result, null, 2));
|
|
2577
|
+
}
|
|
2578
|
+
catch (error) {
|
|
2579
|
+
emitMemoryCommandError({
|
|
2580
|
+
options,
|
|
2581
|
+
command: 'memory.compact',
|
|
2582
|
+
toolId: 'agentspace.memory-item.add-memory-item',
|
|
2583
|
+
resolvedContext,
|
|
2584
|
+
error,
|
|
2585
|
+
});
|
|
2586
|
+
}
|
|
2587
|
+
}
|
|
2588
|
+
function applyMemoryContextOptions(cmd) {
|
|
2589
|
+
applyCommonOptions(cmd, { withProject: false });
|
|
2590
|
+
cmd.option('--hosted', 'Deprecated/no-op: memory is now server-first by default (hosted Agentspace memory-item ops). Kept for script compatibility.');
|
|
2591
|
+
cmd.option('--scope-id <id>', 'Canonical owner scope override');
|
|
2592
|
+
cmd.option('--project-id <id>', 'Project id used to resolve scope and memory ownership');
|
|
2593
|
+
cmd.option('--project-name <name>', 'Project name override for repo-aware scope resolution');
|
|
2594
|
+
cmd.option('--tenant-id <id>', 'Tenant id header (x-tenant-id)');
|
|
2595
|
+
cmd.option('--locale <locale>', 'Locale header (x-locale)');
|
|
2596
|
+
cmd.option('--fallback-locale <locale>', 'Fallback locale header (x-fallback-locale)');
|
|
2597
|
+
return cmd;
|
|
2598
|
+
}
|
|
2599
|
+
function applyWriteOptions(cmd) {
|
|
2600
|
+
cmd.option('--preview', 'Return a validated preflight summary without executing the tool');
|
|
2601
|
+
cmd.option('--apply', 'Explicitly allow guarded write operations');
|
|
2602
|
+
cmd.option('--confirm', 'Explicitly confirm destructive operations');
|
|
2603
|
+
cmd.option('--idempotency-key <key>', 'Optional guarded-write idempotency key');
|
|
2604
|
+
return cmd;
|
|
2605
|
+
}
|
|
2606
|
+
function applyMemoryClassificationOptions(cmd) {
|
|
2607
|
+
cmd.option('--purpose <value>', 'Repeatable memory purpose tag (resume, howto, architecture, pattern, carry-forward, etc.)', collectRepeatedOption, []);
|
|
2608
|
+
cmd.option('--area <value>', 'Repeatable workstream/area tag (adk, electron, runner, docs, etc.)', collectRepeatedOption, []);
|
|
2609
|
+
cmd.option('--status <value>', 'Repeatable memory lifecycle tag (active, stale, archived, draft, etc.)', collectRepeatedOption, []);
|
|
2610
|
+
return cmd;
|
|
2611
|
+
}
|
|
2612
|
+
function applyMemoryReviewOptions(cmd) {
|
|
2613
|
+
cmd.option('--review-after-days <n>', 'Review/refresh reminder horizon in days', (value) => Number.parseInt(String(value), 10));
|
|
2614
|
+
return cmd;
|
|
2615
|
+
}
|
|
2616
|
+
export function makeMemoryCommand() {
|
|
2617
|
+
const cmd = new Command('mem')
|
|
2618
|
+
.alias('memory')
|
|
2619
|
+
.description('Agentspace server-first memory commands (hosted memory-item ops are the source of truth; the local .aops memory tree is never written)');
|
|
2620
|
+
const docCmd = cmd.command('doc').description('Advanced: read Docman through recommended memory refs');
|
|
2621
|
+
applyMemoryContextOptions(cmd.command('get')
|
|
2622
|
+
.description('Get a memory item by id')
|
|
2623
|
+
.requiredOption('--id <id>', 'Memory item id')
|
|
2624
|
+
.action(async (options) => {
|
|
2625
|
+
await runMemoryGet(options);
|
|
2626
|
+
}));
|
|
2627
|
+
applyMemoryContextOptions(applyMemoryClassificationOptions(cmd.command('list')
|
|
2628
|
+
.description('List memory items within the current owner scope')
|
|
2629
|
+
.option('--subject <kind>', 'Subject kind: project, ktask, sprint, phase, utask, issue, feedback')
|
|
2630
|
+
.option('--id <id>', 'Subject id (optional for project; defaults to current project id)')
|
|
2631
|
+
.option('--kind <kind>', 'Filter by memory kind')
|
|
2632
|
+
.option('--durability <durability>', 'Filter by durability: short, durable, sticky')
|
|
2633
|
+
.option('--subject-type <type>', 'Subject type, e.g. projectman.sprint')
|
|
2634
|
+
.option('--subject-id <id>', 'Subject id')
|
|
2635
|
+
.option('--source-type <type>', 'Exact source type filter')
|
|
2636
|
+
.option('--source-id <id>', 'Exact source id filter')
|
|
2637
|
+
.option('--task-id <id>', 'Linked kanban task id')
|
|
2638
|
+
.option('--sprint-id <id>', 'Linked sprint id')
|
|
2639
|
+
.option('--phase-id <id>', 'Linked phase id')
|
|
2640
|
+
.option('--utask-id <id>', 'Linked utask id')
|
|
2641
|
+
.option('--issue-id <id>', 'Linked issue id')
|
|
2642
|
+
.option('--feedback-id <id>', 'Linked feedback id')
|
|
2643
|
+
.option('--limit <count>', 'Result limit', (value) => Number.parseInt(String(value), 10))
|
|
2644
|
+
.action(async (options) => {
|
|
2645
|
+
await runMemoryList(options);
|
|
2646
|
+
})));
|
|
2647
|
+
applyMemoryContextOptions(applyMemoryClassificationOptions(cmd.command('search')
|
|
2648
|
+
.description('Recommended: search memory with subject-aware retrieval ranking')
|
|
2649
|
+
.option('--subject <kind>', 'Subject kind: project, ktask, sprint, phase, utask, issue, feedback')
|
|
2650
|
+
.option('--id <id>', 'Subject id (optional for project; defaults to current project id)')
|
|
2651
|
+
.option('--label <text>', 'Optional subject label/title')
|
|
2652
|
+
.option('--query <text>', 'Search query')
|
|
2653
|
+
.option('--q <text>', 'Alias for --query')
|
|
2654
|
+
.option('--goal <text>', 'Goal hint')
|
|
2655
|
+
.option('--runtime-profile <profile>', 'Runtime profile hint')
|
|
2656
|
+
.option('--workflow-id <id>', 'Workflow id hint')
|
|
2657
|
+
.option('--step-id <id>', 'Workflow step id hint')
|
|
2658
|
+
.option('--subject-type <type>', 'Subject type, e.g. projectman.sprint')
|
|
2659
|
+
.option('--subject-id <id>', 'Subject id')
|
|
2660
|
+
.option('--subject-label <label>', 'Subject label')
|
|
2661
|
+
.option('--task-id <id>', 'Linked kanban task id')
|
|
2662
|
+
.option('--sprint-id <id>', 'Linked sprint id')
|
|
2663
|
+
.option('--phase-id <id>', 'Linked phase id')
|
|
2664
|
+
.option('--utask-id <id>', 'Linked utask id')
|
|
2665
|
+
.option('--issue-id <id>', 'Linked issue id')
|
|
2666
|
+
.option('--feedback-id <id>', 'Linked feedback id')
|
|
2667
|
+
.option('--tag <value>', 'Repeatable retrieval tag', collectRepeatedOption, [])
|
|
2668
|
+
.option('--source-type <value>', 'Repeatable retrieval source type', collectRepeatedOption, [])
|
|
2669
|
+
.option('--source-id <value>', 'Repeatable retrieval source id', collectRepeatedOption, [])
|
|
2670
|
+
.option('--candidate-limit <count>', 'Candidate fetch limit', (value) => Number.parseInt(String(value), 10))
|
|
2671
|
+
.option('--limit <count>', 'Result limit', (value) => Number.parseInt(String(value), 10))
|
|
2672
|
+
.option('--strict-classification', 'Apply purpose/area/status as post-search filters instead of retrieval hints')
|
|
2673
|
+
.action(async (options) => {
|
|
2674
|
+
await runMemorySearch(options);
|
|
2675
|
+
})));
|
|
2676
|
+
applyMemoryContextOptions(applyMemoryClassificationOptions(cmd.command('synopsis')
|
|
2677
|
+
.description('Build a deterministic project/subject synopsis from hosted memory')
|
|
2678
|
+
.option('--subject <kind>', 'Subject kind: project, ktask, sprint, phase, utask, issue, feedback')
|
|
2679
|
+
.option('--id <id>', 'Subject id (optional for project; defaults to current project id)')
|
|
2680
|
+
.option('--label <text>', 'Optional subject label/title')
|
|
2681
|
+
.option('--query <text>', 'Synopsis query')
|
|
2682
|
+
.option('--q <text>', 'Alias for --query')
|
|
2683
|
+
.option('--goal <text>', 'Goal hint')
|
|
2684
|
+
.option('--runtime-profile <profile>', 'Runtime profile hint')
|
|
2685
|
+
.option('--workflow-id <id>', 'Workflow id hint')
|
|
2686
|
+
.option('--step-id <id>', 'Workflow step id hint')
|
|
2687
|
+
.option('--subject-type <type>', 'Subject type, e.g. projectman.sprint')
|
|
2688
|
+
.option('--subject-id <id>', 'Subject id')
|
|
2689
|
+
.option('--subject-label <label>', 'Subject label')
|
|
2690
|
+
.option('--task-id <id>', 'Linked kanban task id')
|
|
2691
|
+
.option('--sprint-id <id>', 'Linked sprint id')
|
|
2692
|
+
.option('--phase-id <id>', 'Linked phase id')
|
|
2693
|
+
.option('--utask-id <id>', 'Linked utask id')
|
|
2694
|
+
.option('--issue-id <id>', 'Linked issue id')
|
|
2695
|
+
.option('--feedback-id <id>', 'Linked feedback id')
|
|
2696
|
+
.option('--tag <value>', 'Repeatable retrieval tag', collectRepeatedOption, [])
|
|
2697
|
+
.option('--source-type <value>', 'Repeatable retrieval source type', collectRepeatedOption, [])
|
|
2698
|
+
.option('--source-id <value>', 'Repeatable retrieval source id', collectRepeatedOption, [])
|
|
2699
|
+
.option('--candidate-limit <count>', 'Candidate fetch limit', (value) => Number.parseInt(String(value), 10))
|
|
2700
|
+
.option('--limit <count>', 'Result limit', (value) => Number.parseInt(String(value), 10))
|
|
2701
|
+
.action(async (options) => {
|
|
2702
|
+
await runMemorySynopsis(options);
|
|
2703
|
+
})));
|
|
2704
|
+
applyMemoryContextOptions(applyMemoryClassificationOptions(cmd.command('brief')
|
|
2705
|
+
.description('Build a read-only bootstrap brief from synopsis, resume, sticky guidance, and refs')
|
|
2706
|
+
.option('--subject <kind>', 'Subject kind: project, ktask, sprint, phase, utask, issue, feedback')
|
|
2707
|
+
.option('--id <id>', 'Subject id (optional for project; defaults to current project id)')
|
|
2708
|
+
.option('--label <text>', 'Optional subject label/title')
|
|
2709
|
+
.option('--query <text>', 'Brief query')
|
|
2710
|
+
.option('--q <text>', 'Alias for --query')
|
|
2711
|
+
.option('--goal <text>', 'Goal hint')
|
|
2712
|
+
.option('--runtime-profile <profile>', 'Runtime profile hint')
|
|
2713
|
+
.option('--workflow-id <id>', 'Workflow id hint')
|
|
2714
|
+
.option('--step-id <id>', 'Workflow step id hint')
|
|
2715
|
+
.option('--subject-type <type>', 'Subject type, e.g. projectman.sprint')
|
|
2716
|
+
.option('--subject-id <id>', 'Subject id')
|
|
2717
|
+
.option('--subject-label <label>', 'Subject label')
|
|
2718
|
+
.option('--task-id <id>', 'Linked kanban task id')
|
|
2719
|
+
.option('--sprint-id <id>', 'Linked sprint id')
|
|
2720
|
+
.option('--phase-id <id>', 'Linked phase id')
|
|
2721
|
+
.option('--utask-id <id>', 'Linked utask id')
|
|
2722
|
+
.option('--issue-id <id>', 'Linked issue id')
|
|
2723
|
+
.option('--feedback-id <id>', 'Linked feedback id')
|
|
2724
|
+
.option('--tag <value>', 'Repeatable retrieval tag', collectRepeatedOption, [])
|
|
2725
|
+
.option('--source-type <value>', 'Repeatable retrieval source type', collectRepeatedOption, [])
|
|
2726
|
+
.option('--source-id <value>', 'Repeatable retrieval source id', collectRepeatedOption, [])
|
|
2727
|
+
.option('--candidate-limit <count>', 'Candidate fetch limit', (value) => Number.parseInt(String(value), 10))
|
|
2728
|
+
.option('--limit <count>', 'Brief item/ref limit', (value) => Number.parseInt(String(value), 10))
|
|
2729
|
+
.option('--depth <mode>', 'Brief depth: light or deep', 'light')
|
|
2730
|
+
.addHelpText('after', '\nWhen to call:\n' +
|
|
2731
|
+
' Use at session start/resume before implementation. It is a read-only startup pack; it should not replace PM state or write memory.\n')
|
|
2732
|
+
.action(async (options) => {
|
|
2733
|
+
await runMemoryBrief(options);
|
|
2734
|
+
})));
|
|
2735
|
+
applyMemoryContextOptions(applyMemoryClassificationOptions(cmd.command('resume')
|
|
2736
|
+
.description('Recommended: build a curated resume pack from hosted memory')
|
|
2737
|
+
.option('--subject <kind>', 'Subject kind: project, ktask, sprint, phase, utask, issue, feedback')
|
|
2738
|
+
.option('--id <id>', 'Subject id (optional for project; defaults to current project id)')
|
|
2739
|
+
.option('--label <text>', 'Optional subject label/title')
|
|
2740
|
+
.option('--query <text>', 'Resume query')
|
|
2741
|
+
.option('--q <text>', 'Alias for --query')
|
|
2742
|
+
.option('--goal <text>', 'Goal hint')
|
|
2743
|
+
.option('--runtime-profile <profile>', 'Runtime profile hint')
|
|
2744
|
+
.option('--workflow-id <id>', 'Workflow id hint')
|
|
2745
|
+
.option('--step-id <id>', 'Workflow step id hint')
|
|
2746
|
+
.option('--subject-type <type>', 'Subject type, e.g. projectman.sprint')
|
|
2747
|
+
.option('--subject-id <id>', 'Subject id')
|
|
2748
|
+
.option('--subject-label <label>', 'Subject label')
|
|
2749
|
+
.option('--task-id <id>', 'Linked kanban task id')
|
|
2750
|
+
.option('--sprint-id <id>', 'Linked sprint id')
|
|
2751
|
+
.option('--phase-id <id>', 'Linked phase id')
|
|
2752
|
+
.option('--utask-id <id>', 'Linked utask id')
|
|
2753
|
+
.option('--issue-id <id>', 'Linked issue id')
|
|
2754
|
+
.option('--feedback-id <id>', 'Linked feedback id')
|
|
2755
|
+
.option('--tag <value>', 'Repeatable retrieval tag', collectRepeatedOption, [])
|
|
2756
|
+
.option('--source-type <value>', 'Repeatable retrieval source type', collectRepeatedOption, [])
|
|
2757
|
+
.option('--source-id <value>', 'Repeatable retrieval source id', collectRepeatedOption, [])
|
|
2758
|
+
.option('--candidate-limit <count>', 'Candidate fetch limit', (value) => Number.parseInt(String(value), 10))
|
|
2759
|
+
.option('--limit <count>', 'Result limit', (value) => Number.parseInt(String(value), 10))
|
|
2760
|
+
.option('--depth <mode>', 'Resume pack depth: light or deep', 'light')
|
|
2761
|
+
.action(async (options) => {
|
|
2762
|
+
await runMemoryResume(options);
|
|
2763
|
+
})));
|
|
2764
|
+
applyMemoryContextOptions(applyMemoryClassificationOptions(docCmd.command('refs')
|
|
2765
|
+
.description('List Docman-capable recommended refs from a curated resume pack')
|
|
2766
|
+
.option('--subject <kind>', 'Subject kind: project, ktask, sprint, phase, utask, issue, feedback')
|
|
2767
|
+
.option('--id <id>', 'Subject id (optional for project; defaults to current project id)')
|
|
2768
|
+
.option('--label <text>', 'Optional subject label/title')
|
|
2769
|
+
.option('--query <text>', 'Resume query')
|
|
2770
|
+
.option('--goal <text>', 'Goal hint')
|
|
2771
|
+
.option('--runtime-profile <profile>', 'Runtime profile hint')
|
|
2772
|
+
.option('--workflow-id <id>', 'Workflow id hint')
|
|
2773
|
+
.option('--step-id <id>', 'Workflow step id hint')
|
|
2774
|
+
.option('--subject-type <type>', 'Subject type, e.g. projectman.sprint')
|
|
2775
|
+
.option('--subject-id <id>', 'Subject id')
|
|
2776
|
+
.option('--subject-label <label>', 'Subject label')
|
|
2777
|
+
.option('--task-id <id>', 'Linked kanban task id')
|
|
2778
|
+
.option('--sprint-id <id>', 'Linked sprint id')
|
|
2779
|
+
.option('--phase-id <id>', 'Linked phase id')
|
|
2780
|
+
.option('--utask-id <id>', 'Linked utask id')
|
|
2781
|
+
.option('--issue-id <id>', 'Linked issue id')
|
|
2782
|
+
.option('--feedback-id <id>', 'Linked feedback id')
|
|
2783
|
+
.option('--tag <value>', 'Repeatable retrieval tag', collectRepeatedOption, [])
|
|
2784
|
+
.option('--source-type <value>', 'Repeatable retrieval source type', collectRepeatedOption, [])
|
|
2785
|
+
.option('--source-id <value>', 'Repeatable retrieval source id', collectRepeatedOption, [])
|
|
2786
|
+
.option('--candidate-limit <count>', 'Candidate fetch limit', (value) => Number.parseInt(String(value), 10))
|
|
2787
|
+
.option('--limit <count>', 'Result limit', (value) => Number.parseInt(String(value), 10))
|
|
2788
|
+
.option('--depth <mode>', 'Resume pack depth: light or deep', 'light')
|
|
2789
|
+
.action(async (options) => {
|
|
2790
|
+
await runMemoryDocRefs(options);
|
|
2791
|
+
})));
|
|
2792
|
+
applyMemoryContextOptions(applyMemoryClassificationOptions(docCmd.command('answer')
|
|
2793
|
+
.description('Run Docman answer-pack against a recommended memory ref')
|
|
2794
|
+
.option('--subject <kind>', 'Subject kind: project, ktask, sprint, phase, utask, issue, feedback')
|
|
2795
|
+
.option('--id <id>', 'Subject id (optional for project; defaults to current project id)')
|
|
2796
|
+
.option('--label <text>', 'Optional subject label/title')
|
|
2797
|
+
.option('--query <text>', 'Resume query')
|
|
2798
|
+
.option('--goal <text>', 'Goal hint')
|
|
2799
|
+
.option('--runtime-profile <profile>', 'Runtime profile hint')
|
|
2800
|
+
.option('--workflow-id <id>', 'Workflow id hint')
|
|
2801
|
+
.option('--step-id <id>', 'Workflow step id hint')
|
|
2802
|
+
.option('--subject-type <type>', 'Subject type')
|
|
2803
|
+
.option('--subject-id <id>', 'Subject id')
|
|
2804
|
+
.option('--subject-label <label>', 'Subject label')
|
|
2805
|
+
.option('--task-id <id>', 'Linked kanban task id')
|
|
2806
|
+
.option('--sprint-id <id>', 'Linked sprint id')
|
|
2807
|
+
.option('--phase-id <id>', 'Linked phase id')
|
|
2808
|
+
.option('--utask-id <id>', 'Linked utask id')
|
|
2809
|
+
.option('--issue-id <id>', 'Linked issue id')
|
|
2810
|
+
.option('--feedback-id <id>', 'Linked feedback id')
|
|
2811
|
+
.option('--tag <value>', 'Repeatable retrieval tag', collectRepeatedOption, [])
|
|
2812
|
+
.option('--source-type <value>', 'Repeatable retrieval source type', collectRepeatedOption, [])
|
|
2813
|
+
.option('--source-id <value>', 'Repeatable retrieval source id', collectRepeatedOption, [])
|
|
2814
|
+
.option('--candidate-limit <count>', 'Candidate fetch limit', (value) => Number.parseInt(String(value), 10))
|
|
2815
|
+
.option('--limit <count>', 'Result limit', (value) => Number.parseInt(String(value), 10))
|
|
2816
|
+
.option('--depth <mode>', 'Resume pack depth: light or deep', 'light')
|
|
2817
|
+
.option('--ref-index <n>', 'Recommended ref index (1-based)', (value) => Number.parseInt(String(value), 10))
|
|
2818
|
+
.requiredOption('--q <text>', 'Question passed to Docman answer-pack')
|
|
2819
|
+
.option('--document-version-id <id>', 'Explicit document version id override')
|
|
2820
|
+
.option('--ensure <mode>', 'Ensure retrieval rows first: none, index, summary', 'none')
|
|
2821
|
+
.option('--retrieval-strategy <mode>', 'Retrieval strategy: lexical, hybrid, semantic')
|
|
2822
|
+
.action(async (options) => {
|
|
2823
|
+
await runMemoryDocAnswer(options);
|
|
2824
|
+
})));
|
|
2825
|
+
applyMemoryContextOptions(applyMemoryClassificationOptions(docCmd.command('source')
|
|
2826
|
+
.description('Fetch exact Docman source from a recommended memory ref')
|
|
2827
|
+
.option('--subject <kind>', 'Subject kind: project, ktask, sprint, phase, utask, issue, feedback')
|
|
2828
|
+
.option('--id <id>', 'Subject id (optional for project; defaults to current project id)')
|
|
2829
|
+
.option('--label <text>', 'Optional subject label/title')
|
|
2830
|
+
.option('--query <text>', 'Resume query')
|
|
2831
|
+
.option('--goal <text>', 'Goal hint')
|
|
2832
|
+
.option('--runtime-profile <profile>', 'Runtime profile hint')
|
|
2833
|
+
.option('--workflow-id <id>', 'Workflow id hint')
|
|
2834
|
+
.option('--step-id <id>', 'Workflow step id hint')
|
|
2835
|
+
.option('--subject-type <type>', 'Subject type')
|
|
2836
|
+
.option('--subject-id <id>', 'Subject id')
|
|
2837
|
+
.option('--subject-label <label>', 'Subject label')
|
|
2838
|
+
.option('--task-id <id>', 'Linked kanban task id')
|
|
2839
|
+
.option('--sprint-id <id>', 'Linked sprint id')
|
|
2840
|
+
.option('--phase-id <id>', 'Linked phase id')
|
|
2841
|
+
.option('--utask-id <id>', 'Linked utask id')
|
|
2842
|
+
.option('--issue-id <id>', 'Linked issue id')
|
|
2843
|
+
.option('--feedback-id <id>', 'Linked feedback id')
|
|
2844
|
+
.option('--tag <value>', 'Repeatable retrieval tag', collectRepeatedOption, [])
|
|
2845
|
+
.option('--source-type <value>', 'Repeatable retrieval source type', collectRepeatedOption, [])
|
|
2846
|
+
.option('--source-id <value>', 'Repeatable retrieval source id', collectRepeatedOption, [])
|
|
2847
|
+
.option('--candidate-limit <count>', 'Candidate fetch limit', (value) => Number.parseInt(String(value), 10))
|
|
2848
|
+
.option('--limit <count>', 'Result limit', (value) => Number.parseInt(String(value), 10))
|
|
2849
|
+
.option('--depth <mode>', 'Resume pack depth: light or deep', 'light')
|
|
2850
|
+
.option('--ref-index <n>', 'Recommended ref index (1-based)', (value) => Number.parseInt(String(value), 10))
|
|
2851
|
+
.option('--document-version-id <id>', 'Explicit document version id override')
|
|
2852
|
+
.option('--section-id <id>', 'Explicit section id override')
|
|
2853
|
+
.option('--page-version-id <id>', 'Explicit page version id override')
|
|
2854
|
+
.option('--page-number <n>', 'Explicit page number override', (value) => Number.parseInt(String(value), 10))
|
|
2855
|
+
.action(async (options) => {
|
|
2856
|
+
await runMemoryDocSource(options);
|
|
2857
|
+
})));
|
|
2858
|
+
applyMemoryContextOptions(applyMemoryClassificationOptions(docCmd.command('publish')
|
|
2859
|
+
.description('Materialize Docman output from a recommended memory ref')
|
|
2860
|
+
.option('--subject <kind>', 'Subject kind: project, ktask, sprint, phase, utask, issue, feedback')
|
|
2861
|
+
.option('--id <id>', 'Subject id (optional for project; defaults to current project id)')
|
|
2862
|
+
.option('--label <text>', 'Optional subject label/title')
|
|
2863
|
+
.option('--query <text>', 'Resume query')
|
|
2864
|
+
.option('--goal <text>', 'Goal hint')
|
|
2865
|
+
.option('--runtime-profile <profile>', 'Runtime profile hint')
|
|
2866
|
+
.option('--workflow-id <id>', 'Workflow id hint')
|
|
2867
|
+
.option('--step-id <id>', 'Workflow step id hint')
|
|
2868
|
+
.option('--subject-type <type>', 'Subject type')
|
|
2869
|
+
.option('--subject-id <id>', 'Subject id')
|
|
2870
|
+
.option('--subject-label <label>', 'Subject label')
|
|
2871
|
+
.option('--task-id <id>', 'Linked kanban task id')
|
|
2872
|
+
.option('--sprint-id <id>', 'Linked sprint id')
|
|
2873
|
+
.option('--phase-id <id>', 'Linked phase id')
|
|
2874
|
+
.option('--utask-id <id>', 'Linked utask id')
|
|
2875
|
+
.option('--issue-id <id>', 'Linked issue id')
|
|
2876
|
+
.option('--feedback-id <id>', 'Linked feedback id')
|
|
2877
|
+
.option('--tag <value>', 'Repeatable retrieval tag', collectRepeatedOption, [])
|
|
2878
|
+
.option('--source-type <value>', 'Repeatable retrieval source type', collectRepeatedOption, [])
|
|
2879
|
+
.option('--source-id <value>', 'Repeatable retrieval source id', collectRepeatedOption, [])
|
|
2880
|
+
.option('--candidate-limit <count>', 'Candidate fetch limit', (value) => Number.parseInt(String(value), 10))
|
|
2881
|
+
.option('--limit <count>', 'Result limit', (value) => Number.parseInt(String(value), 10))
|
|
2882
|
+
.option('--depth <mode>', 'Resume pack depth: light or deep', 'light')
|
|
2883
|
+
.option('--ref-index <n>', 'Recommended ref index (1-based)', (value) => Number.parseInt(String(value), 10))
|
|
2884
|
+
.option('--document-version-id <id>', 'Explicit document version id override')
|
|
2885
|
+
.option('--section-id <id>', 'Explicit section id override')
|
|
2886
|
+
.option('--page-version-id <id>', 'Explicit page version id override')
|
|
2887
|
+
.option('--page-number <n>', 'Explicit page number override', (value) => Number.parseInt(String(value), 10))
|
|
2888
|
+
.option('--target <mode>', 'Materialize target: markdown or html', 'markdown')
|
|
2889
|
+
.action(async (options) => {
|
|
2890
|
+
await runMemoryDocPublish(options);
|
|
2891
|
+
})));
|
|
2892
|
+
applyMemoryContextOptions(applyWriteOptions(applyMemoryReviewOptions(applyMemoryClassificationOptions(cmd.command('checkpoint')
|
|
2893
|
+
.description('Write a short rolling checkpoint; defaults to project resume memory')
|
|
2894
|
+
.requiredOption('--content <textOrFile>', 'Checkpoint content text, or @file to read content from disk')
|
|
2895
|
+
.option('--as <kind>', 'Checkpoint flavor: status, decision, blocker, milestone', 'status')
|
|
2896
|
+
.option('--subject <kind>', 'Subject kind: project, ktask, sprint, phase, utask, issue, feedback')
|
|
2897
|
+
.option('--id <id>', 'Subject id (optional for project; defaults to current project id)')
|
|
2898
|
+
.option('--label <text>', 'Optional subject label/title')
|
|
2899
|
+
.option('--durability <durability>', 'Memory durability; defaults to short')
|
|
2900
|
+
.option('--importance <value>', 'Importance score (0-100)', (value) => Number.parseInt(String(value), 10))
|
|
2901
|
+
.option('--tag <value>', 'Repeatable tag', collectRepeatedOption, [])
|
|
2902
|
+
.option('--task-id <id>', 'Linked kanban task id')
|
|
2903
|
+
.option('--kanban-task-id <id>', 'Linked kanban task id')
|
|
2904
|
+
.option('--sprint-id <id>', 'Linked sprint id')
|
|
2905
|
+
.option('--phase-id <id>', 'Linked phase id')
|
|
2906
|
+
.option('--utask-id <id>', 'Linked utask id')
|
|
2907
|
+
.option('--microtask-id <id>', 'Linked microtask id')
|
|
2908
|
+
.option('--issue-id <id>', 'Linked issue id')
|
|
2909
|
+
.option('--feedback-id <id>', 'Linked feedback id')
|
|
2910
|
+
.option('--next-action <text>', 'Recommended next action')
|
|
2911
|
+
.option('--next-read-ref <value>', 'Repeatable next read ref (string, JSON object/array, or @file.json)', collectRepeatedOption, [])
|
|
2912
|
+
.option('--source-ref <value>', 'Repeatable source ref (string, JSON object/array, or @file.json)', collectRepeatedOption, [])
|
|
2913
|
+
.option('--validation-state <text>', 'Validation state summary')
|
|
2914
|
+
.option('--supersede <memoryId>', 'Explicitly supersede an older checkpoint memory id'))).addHelpText('after', '\nWhen to call:\n' +
|
|
2915
|
+
' Use after a meaningful milestone, decision, blocker, or handoff point. Do not write one for every chat line or tiny edit.\n' +
|
|
2916
|
+
' Default checkpoint is a short resume/carry-forward record; --as decision|blocker|milestone changes the checkpoint shape.\n' +
|
|
2917
|
+
MEMORY_EVIDENCE_HELP_TEXT + '\n')).action(async (options) => {
|
|
2918
|
+
await runMemoryCheckpoint(options);
|
|
2919
|
+
}));
|
|
2920
|
+
applyMemoryContextOptions(applyWriteOptions(applyMemoryReviewOptions(applyMemoryClassificationOptions(cmd.command('summary')
|
|
2921
|
+
.description('Write a short session summary; durable closeout requires explicit --closeout --durability durable --confirm')
|
|
2922
|
+
.requiredOption('--content <textOrFile>', 'Summary content text, or @file to read content from disk')
|
|
2923
|
+
.option('--closeout', 'Mark this summary as a closeout summary')
|
|
2924
|
+
.option('--subject <kind>', 'Subject kind: project, ktask, sprint, phase, utask, issue, feedback')
|
|
2925
|
+
.option('--id <id>', 'Subject id (optional for project; defaults to current project id)')
|
|
2926
|
+
.option('--label <text>', 'Optional subject label/title')
|
|
2927
|
+
.option('--durability <durability>', 'Memory durability; defaults to short')
|
|
2928
|
+
.option('--importance <value>', 'Importance score (0-100)', (value) => Number.parseInt(String(value), 10))
|
|
2929
|
+
.option('--tag <value>', 'Repeatable tag', collectRepeatedOption, [])
|
|
2930
|
+
.option('--task-id <id>', 'Linked kanban task id')
|
|
2931
|
+
.option('--kanban-task-id <id>', 'Linked kanban task id')
|
|
2932
|
+
.option('--sprint-id <id>', 'Linked sprint id')
|
|
2933
|
+
.option('--phase-id <id>', 'Linked phase id')
|
|
2934
|
+
.option('--utask-id <id>', 'Linked utask id')
|
|
2935
|
+
.option('--microtask-id <id>', 'Linked microtask id')
|
|
2936
|
+
.option('--issue-id <id>', 'Linked issue id')
|
|
2937
|
+
.option('--feedback-id <id>', 'Linked feedback id')
|
|
2938
|
+
.option('--next-action <text>', 'Recommended next action')
|
|
2939
|
+
.option('--next-read-ref <value>', 'Repeatable next read ref (string, JSON object/array, or @file.json)', collectRepeatedOption, [])
|
|
2940
|
+
.option('--source-ref <value>', 'Repeatable source ref (string, JSON object/array, or @file.json)', collectRepeatedOption, [])
|
|
2941
|
+
.option('--validation-state <text>', 'Validation state summary'))).addHelpText('after', '\nWhen to call:\n' +
|
|
2942
|
+
' Use at session end or explicit operator closeout/summary points. For ordinary continuation, prefer mem checkpoint.\n' +
|
|
2943
|
+
' Durable closeout requires --closeout --durability durable --confirm; otherwise summaries stay short.\n' +
|
|
2944
|
+
MEMORY_EVIDENCE_HELP_TEXT + '\n')).action(async (options) => {
|
|
2945
|
+
await runMemorySummary(options);
|
|
2946
|
+
}));
|
|
2947
|
+
applyMemoryContextOptions(applyWriteOptions(applyMemoryReviewOptions(applyMemoryClassificationOptions(cmd.command('update')
|
|
2948
|
+
.description('Advanced: update an existing memory item')
|
|
2949
|
+
.requiredOption('--id <id>', 'Memory item id')
|
|
2950
|
+
.option('--mode <mode>', 'Memory mode defaults: kickoff, resume, decision, blocker, closeout, rule')
|
|
2951
|
+
.option('--subject <kind>', 'Subject kind: project, ktask, sprint, phase, utask, issue, feedback')
|
|
2952
|
+
.option('--kind <kind>', 'Memory kind patch')
|
|
2953
|
+
.option('--durability <durability>', 'Memory durability: short, durable, sticky')
|
|
2954
|
+
.option('--content <textOrFile>', 'Updated memory content text, or @file to read content from disk')
|
|
2955
|
+
.option('--importance <value>', 'Importance score (0-100)', (value) => Number.parseInt(String(value), 10))
|
|
2956
|
+
.option('--tag <value>', 'Repeatable tag', collectRepeatedOption, [])
|
|
2957
|
+
.option('--subject-type <type>', 'Subject type')
|
|
2958
|
+
.option('--subject-id <id>', 'Subject id')
|
|
2959
|
+
.option('--subject-title <text>', 'Subject title')
|
|
2960
|
+
.option('--task-id <id>', 'Linked kanban task id')
|
|
2961
|
+
.option('--kanban-task-id <id>', 'Linked kanban task id')
|
|
2962
|
+
.option('--sprint-id <id>', 'Linked sprint id')
|
|
2963
|
+
.option('--phase-id <id>', 'Linked phase id')
|
|
2964
|
+
.option('--utask-id <id>', 'Linked utask id')
|
|
2965
|
+
.option('--microtask-id <id>', 'Linked microtask id')
|
|
2966
|
+
.option('--issue-id <id>', 'Linked issue id')
|
|
2967
|
+
.option('--feedback-id <id>', 'Linked feedback id')
|
|
2968
|
+
.option('--next-action <text>', 'Recommended next action')
|
|
2969
|
+
.option('--next-read-ref <value>', 'Repeatable next read ref (string, JSON object/array, or @file.json)', collectRepeatedOption, [])
|
|
2970
|
+
.option('--source-ref <value>', 'Repeatable source ref (string, JSON object/array, or @file.json)', collectRepeatedOption, [])
|
|
2971
|
+
.option('--validation-state <text>', 'Validation state summary')
|
|
2972
|
+
.option('--sticky-rank <n>', 'Sticky ordering rank (higher first)', (value) => Number.parseInt(String(value), 10))
|
|
2973
|
+
.option('--supersede <memoryId>', 'Supersede an older sticky memory id')
|
|
2974
|
+
.option('--pattern-name <text>', 'Reusable rule/pattern name')
|
|
2975
|
+
.option('--pattern-when <text>', 'When this rule applies')
|
|
2976
|
+
.option('--pattern-why <text>', 'Why this rule matters')
|
|
2977
|
+
.option('--pattern-evidence <text>', 'Pattern evidence or notes'))).addHelpText('after', MEMORY_EVIDENCE_HELP_TEXT)).action(async (options) => {
|
|
2978
|
+
await runMemoryUpdate(options);
|
|
2979
|
+
}));
|
|
2980
|
+
applyMemoryContextOptions(applyWriteOptions(applyMemoryReviewOptions(applyMemoryClassificationOptions(cmd.command('write')
|
|
2981
|
+
.description('Advanced: write an Agentspace memory item with standardized metadata')
|
|
2982
|
+
.option('--mode <mode>', 'Memory mode: kickoff, resume, decision, blocker, closeout, rule')
|
|
2983
|
+
.option('--subject <kind>', 'Subject kind: project, ktask, sprint, phase, utask, issue, feedback')
|
|
2984
|
+
.option('--id <id>', 'Subject id (optional for project; defaults to current project id)')
|
|
2985
|
+
.option('--label <text>', 'Optional subject label/title')
|
|
2986
|
+
.option('--durability <durability>', 'Memory durability: short, durable, sticky')
|
|
2987
|
+
.option('--sticky-rank <n>', 'Sticky ordering rank (higher first)', (value) => Number.parseInt(String(value), 10))
|
|
2988
|
+
.option('--supersede <memoryId>', 'Supersede an older sticky memory id')
|
|
2989
|
+
.option('--kind <kind>', 'Memory kind')
|
|
2990
|
+
.requiredOption('--content <textOrFile>', 'Memory content text, or @file to read content from disk')
|
|
2991
|
+
.option('--importance <value>', 'Importance score (0-100)', (value) => Number.parseInt(String(value), 10))
|
|
2992
|
+
.option('--tag <value>', 'Repeatable tag', collectRepeatedOption, [])
|
|
2993
|
+
.option('--source-type <type>', 'Source type')
|
|
2994
|
+
.option('--source-id <id>', 'Source id')
|
|
2995
|
+
.option('--subject-type <type>', 'Subject type')
|
|
2996
|
+
.option('--subject-id <id>', 'Subject id')
|
|
2997
|
+
.option('--subject-title <text>', 'Subject title')
|
|
2998
|
+
.option('--task-id <id>', 'Linked kanban task id')
|
|
2999
|
+
.option('--kanban-task-id <id>', 'Linked kanban task id')
|
|
3000
|
+
.option('--sprint-id <id>', 'Linked sprint id')
|
|
3001
|
+
.option('--phase-id <id>', 'Linked phase id')
|
|
3002
|
+
.option('--utask-id <id>', 'Linked utask id')
|
|
3003
|
+
.option('--microtask-id <id>', 'Linked microtask id')
|
|
3004
|
+
.option('--issue-id <id>', 'Linked issue id')
|
|
3005
|
+
.option('--feedback-id <id>', 'Linked feedback id')
|
|
3006
|
+
.option('--next-action <text>', 'Recommended next action')
|
|
3007
|
+
.option('--next-read-ref <value>', 'Repeatable next read ref (string, JSON object/array, or @file.json)', collectRepeatedOption, [])
|
|
3008
|
+
.option('--source-ref <value>', 'Repeatable source ref (string, JSON object/array, or @file.json)', collectRepeatedOption, [])
|
|
3009
|
+
.option('--validation-state <text>', 'Validation state summary')
|
|
3010
|
+
.option('--pattern-name <text>', 'Reusable rule/pattern name')
|
|
3011
|
+
.option('--pattern-when <text>', 'When this rule applies')
|
|
3012
|
+
.option('--pattern-why <text>', 'Why this rule matters')
|
|
3013
|
+
.option('--pattern-evidence <text>', 'Pattern evidence or notes'))).addHelpText('after', '\nNotes:\n' +
|
|
3014
|
+
' AI/automation default should stay on short memory (kickoff, resume, decision, closeout, blocker).\n' +
|
|
3015
|
+
' Use --mode for common modes; for note, use --kind note --durability short|durable|sticky.\n' +
|
|
3016
|
+
' In PowerShell, quote @file content pointers, for example --content \'@tmp/checkpoint.md\'.\n' +
|
|
3017
|
+
' Durable `note` and sticky `rule` are operator-controlled surfaces; do not write them from agent loops unless explicitly requested.\n' +
|
|
3018
|
+
' If the content is long-lived, prefer a short pointer plus --next-read-ref/--source-ref instead of pasting full documents.\n' +
|
|
3019
|
+
MEMORY_EVIDENCE_HELP_TEXT + '\n')).action(async (options) => {
|
|
3020
|
+
await runMemoryWrite(options);
|
|
3021
|
+
}));
|
|
3022
|
+
applyMemoryContextOptions(applyWriteOptions(applyMemoryClassificationOptions(cmd.command('compact')
|
|
3023
|
+
.description('Build a reviewable compact pack from old short memory and optionally write a summary')
|
|
3024
|
+
.option('--older-than-days <n>', 'Only compact memories older than this many days (default: 30)', (value) => Number.parseInt(String(value), 10))
|
|
3025
|
+
.option('--keep-latest <n>', 'Always keep this many newest matching memories out of the compact source set (default: 20)', (value) => Number.parseInt(String(value), 10))
|
|
3026
|
+
.option('--max-items <n>', 'Maximum number of source memories to include in one compact pack (default: 20)', (value) => Number.parseInt(String(value), 10))
|
|
3027
|
+
.option('--kind <kind>', 'Optional source memory kind filter')
|
|
3028
|
+
.option('--durability <durability>', 'Source durability; V1 only allows short')
|
|
3029
|
+
.option('--target-kind <kind>', 'Summary memory kind to write when --write-summary is used (default: checkpoint)')
|
|
3030
|
+
.option('--target-durability <durability>', 'Summary memory durability: short or durable (default: durable)')
|
|
3031
|
+
.option('--content <text>', 'Reviewed compact summary content; defaults to deterministic draftSummary')
|
|
3032
|
+
.option('--write-summary', 'Write the compact summary as a new hosted memory item')
|
|
3033
|
+
.option('--mark-source', 'Mark source memories as compactedInto the written summary')
|
|
3034
|
+
.option('--prune-source', 'Delete source hosted memory items after writing the summary; requires --apply --confirm')
|
|
3035
|
+
.option('--subject <kind>', 'Subject kind for filters and written summary: project, ktask, sprint, phase, utask, issue, feedback')
|
|
3036
|
+
.option('--id <id>', 'Subject id filter / summary subject id')
|
|
3037
|
+
.option('--subject-type <type>', 'Subject type filter / summary subject type')
|
|
3038
|
+
.option('--subject-id <id>', 'Subject id filter / summary subject id')
|
|
3039
|
+
.option('--task-id <id>', 'Linked kanban task id')
|
|
3040
|
+
.option('--sprint-id <id>', 'Linked sprint id')
|
|
3041
|
+
.option('--phase-id <id>', 'Linked phase id')
|
|
3042
|
+
.option('--utask-id <id>', 'Linked utask id')
|
|
3043
|
+
.option('--issue-id <id>', 'Linked issue id')
|
|
3044
|
+
.option('--feedback-id <id>', 'Linked feedback id'))).action(async (options) => {
|
|
3045
|
+
await runMemoryCompact(options);
|
|
3046
|
+
}));
|
|
3047
|
+
applyMemoryContextOptions(applyWriteOptions(applyMemoryClassificationOptions(cmd.command('prune')
|
|
3048
|
+
.description('Prune old hosted short memory items with a dry-run first')
|
|
3049
|
+
.option('--older-than-days <n>', 'Only prune memories older than this many days (default: 30)', (value) => Number.parseInt(String(value), 10))
|
|
3050
|
+
.option('--keep-latest <n>', 'Always keep this many newest matching memories (default: 20)', (value) => Number.parseInt(String(value), 10))
|
|
3051
|
+
.option('--max-delete <n>', 'Maximum number of hosted memory items to delete in one apply run', (value) => Number.parseInt(String(value), 10))
|
|
3052
|
+
.option('--include-synced', 'Deprecated no-op (server-first): retained for script compatibility; prune operates on hosted memory items')
|
|
3053
|
+
.option('--kind <kind>', 'Optional memory kind filter')
|
|
3054
|
+
.option('--durability <durability>', 'Retention target; V1 only allows short')
|
|
3055
|
+
.option('--subject <kind>', 'Subject kind: project, ktask, sprint, phase, utask, issue, feedback')
|
|
3056
|
+
.option('--id <id>', 'Subject id filter')
|
|
3057
|
+
.option('--subject-type <type>', 'Subject type filter')
|
|
3058
|
+
.option('--subject-id <id>', 'Subject id filter')
|
|
3059
|
+
.option('--task-id <id>', 'Linked kanban task id')
|
|
3060
|
+
.option('--sprint-id <id>', 'Linked sprint id')
|
|
3061
|
+
.option('--phase-id <id>', 'Linked phase id')
|
|
3062
|
+
.option('--utask-id <id>', 'Linked utask id')
|
|
3063
|
+
.option('--issue-id <id>', 'Linked issue id')
|
|
3064
|
+
.option('--feedback-id <id>', 'Linked feedback id'))).action(async (options) => {
|
|
3065
|
+
await runMemoryPrune(options);
|
|
3066
|
+
}));
|
|
3067
|
+
applyMemoryContextOptions(applyWriteOptions(cmd.command('delete')
|
|
3068
|
+
.description('Advanced: delete a memory item by id')
|
|
3069
|
+
.requiredOption('--id <id>', 'Memory item id')).action(async (options) => {
|
|
3070
|
+
await runMemoryDelete(options);
|
|
3071
|
+
}));
|
|
3072
|
+
cmd.addHelpText('before', `
|
|
3073
|
+
Recommended path:
|
|
3074
|
+
1. mem brief
|
|
3075
|
+
2. mem synopsis
|
|
3076
|
+
3. mem search
|
|
3077
|
+
4. mem resume
|
|
3078
|
+
5. mem checkpoint
|
|
3079
|
+
|
|
3080
|
+
Advanced surface:
|
|
3081
|
+
mem summary, mem write, mem update, mem compact, mem prune, mem delete, mem doc *
|
|
3082
|
+
`);
|
|
3083
|
+
cmd.addHelpText('after', buildOperatorCookbook({
|
|
3084
|
+
examples: [
|
|
3085
|
+
'aops-cli mem brief --subject project --json',
|
|
3086
|
+
'aops-cli mem list --subject project --json',
|
|
3087
|
+
'aops-cli mem synopsis --subject project --json',
|
|
3088
|
+
'aops-cli mem get --id <memory-id> --json',
|
|
3089
|
+
'aops-cli mem resume --subject sprint --id <sprint-id> --json',
|
|
3090
|
+
'aops-cli mem checkpoint --content "Slice 1 devam ediyor." --task-id <task-id> --apply --json',
|
|
3091
|
+
'aops-cli mem summary --content "Session summary." --apply --json',
|
|
3092
|
+
'aops-cli mem doc refs --subject project --json',
|
|
3093
|
+
'aops-cli mem doc answer --subject sprint --id <sprint-id> --q "What changed?" --ensure summary --json',
|
|
3094
|
+
'aops-cli mem write --kind kickoff --durability short --subject project --content "Yarin buradan devam et." --apply --json',
|
|
3095
|
+
'aops-cli mem write --kind resume --durability short --subject sprint --content "Buradan devam et." --next-read-ref @./next-read-ref.json --apply --json',
|
|
3096
|
+
"aops-cli mem write --kind resume --durability short --subject project --content '@tmp/checkpoint.md' --apply --json",
|
|
3097
|
+
'aops-cli mem write --kind resume --durability short --subject project --content "Yerel carry-forward" --apply --json',
|
|
3098
|
+
'aops-cli mem resume --subject project --json',
|
|
3099
|
+
'aops-cli mem write --kind rule --durability sticky --subject project --purpose howto --area adk-electron --content "ADK komutunu once manifest sync, sonra electron bridge ile bagla." --review-after-days 30 --sticky-rank 90 --apply --json',
|
|
3100
|
+
'aops-cli mem update --id <memory-id> --kind closeout --durability short --content "Guncel ozet" --apply --json',
|
|
3101
|
+
'aops-cli mem compact --older-than-days 30 --keep-latest 20 --json',
|
|
3102
|
+
'aops-cli mem compact --older-than-days 30 --keep-latest 20 --write-summary --mark-source --apply --json',
|
|
3103
|
+
'aops-cli mem prune --older-than-days 30 --keep-latest 20 --json',
|
|
3104
|
+
'aops-cli mem prune --older-than-days 30 --keep-latest 20 --apply --confirm --json',
|
|
3105
|
+
'aops-cli mem delete --id <memory-id> --apply --confirm --json',
|
|
3106
|
+
'aops-cli mem search --q "staged migration" --subject project --json',
|
|
3107
|
+
'aops-cli mem resume --subject project --q "current slice" --limit 5 --json',
|
|
3108
|
+
'aops-cli mem search --q "adk electron bridge" --purpose howto --area adk-electron --strict-classification --json',
|
|
3109
|
+
],
|
|
3110
|
+
guide: GUIDE_PATHS.agentspace,
|
|
3111
|
+
notes: [
|
|
3112
|
+
'`mem` primary operator surface\'idir; `memory` legacy alias olarak korunur.',
|
|
3113
|
+
'memory neyin okunacagini soyler; Docman canonical icerigi verir; Projectman execution state PM uzerinden yonetilir.',
|
|
3114
|
+
'AI/automation varsayilani short memory olmalidir; durable `note` ve sticky `rule` yalnizca operator/human acikca isterse yazilmalidir.',
|
|
3115
|
+
'`--mode` kickoff/resume/decision/blocker/closeout/rule kisayollaridir; `note` icin `--kind note --durability short|durable|sticky` kullan.',
|
|
3116
|
+
"PowerShell icinde @file content pointerlarini tirnakla yaz: `--content '@tmp/checkpoint.md'`.",
|
|
3117
|
+
'Memory backend SERVER-FIRST hosted Agentspace memory-item ops uzerindendir (create/write/read hepsi hosted); local `.aops/agentspace/memory` source-of-truth DEGIL, yalnizca read-only file-search cache.',
|
|
3118
|
+
'`handoff` gorunumu canonical `resume` kayitlarinin alias\'idir.',
|
|
3119
|
+
'--content zorunludur; @file ile dosyadan okunur (or paste inline); uzun canonical icerik yerine --next-read-ref/--source-ref ile pointer ver.',
|
|
3120
|
+
'Soft TTL silme yapmaz; expired memory tutulur ama retrieval tarafinda asagi itilir.',
|
|
3121
|
+
'`mem compact` short memory kayitlarini reviewable pack ve opsiyonel durable summary haline getirir; durable/sticky source memory default compact edilmez.',
|
|
3122
|
+
'`mem prune` hosted short memory icin dry-run first retention yuzeyidir (list->remove orchestration); destructive silme --apply --confirm ister.',
|
|
3123
|
+
'memory search icinde --purpose/--area/--status varsayilan olarak retrieval hint\'idir; strict filtre icin --strict-classification kullan.',
|
|
3124
|
+
'`mem search|resume|synopsis --q` kisa alias olarak `--query` ile aynidir; genis project context icin `--limit` ile birlikte kullan.',
|
|
3125
|
+
'Canonical uzun anlatim gerekiyorsa tam metin yerine nextReadRefs/sourceRefs ile Docman pointer\'i ver.',
|
|
3126
|
+
],
|
|
3127
|
+
}));
|
|
3128
|
+
return cmd;
|
|
3129
|
+
}
|