@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,549 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import { logSuccess } from '@aopslabs/xf-cli-ui';
|
|
4
|
+
import { compactPayload, normalizeNonEmpty } from '../utils/command.js';
|
|
5
|
+
import { resolveOwnerScopeIdFromBinding, resolveProjectBindingContext, } from '../utils/project-context.js';
|
|
6
|
+
import { createNewLocalMemoryEntry, readLocalMemoryEntries, rebuildLocalMemoryWorkspace, resolveMemoryWorkspacePaths, writeLocalMemoryEntryFile, } from '../utils/memory-workspace.js';
|
|
7
|
+
const REPO_FIRST_CHECKPOINT_TOOL_ID = 'repo-first.checkpoint';
|
|
8
|
+
const CHECKPOINT_SCHEMA_VERSION = 1;
|
|
9
|
+
const MISSION_SUBJECT_TYPE = 'agentspace.mission';
|
|
10
|
+
const PROJECT_SUBJECT_TYPE = 'projectman.plan';
|
|
11
|
+
function isRecord(value) {
|
|
12
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
13
|
+
}
|
|
14
|
+
function toStringArray(value) {
|
|
15
|
+
if (!Array.isArray(value))
|
|
16
|
+
return [];
|
|
17
|
+
return value.map((entry) => normalizeNonEmpty(entry)).filter((entry) => Boolean(entry));
|
|
18
|
+
}
|
|
19
|
+
function uniqueStrings(values) {
|
|
20
|
+
const seen = new Set();
|
|
21
|
+
const result = [];
|
|
22
|
+
for (const value of values) {
|
|
23
|
+
const normalized = normalizeNonEmpty(value);
|
|
24
|
+
if (!normalized)
|
|
25
|
+
continue;
|
|
26
|
+
const key = normalized.toLowerCase();
|
|
27
|
+
if (seen.has(key))
|
|
28
|
+
continue;
|
|
29
|
+
seen.add(key);
|
|
30
|
+
result.push(normalized);
|
|
31
|
+
}
|
|
32
|
+
return result;
|
|
33
|
+
}
|
|
34
|
+
function collectRepeatedOption(value, previous = []) {
|
|
35
|
+
return [...previous, value];
|
|
36
|
+
}
|
|
37
|
+
function expandAtFileContent(value) {
|
|
38
|
+
const normalized = normalizeNonEmpty(value);
|
|
39
|
+
if (!normalized)
|
|
40
|
+
return undefined;
|
|
41
|
+
if (!normalized.startsWith('@'))
|
|
42
|
+
return normalized;
|
|
43
|
+
const filePath = normalized.slice(1);
|
|
44
|
+
if (!filePath)
|
|
45
|
+
throw new Error('Content file pointer cannot be empty.');
|
|
46
|
+
return readFileSync(filePath, 'utf8');
|
|
47
|
+
}
|
|
48
|
+
function parseRefValue(value, flagName) {
|
|
49
|
+
const expanded = expandAtFileContent(value);
|
|
50
|
+
const normalized = normalizeNonEmpty(expanded);
|
|
51
|
+
if (!normalized)
|
|
52
|
+
return undefined;
|
|
53
|
+
if (!normalized.startsWith('{') && !normalized.startsWith('['))
|
|
54
|
+
return normalized;
|
|
55
|
+
try {
|
|
56
|
+
return JSON.parse(normalized);
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
60
|
+
throw new Error(`${flagName} must be a string, JSON object/array, or @file.json with valid JSON: ${message}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function parseRefValues(values, flagName) {
|
|
64
|
+
return toStringArray(values)
|
|
65
|
+
.map((value) => parseRefValue(value, flagName))
|
|
66
|
+
.flatMap((value) => (Array.isArray(value) ? value : [value]))
|
|
67
|
+
.filter((value) => value !== undefined);
|
|
68
|
+
}
|
|
69
|
+
function memoryEntryId(entry) {
|
|
70
|
+
return normalizeNonEmpty(entry.memoryId) ?? normalizeNonEmpty(entry.id);
|
|
71
|
+
}
|
|
72
|
+
function checkpointAs(entry) {
|
|
73
|
+
const rawValue = normalizeNonEmpty(entry.raw?.checkpointAs);
|
|
74
|
+
if (rawValue)
|
|
75
|
+
return rawValue;
|
|
76
|
+
return (entry.kind ?? '').toLowerCase() === 'checkpoint' ? 'session' : undefined;
|
|
77
|
+
}
|
|
78
|
+
function entryTags(entry) {
|
|
79
|
+
return new Set(toStringArray(entry.tags).map((value) => value.toLowerCase()));
|
|
80
|
+
}
|
|
81
|
+
function isCheckpointEntry(entry, includeLegacy = true) {
|
|
82
|
+
const kind = (entry.kind ?? '').toLowerCase();
|
|
83
|
+
const asValue = checkpointAs(entry);
|
|
84
|
+
if (asValue === 'milestone')
|
|
85
|
+
return false;
|
|
86
|
+
if (kind === 'checkpoint')
|
|
87
|
+
return true;
|
|
88
|
+
return includeLegacy && entryTags(entry).has('memory:checkpoint');
|
|
89
|
+
}
|
|
90
|
+
function sameSubject(entry, subject) {
|
|
91
|
+
const subjectType = normalizeNonEmpty(subject.subjectType);
|
|
92
|
+
const subjectId = normalizeNonEmpty(subject.subjectId);
|
|
93
|
+
if (subjectType && normalizeNonEmpty(entry.subjectType) !== subjectType)
|
|
94
|
+
return false;
|
|
95
|
+
if (subjectId && normalizeNonEmpty(entry.subjectId) !== subjectId)
|
|
96
|
+
return false;
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
function subjectKey(entry) {
|
|
100
|
+
return `${normalizeNonEmpty(entry.subjectType) ?? 'subject'}::${normalizeNonEmpty(entry.subjectId) ?? 'unknown'}`;
|
|
101
|
+
}
|
|
102
|
+
function buildSupersededIdSet(entries) {
|
|
103
|
+
const superseded = new Set();
|
|
104
|
+
for (const entry of entries) {
|
|
105
|
+
const supersedes = normalizeNonEmpty(entry.raw?.supersedes);
|
|
106
|
+
if (supersedes)
|
|
107
|
+
superseded.add(supersedes);
|
|
108
|
+
}
|
|
109
|
+
return superseded;
|
|
110
|
+
}
|
|
111
|
+
function buildCheckpointState(entries, includeLegacy = true) {
|
|
112
|
+
const checkpoints = entries.filter((entry) => isCheckpointEntry(entry, includeLegacy));
|
|
113
|
+
const supersededIds = buildSupersededIdSet(checkpoints);
|
|
114
|
+
const currentIds = new Set();
|
|
115
|
+
const seenSubjects = new Set();
|
|
116
|
+
for (const entry of checkpoints) {
|
|
117
|
+
const id = memoryEntryId(entry);
|
|
118
|
+
if (!id || supersededIds.has(id))
|
|
119
|
+
continue;
|
|
120
|
+
const key = subjectKey(entry);
|
|
121
|
+
if (seenSubjects.has(key))
|
|
122
|
+
continue;
|
|
123
|
+
seenSubjects.add(key);
|
|
124
|
+
currentIds.add(id);
|
|
125
|
+
}
|
|
126
|
+
return { supersededIds, currentIds };
|
|
127
|
+
}
|
|
128
|
+
function resolveCheckpointSubject(options, context) {
|
|
129
|
+
const missionId = normalizeNonEmpty(options.missionId);
|
|
130
|
+
if (missionId) {
|
|
131
|
+
return {
|
|
132
|
+
subjectType: MISSION_SUBJECT_TYPE,
|
|
133
|
+
subjectId: missionId,
|
|
134
|
+
subjectTitle: normalizeNonEmpty(options.missionSlug) ?? `mission:${missionId}`,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
const subjectId = normalizeNonEmpty(options.subjectId) ?? resolveOwnerScopeIdFromBinding(context) ?? normalizeNonEmpty(context.projectId);
|
|
138
|
+
if (!subjectId) {
|
|
139
|
+
throw new Error('Checkpoint subject could not be resolved. Provide --mission-id, --subject-id, --project-id, or repo config.');
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
subjectType: normalizeNonEmpty(options.subjectType) ?? PROJECT_SUBJECT_TYPE,
|
|
143
|
+
subjectId,
|
|
144
|
+
subjectTitle: normalizeNonEmpty(options.subjectTitle) ?? normalizeNonEmpty(context.projectName) ?? `project:${subjectId}`,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
function resolveReadSubject(options) {
|
|
148
|
+
const missionId = normalizeNonEmpty(options.missionId);
|
|
149
|
+
if (missionId)
|
|
150
|
+
return { subjectType: MISSION_SUBJECT_TYPE, subjectId: missionId };
|
|
151
|
+
const subjectType = normalizeNonEmpty(options.subjectType);
|
|
152
|
+
const subjectId = normalizeNonEmpty(options.subjectId);
|
|
153
|
+
if (!subjectType && !subjectId)
|
|
154
|
+
return undefined;
|
|
155
|
+
return { subjectType, subjectId };
|
|
156
|
+
}
|
|
157
|
+
function resolveCurrentReadSubject(options, context) {
|
|
158
|
+
const explicit = resolveReadSubject(options);
|
|
159
|
+
if (explicit)
|
|
160
|
+
return explicit;
|
|
161
|
+
return resolveCheckpointSubject({}, context);
|
|
162
|
+
}
|
|
163
|
+
function buildLinkedIds(options) {
|
|
164
|
+
return compactPayload({
|
|
165
|
+
kanbanTaskId: normalizeNonEmpty(options.kanbanTaskId) ?? normalizeNonEmpty(options.taskId),
|
|
166
|
+
sprintId: normalizeNonEmpty(options.sprintId),
|
|
167
|
+
phaseId: normalizeNonEmpty(options.phaseId),
|
|
168
|
+
microtaskId: normalizeNonEmpty(options.microtaskId) ?? normalizeNonEmpty(options.utaskId),
|
|
169
|
+
issueId: normalizeNonEmpty(options.issueId),
|
|
170
|
+
feedbackId: normalizeNonEmpty(options.feedbackId),
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
function buildCheckpointContent(options) {
|
|
174
|
+
const summary = normalizeNonEmpty(options.summary);
|
|
175
|
+
if (!summary)
|
|
176
|
+
throw new Error('Provide --summary for checkpoint create.');
|
|
177
|
+
const position = normalizeNonEmpty(options.position);
|
|
178
|
+
const doneWork = toStringArray(options.doneWork);
|
|
179
|
+
const nextSteps = toStringArray(options.nextStep);
|
|
180
|
+
const notes = normalizeNonEmpty(expandAtFileContent(options.content));
|
|
181
|
+
const lines = ['# Checkpoint', '', '## Summary', summary, ''];
|
|
182
|
+
if (position)
|
|
183
|
+
lines.push('## Position', position, '');
|
|
184
|
+
if (doneWork.length > 0) {
|
|
185
|
+
lines.push('## Done Work');
|
|
186
|
+
doneWork.forEach((item) => lines.push(`- ${item}`));
|
|
187
|
+
lines.push('');
|
|
188
|
+
}
|
|
189
|
+
if (nextSteps.length > 0) {
|
|
190
|
+
lines.push('## Next Steps');
|
|
191
|
+
nextSteps.forEach((item) => lines.push(`- ${item}`));
|
|
192
|
+
lines.push('');
|
|
193
|
+
}
|
|
194
|
+
if (notes)
|
|
195
|
+
lines.push('## Notes', notes, '');
|
|
196
|
+
return `${lines.join('\n').trim()}\n`;
|
|
197
|
+
}
|
|
198
|
+
function resolvePreviousCheckpointId(entries, subject, explicitSupersede) {
|
|
199
|
+
const explicit = normalizeNonEmpty(explicitSupersede);
|
|
200
|
+
if (explicit)
|
|
201
|
+
return explicit;
|
|
202
|
+
const state = buildCheckpointState(entries, true);
|
|
203
|
+
const previous = entries.find((entry) => {
|
|
204
|
+
const id = memoryEntryId(entry);
|
|
205
|
+
return Boolean(id && state.currentIds.has(id) && sameSubject(entry, subject));
|
|
206
|
+
});
|
|
207
|
+
return previous ? memoryEntryId(previous) : undefined;
|
|
208
|
+
}
|
|
209
|
+
function buildCheckpointEntry(params) {
|
|
210
|
+
const { options, context, subject, previousCheckpointId } = params;
|
|
211
|
+
const linked = buildLinkedIds(options);
|
|
212
|
+
const sourceRefs = parseRefValues(options.sourceRef, '--source-ref');
|
|
213
|
+
const nextReadRefs = parseRefValues(options.nextReadRef, '--next-read-ref');
|
|
214
|
+
const doneWork = toStringArray(options.doneWork);
|
|
215
|
+
const nextSteps = toStringArray(options.nextStep);
|
|
216
|
+
const timestamp = new Date().toISOString();
|
|
217
|
+
const anchors = compactPayload({
|
|
218
|
+
missionId: normalizeNonEmpty(options.missionId),
|
|
219
|
+
missionSlug: normalizeNonEmpty(options.missionSlug),
|
|
220
|
+
sessionId: normalizeNonEmpty(options.sessionId),
|
|
221
|
+
projectId: normalizeNonEmpty(context.projectId),
|
|
222
|
+
projectSlug: normalizeNonEmpty(context.projectSlug),
|
|
223
|
+
agent: normalizeNonEmpty(options.agent),
|
|
224
|
+
timestamp,
|
|
225
|
+
});
|
|
226
|
+
return createNewLocalMemoryEntry({
|
|
227
|
+
kind: 'checkpoint',
|
|
228
|
+
durability: 'short',
|
|
229
|
+
content: buildCheckpointContent(options),
|
|
230
|
+
subjectType: subject.subjectType,
|
|
231
|
+
subjectId: subject.subjectId,
|
|
232
|
+
subjectTitle: subject.subjectTitle,
|
|
233
|
+
projectId: normalizeNonEmpty(context.projectId),
|
|
234
|
+
importance: options.importance,
|
|
235
|
+
nextAction: normalizeNonEmpty(options.nextAction) ?? nextSteps[0],
|
|
236
|
+
validationState: normalizeNonEmpty(options.validationState),
|
|
237
|
+
sourceRefs,
|
|
238
|
+
nextReadRefs,
|
|
239
|
+
purpose: ['checkpoint', 'session'],
|
|
240
|
+
areas: [],
|
|
241
|
+
status: ['active'],
|
|
242
|
+
tags: uniqueStrings([
|
|
243
|
+
'memory:checkpoint',
|
|
244
|
+
'checkpoint:session',
|
|
245
|
+
context.projectId ? `project:${context.projectId}` : undefined,
|
|
246
|
+
options.missionId ? `mission:${options.missionId}` : undefined,
|
|
247
|
+
linked.kanbanTaskId ? `kanban-task:${linked.kanbanTaskId}` : undefined,
|
|
248
|
+
linked.sprintId ? `sprint:${linked.sprintId}` : undefined,
|
|
249
|
+
linked.phaseId ? `phase:${linked.phaseId}` : undefined,
|
|
250
|
+
linked.microtaskId ? `microtask:${linked.microtaskId}` : undefined,
|
|
251
|
+
linked.issueId ? `issue:${linked.issueId}` : undefined,
|
|
252
|
+
linked.feedbackId ? `feedback:${linked.feedbackId}` : undefined,
|
|
253
|
+
...toStringArray(options.tag),
|
|
254
|
+
]),
|
|
255
|
+
raw: compactPayload({
|
|
256
|
+
checkpointAs: 'session',
|
|
257
|
+
supersedes: previousCheckpointId,
|
|
258
|
+
checkpoint: compactPayload({
|
|
259
|
+
schemaVersion: CHECKPOINT_SCHEMA_VERSION,
|
|
260
|
+
summary: normalizeNonEmpty(options.summary),
|
|
261
|
+
position: normalizeNonEmpty(options.position),
|
|
262
|
+
doneWork,
|
|
263
|
+
nextSteps,
|
|
264
|
+
sourceRefs,
|
|
265
|
+
anchors,
|
|
266
|
+
}),
|
|
267
|
+
}),
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
function summarizeContent(content) {
|
|
271
|
+
return normalizeNonEmpty(content.replace(/^# .+$/m, '').split('\n').map((line) => line.trim()).find(Boolean));
|
|
272
|
+
}
|
|
273
|
+
function normalizeCheckpointRecord(entry, state, options = {}) {
|
|
274
|
+
const id = memoryEntryId(entry);
|
|
275
|
+
const rawCheckpoint = isRecord(entry.raw?.checkpoint) ? entry.raw.checkpoint : {};
|
|
276
|
+
const asValue = checkpointAs(entry);
|
|
277
|
+
return compactPayload({
|
|
278
|
+
id,
|
|
279
|
+
kind: entry.kind,
|
|
280
|
+
checkpointAs: asValue,
|
|
281
|
+
current: id ? state.currentIds.has(id) : false,
|
|
282
|
+
superseded: id ? state.supersededIds.has(id) : false,
|
|
283
|
+
supersedes: normalizeNonEmpty(entry.raw?.supersedes),
|
|
284
|
+
legacy: (entry.kind ?? '').toLowerCase() !== 'checkpoint',
|
|
285
|
+
summary: normalizeNonEmpty(rawCheckpoint.summary) ?? summarizeContent(entry.content),
|
|
286
|
+
position: normalizeNonEmpty(rawCheckpoint.position),
|
|
287
|
+
doneWork: Array.isArray(rawCheckpoint.doneWork) ? rawCheckpoint.doneWork : undefined,
|
|
288
|
+
nextSteps: Array.isArray(rawCheckpoint.nextSteps) ? rawCheckpoint.nextSteps : undefined,
|
|
289
|
+
subject: compactPayload({
|
|
290
|
+
type: entry.subjectType,
|
|
291
|
+
id: entry.subjectId,
|
|
292
|
+
title: entry.subjectTitle,
|
|
293
|
+
}),
|
|
294
|
+
anchors: isRecord(rawCheckpoint.anchors) ? compactPayload(rawCheckpoint.anchors) : undefined,
|
|
295
|
+
tags: entry.tags,
|
|
296
|
+
sourceRefs: entry.sourceRefs,
|
|
297
|
+
nextReadRefs: entry.nextReadRefs,
|
|
298
|
+
nextAction: entry.nextAction,
|
|
299
|
+
validationState: entry.validationState,
|
|
300
|
+
createdAt: entry.createdAt,
|
|
301
|
+
updatedAt: entry.updatedAt,
|
|
302
|
+
storage: entry.storage ?? 'local-cache',
|
|
303
|
+
content: options.full === true ? entry.content : undefined,
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
function buildResolvedContextRecord(context) {
|
|
307
|
+
return compactPayload({
|
|
308
|
+
repoRoot: context.repoRoot,
|
|
309
|
+
configPath: context.configPath,
|
|
310
|
+
configFound: context.configFound,
|
|
311
|
+
projectId: context.projectId,
|
|
312
|
+
scopeId: context.scopeId,
|
|
313
|
+
projectName: context.projectName,
|
|
314
|
+
projectSlug: context.projectSlug,
|
|
315
|
+
localRoot: context.localRoot,
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
function buildEnvelope(params) {
|
|
319
|
+
return compactPayload({
|
|
320
|
+
command: params.command,
|
|
321
|
+
toolId: REPO_FIRST_CHECKPOINT_TOOL_ID,
|
|
322
|
+
resolvedContext: buildResolvedContextRecord(params.context),
|
|
323
|
+
input: params.input,
|
|
324
|
+
result: params.result,
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
async function resolveCheckpointContext(options, params = {}) {
|
|
328
|
+
if (options.hosted === true) {
|
|
329
|
+
throw new Error('Hosted checkpoint facade waits for S2 Agentspace schema support. Use repo-first checkpoint commands for S1.');
|
|
330
|
+
}
|
|
331
|
+
const resolved = await resolveProjectBindingContext(options, {
|
|
332
|
+
requireProject: params.requireScope === true,
|
|
333
|
+
});
|
|
334
|
+
const scopeId = normalizeNonEmpty(options.scopeId) ?? resolveOwnerScopeIdFromBinding(resolved);
|
|
335
|
+
if (params.requireScope === true && !scopeId && !normalizeNonEmpty(resolved.projectId)) {
|
|
336
|
+
throw new Error('Checkpoint context could not be resolved. Provide --project-id/--project-name/--project-slug or repo config.');
|
|
337
|
+
}
|
|
338
|
+
return { ...resolved, scopeId };
|
|
339
|
+
}
|
|
340
|
+
async function loadCheckpointEntries(context) {
|
|
341
|
+
const paths = resolveMemoryWorkspacePaths(context);
|
|
342
|
+
const entries = await readLocalMemoryEntries(paths.localItemsDir);
|
|
343
|
+
await rebuildLocalMemoryWorkspace({
|
|
344
|
+
...context,
|
|
345
|
+
items: entries,
|
|
346
|
+
});
|
|
347
|
+
return entries;
|
|
348
|
+
}
|
|
349
|
+
function filterCheckpointEntries(entries, options) {
|
|
350
|
+
const includeLegacy = options.includeLegacy !== false;
|
|
351
|
+
const all = entries.filter((entry) => isCheckpointEntry(entry, includeLegacy));
|
|
352
|
+
const state = buildCheckpointState(all, includeLegacy);
|
|
353
|
+
const filtered = all
|
|
354
|
+
.filter((entry) => !options.subject || sameSubject(entry, options.subject))
|
|
355
|
+
.filter((entry) => {
|
|
356
|
+
if (options.current !== true)
|
|
357
|
+
return true;
|
|
358
|
+
const id = memoryEntryId(entry);
|
|
359
|
+
return Boolean(id && state.currentIds.has(id));
|
|
360
|
+
})
|
|
361
|
+
.slice(0, options.limit);
|
|
362
|
+
return { entries: filtered, state };
|
|
363
|
+
}
|
|
364
|
+
export async function runCheckpointCreate(options = {}) {
|
|
365
|
+
const context = await resolveCheckpointContext(options, { requireScope: true });
|
|
366
|
+
const subject = resolveCheckpointSubject(options, context);
|
|
367
|
+
const paths = resolveMemoryWorkspacePaths(context);
|
|
368
|
+
const existingEntries = await readLocalMemoryEntries(paths.localItemsDir);
|
|
369
|
+
const previousCheckpointId = resolvePreviousCheckpointId(existingEntries, subject, options.supersede);
|
|
370
|
+
const entry = buildCheckpointEntry({ options, context, subject, previousCheckpointId });
|
|
371
|
+
if (options.preview === true) {
|
|
372
|
+
const result = { preview: true, wouldWrite: normalizeCheckpointRecord(entry, buildCheckpointState([...existingEntries, entry]), { full: true }) };
|
|
373
|
+
if (options.json) {
|
|
374
|
+
console.log(JSON.stringify(buildEnvelope({
|
|
375
|
+
command: 'checkpoint.create',
|
|
376
|
+
context,
|
|
377
|
+
input: compactPayload({ repoFirst: true, preview: true }),
|
|
378
|
+
result,
|
|
379
|
+
}), null, 2));
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
logSuccess('Repo-first checkpoint preview completed.');
|
|
383
|
+
console.log(JSON.stringify(result, null, 2));
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
if (options.apply !== true) {
|
|
387
|
+
throw new Error('Retry with --apply because checkpoint create writes repo-first memory.');
|
|
388
|
+
}
|
|
389
|
+
await writeLocalMemoryEntryFile(paths.localItemsDir, entry);
|
|
390
|
+
const nextEntries = await readLocalMemoryEntries(paths.localItemsDir);
|
|
391
|
+
await rebuildLocalMemoryWorkspace({
|
|
392
|
+
...context,
|
|
393
|
+
items: nextEntries,
|
|
394
|
+
});
|
|
395
|
+
const result = { data: normalizeCheckpointRecord(entry, buildCheckpointState(nextEntries), { full: true }) };
|
|
396
|
+
if (options.json) {
|
|
397
|
+
console.log(JSON.stringify(buildEnvelope({
|
|
398
|
+
command: 'checkpoint.create',
|
|
399
|
+
context,
|
|
400
|
+
input: compactPayload({ repoFirst: true, subject }),
|
|
401
|
+
result,
|
|
402
|
+
}), null, 2));
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
logSuccess('Repo-first checkpoint written.');
|
|
406
|
+
console.log(JSON.stringify(result, null, 2));
|
|
407
|
+
}
|
|
408
|
+
export async function runCheckpointList(options = {}) {
|
|
409
|
+
const context = await resolveCheckpointContext(options, { requireScope: true });
|
|
410
|
+
const entries = await loadCheckpointEntries(context);
|
|
411
|
+
const subject = resolveReadSubject(options);
|
|
412
|
+
const { entries: filtered, state } = filterCheckpointEntries(entries, {
|
|
413
|
+
subject,
|
|
414
|
+
current: options.current === true && options.timeline !== true,
|
|
415
|
+
includeLegacy: options.includeLegacy,
|
|
416
|
+
limit: options.limit,
|
|
417
|
+
});
|
|
418
|
+
const result = {
|
|
419
|
+
mode: options.current === true && options.timeline !== true ? 'current' : 'timeline',
|
|
420
|
+
data: filtered.map((entry) => normalizeCheckpointRecord(entry, state)),
|
|
421
|
+
};
|
|
422
|
+
if (options.json) {
|
|
423
|
+
console.log(JSON.stringify(buildEnvelope({
|
|
424
|
+
command: 'checkpoint.list',
|
|
425
|
+
context,
|
|
426
|
+
input: compactPayload({ repoFirst: true, subject, current: options.current, timeline: options.timeline }),
|
|
427
|
+
result,
|
|
428
|
+
}), null, 2));
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
logSuccess('Repo-first checkpoints loaded.');
|
|
432
|
+
console.log(JSON.stringify(result, null, 2));
|
|
433
|
+
}
|
|
434
|
+
export async function runCheckpointGet(options = {}) {
|
|
435
|
+
const context = await resolveCheckpointContext(options, { requireScope: true });
|
|
436
|
+
const entries = await loadCheckpointEntries(context);
|
|
437
|
+
const includeLegacy = options.includeLegacy !== false;
|
|
438
|
+
const state = buildCheckpointState(entries.filter((entry) => isCheckpointEntry(entry, includeLegacy)), includeLegacy);
|
|
439
|
+
const id = normalizeNonEmpty(options.id);
|
|
440
|
+
let currentSubject;
|
|
441
|
+
let entry;
|
|
442
|
+
if (id) {
|
|
443
|
+
entry = entries.find((candidate) => memoryEntryId(candidate) === id && isCheckpointEntry(candidate, includeLegacy));
|
|
444
|
+
}
|
|
445
|
+
else {
|
|
446
|
+
const subject = resolveCurrentReadSubject(options, context);
|
|
447
|
+
currentSubject = subject;
|
|
448
|
+
entry = entries.find((candidate) => {
|
|
449
|
+
const candidateId = memoryEntryId(candidate);
|
|
450
|
+
return Boolean(candidateId && state.currentIds.has(candidateId) && sameSubject(candidate, subject));
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
if (!entry)
|
|
454
|
+
throw new Error(id ? 'Repo-first checkpoint was not found.' : 'No current repo-first checkpoint was found for the requested subject.');
|
|
455
|
+
const result = { data: normalizeCheckpointRecord(entry, state, { full: options.full === true }) };
|
|
456
|
+
if (options.json) {
|
|
457
|
+
console.log(JSON.stringify(buildEnvelope({
|
|
458
|
+
command: 'checkpoint.get',
|
|
459
|
+
context,
|
|
460
|
+
input: compactPayload({ repoFirst: true, id, subject: currentSubject, current: !id }),
|
|
461
|
+
result,
|
|
462
|
+
}), null, 2));
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
logSuccess('Repo-first checkpoint loaded.');
|
|
466
|
+
console.log(JSON.stringify(result, null, 2));
|
|
467
|
+
}
|
|
468
|
+
function applyCheckpointContextOptions(cmd) {
|
|
469
|
+
cmd.option('--project-id <id>', 'Project id');
|
|
470
|
+
cmd.option('--project-name <name>', 'Project name');
|
|
471
|
+
cmd.option('--project-slug <slug>', 'Project slug');
|
|
472
|
+
cmd.option('--scope-id <id>', 'Legacy/internal scope id alias');
|
|
473
|
+
cmd.option('--file-based', 'Use repo-first file backend (default)');
|
|
474
|
+
cmd.option('--hosted', 'Use hosted checkpoint backend (not available until S2 schema support)');
|
|
475
|
+
cmd.option('--json', 'Output JSON only');
|
|
476
|
+
return cmd;
|
|
477
|
+
}
|
|
478
|
+
function applyCheckpointWriteOptions(cmd) {
|
|
479
|
+
cmd.option('--preview', 'Return a validated preflight summary without writing');
|
|
480
|
+
cmd.option('--apply', 'Explicitly allow guarded write operations');
|
|
481
|
+
cmd.option('--confirm', 'Reserved for destructive operations; not needed for create');
|
|
482
|
+
cmd.option('--idempotency-key <key>', 'Optional guarded-write idempotency key');
|
|
483
|
+
return cmd;
|
|
484
|
+
}
|
|
485
|
+
export function makeCheckpointCommand() {
|
|
486
|
+
const cmd = new Command('checkpoint')
|
|
487
|
+
.description('Server-first session checkpoint facade over hosted Agentspace memory');
|
|
488
|
+
applyCheckpointContextOptions(applyCheckpointWriteOptions(cmd.command('create')
|
|
489
|
+
.description('Write a structured short session checkpoint')
|
|
490
|
+
.requiredOption('--summary <text>', 'One-sentence carry-forward summary')
|
|
491
|
+
.option('--position <text>', 'Current position or where to resume')
|
|
492
|
+
.option('--done-work <text>', 'Repeatable completed work item', collectRepeatedOption, [])
|
|
493
|
+
.option('--next-step <text>', 'Repeatable next step', collectRepeatedOption, [])
|
|
494
|
+
.option('--content <textOrFile>', 'Optional notes text, or @file to read notes from disk')
|
|
495
|
+
.option('--source-ref <value>', 'Repeatable source ref (string, JSON object/array, or @file.json)', collectRepeatedOption, [])
|
|
496
|
+
.option('--next-read-ref <value>', 'Repeatable next read ref (string, JSON object/array, or @file.json)', collectRepeatedOption, [])
|
|
497
|
+
.option('--mission-id <id>', 'Mission id anchor; uses agentspace.mission subject')
|
|
498
|
+
.option('--mission-slug <slug>', 'Mission slug/title hint')
|
|
499
|
+
.option('--session-id <id>', 'Session id anchor')
|
|
500
|
+
.option('--agent <id>', 'Agent/session actor label')
|
|
501
|
+
.option('--subject-type <type>', 'Explicit subject type for non-mission checkpoint')
|
|
502
|
+
.option('--subject-id <id>', 'Explicit subject id for non-mission checkpoint')
|
|
503
|
+
.option('--subject-title <text>', 'Explicit subject title for non-mission checkpoint')
|
|
504
|
+
.option('--task-id <id>', 'Linked kanban task id')
|
|
505
|
+
.option('--kanban-task-id <id>', 'Linked kanban task id')
|
|
506
|
+
.option('--sprint-id <id>', 'Linked sprint id')
|
|
507
|
+
.option('--phase-id <id>', 'Linked phase id')
|
|
508
|
+
.option('--utask-id <id>', 'Linked utask id')
|
|
509
|
+
.option('--microtask-id <id>', 'Linked microtask id')
|
|
510
|
+
.option('--issue-id <id>', 'Linked issue id')
|
|
511
|
+
.option('--feedback-id <id>', 'Linked feedback id')
|
|
512
|
+
.option('--tag <value>', 'Repeatable tag', collectRepeatedOption, [])
|
|
513
|
+
.option('--importance <value>', 'Importance score (0-100)', (value) => Number.parseInt(String(value), 10))
|
|
514
|
+
.option('--next-action <text>', 'Recommended next action')
|
|
515
|
+
.option('--validation-state <text>', 'Validation state summary')
|
|
516
|
+
.option('--supersede <memoryId>', 'Explicitly supersede an older checkpoint id')).action(async (options) => {
|
|
517
|
+
await runCheckpointCreate(options);
|
|
518
|
+
}));
|
|
519
|
+
applyCheckpointContextOptions(cmd.command('list')
|
|
520
|
+
.description('List checkpoint timeline, including superseded records')
|
|
521
|
+
.option('--mission-id <id>', 'Filter to mission checkpoint subject')
|
|
522
|
+
.option('--subject-type <type>', 'Filter by subject type')
|
|
523
|
+
.option('--subject-id <id>', 'Filter by subject id')
|
|
524
|
+
.option('--current', 'Show only current non-superseded checkpoint per subject')
|
|
525
|
+
.option('--timeline', 'Show timeline view including superseded checkpoints (default)')
|
|
526
|
+
.option('--no-include-legacy', 'Exclude legacy mem checkpoint records')
|
|
527
|
+
.option('--limit <count>', 'Result limit', (value) => Number.parseInt(String(value), 10))
|
|
528
|
+
.action(async (options) => {
|
|
529
|
+
await runCheckpointList(options);
|
|
530
|
+
}));
|
|
531
|
+
applyCheckpointContextOptions(cmd.command('get')
|
|
532
|
+
.description('Get a checkpoint by id, or the current checkpoint for a subject')
|
|
533
|
+
.option('--id <id>', 'Checkpoint id')
|
|
534
|
+
.option('--current', 'Get current checkpoint for the requested subject (default when --id is omitted)')
|
|
535
|
+
.option('--mission-id <id>', 'Mission id subject for current lookup')
|
|
536
|
+
.option('--subject-type <type>', 'Subject type for current lookup')
|
|
537
|
+
.option('--subject-id <id>', 'Subject id for current lookup')
|
|
538
|
+
.option('--full', 'Include checkpoint content body')
|
|
539
|
+
.option('--no-include-legacy', 'Exclude legacy mem checkpoint records')
|
|
540
|
+
.action(async (options) => {
|
|
541
|
+
await runCheckpointGet(options);
|
|
542
|
+
}));
|
|
543
|
+
cmd.addHelpText('after', `
|
|
544
|
+
Notes:
|
|
545
|
+
` + '`checkpoint` writes real repo-first kind=checkpoint records with checkpointAs=session.\n' +
|
|
546
|
+
' Legacy `mem checkpoint --as status|decision|blocker` records remain readable in list/get outputs.\n' +
|
|
547
|
+
' Hosted checkpoint writes wait for S2 Agentspace schema support; S1 is repo-first only.\n');
|
|
548
|
+
return cmd;
|
|
549
|
+
}
|