@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,255 @@
|
|
|
1
|
+
import { normalizeNonEmpty, } from './command.js';
|
|
2
|
+
import { loadAopsRepoConfig, } from './repo-config.js';
|
|
3
|
+
function normalizeProjectName(value) {
|
|
4
|
+
return normalizeNonEmpty(value)?.toLowerCase();
|
|
5
|
+
}
|
|
6
|
+
function normalizeProjectSlug(value) {
|
|
7
|
+
return normalizeNonEmpty(value)?.toLowerCase();
|
|
8
|
+
}
|
|
9
|
+
function matchProjectByName(project, projectName) {
|
|
10
|
+
return normalizeProjectName(project.name) === normalizeProjectName(projectName);
|
|
11
|
+
}
|
|
12
|
+
function matchProjectBySlug(project, projectSlug) {
|
|
13
|
+
return normalizeProjectSlug(project.slug) === normalizeProjectSlug(projectSlug);
|
|
14
|
+
}
|
|
15
|
+
function resolveProjectFromConfig(projects, options) {
|
|
16
|
+
const explicitProjectName = normalizeNonEmpty(options.projectName);
|
|
17
|
+
if (explicitProjectName) {
|
|
18
|
+
const matches = projects.filter((project) => matchProjectByName(project, explicitProjectName));
|
|
19
|
+
if (matches.length === 0) {
|
|
20
|
+
const available = projects.map((project) => project.name).join(', ');
|
|
21
|
+
throw new Error(`Project "${explicitProjectName}" was not found in repo config. Available projects: ${available || '(none)'}.`);
|
|
22
|
+
}
|
|
23
|
+
if (matches.length > 1) {
|
|
24
|
+
const ids = matches
|
|
25
|
+
.map((project) => `${project.name} (${project.scopeId ?? project.projectId})`)
|
|
26
|
+
.join(', ');
|
|
27
|
+
throw new Error(`Project name "${explicitProjectName}" is ambiguous in repo config: ${ids}.`);
|
|
28
|
+
}
|
|
29
|
+
return matches[0];
|
|
30
|
+
}
|
|
31
|
+
const explicitProjectSlug = normalizeNonEmpty(options.projectSlug);
|
|
32
|
+
if (explicitProjectSlug) {
|
|
33
|
+
const matches = projects.filter((project) => matchProjectBySlug(project, explicitProjectSlug));
|
|
34
|
+
if (matches.length === 0) {
|
|
35
|
+
const available = projects
|
|
36
|
+
.map((project) => normalizeNonEmpty(project.slug))
|
|
37
|
+
.filter((entry) => Boolean(entry))
|
|
38
|
+
.join(', ');
|
|
39
|
+
throw new Error(`Project slug "${explicitProjectSlug}" was not found in repo config. Available slugs: ${available || '(none)'}.`);
|
|
40
|
+
}
|
|
41
|
+
if (matches.length > 1) {
|
|
42
|
+
const ids = matches
|
|
43
|
+
.map((project) => `${project.slug ?? project.name} (${project.scopeId ?? project.projectId})`)
|
|
44
|
+
.join(', ');
|
|
45
|
+
throw new Error(`Project slug "${explicitProjectSlug}" is ambiguous in repo config: ${ids}.`);
|
|
46
|
+
}
|
|
47
|
+
return matches[0];
|
|
48
|
+
}
|
|
49
|
+
const explicitScopeId = normalizeNonEmpty(options.scopeId);
|
|
50
|
+
if (explicitScopeId) {
|
|
51
|
+
return (projects.find((project) => project.scopeId === explicitScopeId) ??
|
|
52
|
+
projects.find((project) => project.projectId === explicitScopeId) ??
|
|
53
|
+
null);
|
|
54
|
+
}
|
|
55
|
+
const explicitProjectId = normalizeNonEmpty(options.projectId);
|
|
56
|
+
if (explicitProjectId) {
|
|
57
|
+
return (projects.find((project) => project.projectId === explicitProjectId) ??
|
|
58
|
+
projects.find((project) => project.scopeId === explicitProjectId) ??
|
|
59
|
+
null);
|
|
60
|
+
}
|
|
61
|
+
const activeProjectName = normalizeNonEmpty(options.activeProjectName);
|
|
62
|
+
if (activeProjectName) {
|
|
63
|
+
const active = projects.find((project) => matchProjectByName(project, activeProjectName));
|
|
64
|
+
if (active)
|
|
65
|
+
return active;
|
|
66
|
+
}
|
|
67
|
+
return projects[0] ?? null;
|
|
68
|
+
}
|
|
69
|
+
export async function resolveProjectBindingContext(options, params = {}) {
|
|
70
|
+
const requireProject = params.requireProject === true;
|
|
71
|
+
const { rootDir, configPath, config } = await loadAopsRepoConfig(process.cwd());
|
|
72
|
+
const explicitScopeId = normalizeNonEmpty(options.scopeId);
|
|
73
|
+
const explicitProjectId = normalizeNonEmpty(options.projectId);
|
|
74
|
+
const explicitProjectName = normalizeNonEmpty(options.projectName);
|
|
75
|
+
const explicitProjectSlug = normalizeNonEmpty(options.projectSlug);
|
|
76
|
+
const explicitId = explicitProjectId ?? explicitScopeId;
|
|
77
|
+
const configProject = config
|
|
78
|
+
? resolveProjectFromConfig(config.projects, {
|
|
79
|
+
scopeId: explicitScopeId,
|
|
80
|
+
projectId: explicitProjectId,
|
|
81
|
+
projectName: explicitProjectName,
|
|
82
|
+
projectSlug: explicitProjectSlug,
|
|
83
|
+
activeProjectName: config.activeProjectName,
|
|
84
|
+
})
|
|
85
|
+
: null;
|
|
86
|
+
const projectId = explicitProjectId ??
|
|
87
|
+
explicitScopeId ??
|
|
88
|
+
configProject?.projectId ??
|
|
89
|
+
configProject?.scopeId;
|
|
90
|
+
const scopeId = projectId;
|
|
91
|
+
const projectName = explicitProjectName ?? configProject?.name;
|
|
92
|
+
const projectSlug = explicitProjectSlug ?? normalizeNonEmpty(configProject?.slug);
|
|
93
|
+
const pendingServerProjectLookup = !projectId && !explicitId
|
|
94
|
+
? explicitProjectSlug
|
|
95
|
+
? { kind: 'slug', value: explicitProjectSlug }
|
|
96
|
+
: explicitProjectName
|
|
97
|
+
? { kind: 'name', value: explicitProjectName }
|
|
98
|
+
: undefined
|
|
99
|
+
: undefined;
|
|
100
|
+
if (requireProject && !scopeId && !projectId && !pendingServerProjectLookup) {
|
|
101
|
+
throw new Error('Project context could not be resolved. Provide --project-id <id>, or --project-slug/--project-name (resolved against the server). `--scope-id` remains a legacy/internal alias.');
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
repoRoot: rootDir,
|
|
105
|
+
configPath,
|
|
106
|
+
scopeId: scopeId ?? projectId,
|
|
107
|
+
projectId,
|
|
108
|
+
projectName,
|
|
109
|
+
projectSlug,
|
|
110
|
+
localRoot: normalizeNonEmpty(configProject?.localRoot),
|
|
111
|
+
ownerRepo: normalizeNonEmpty(configProject?.ownerRepo),
|
|
112
|
+
parentProjectSlug: normalizeNonEmpty(configProject?.parentProjectSlug),
|
|
113
|
+
configFound: Boolean(config),
|
|
114
|
+
...(pendingServerProjectLookup ? { pendingServerProjectLookup } : {}),
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
function isRecord(value) {
|
|
118
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
119
|
+
}
|
|
120
|
+
function unwrapHostedEntityRecord(payload) {
|
|
121
|
+
let current = payload;
|
|
122
|
+
if (isRecord(current) && Object.prototype.hasOwnProperty.call(current, 'response')) {
|
|
123
|
+
current = current.response;
|
|
124
|
+
}
|
|
125
|
+
for (let depth = 0; depth < 6 && isRecord(current); depth += 1) {
|
|
126
|
+
const record = current;
|
|
127
|
+
if (normalizeNonEmpty(record.id) ??
|
|
128
|
+
normalizeNonEmpty(record.projectId) ??
|
|
129
|
+
normalizeNonEmpty(record.slug)) {
|
|
130
|
+
return record;
|
|
131
|
+
}
|
|
132
|
+
if (Object.prototype.hasOwnProperty.call(record, 'data')) {
|
|
133
|
+
current = record.data;
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
return record;
|
|
137
|
+
}
|
|
138
|
+
return isRecord(current) ? current : null;
|
|
139
|
+
}
|
|
140
|
+
function unwrapHostedEntityRows(payload) {
|
|
141
|
+
let current = payload;
|
|
142
|
+
if (isRecord(current) && Object.prototype.hasOwnProperty.call(current, 'response')) {
|
|
143
|
+
current = current.response;
|
|
144
|
+
}
|
|
145
|
+
for (let depth = 0; depth < 6; depth += 1) {
|
|
146
|
+
if (Array.isArray(current))
|
|
147
|
+
break;
|
|
148
|
+
if (isRecord(current) && Array.isArray(current.items)) {
|
|
149
|
+
current = current.items;
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
if (isRecord(current) && Object.prototype.hasOwnProperty.call(current, 'data')) {
|
|
153
|
+
current = current.data;
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
158
|
+
if (!Array.isArray(current))
|
|
159
|
+
return [];
|
|
160
|
+
return current.filter((entry) => isRecord(entry));
|
|
161
|
+
}
|
|
162
|
+
export async function hydrateProjectContextFromServer(context, deps) {
|
|
163
|
+
const projectId = normalizeNonEmpty(context.projectId) ?? normalizeNonEmpty(context.scopeId);
|
|
164
|
+
if (!projectId) {
|
|
165
|
+
throw new Error('Cannot verify project against server without a resolved project id. Provide --project-id <id>.');
|
|
166
|
+
}
|
|
167
|
+
const payload = await deps.invokeHostedTool({
|
|
168
|
+
toolId: 'agentspace.project.get-by-id',
|
|
169
|
+
input: { id: projectId },
|
|
170
|
+
});
|
|
171
|
+
const project = unwrapHostedEntityRecord(payload);
|
|
172
|
+
if (!project) {
|
|
173
|
+
throw new Error(`Project ${projectId} not found on server.`);
|
|
174
|
+
}
|
|
175
|
+
const scopeId = normalizeNonEmpty(project.scopeId) ??
|
|
176
|
+
normalizeNonEmpty(project.id) ??
|
|
177
|
+
normalizeNonEmpty(project.projectId) ??
|
|
178
|
+
context.scopeId ??
|
|
179
|
+
projectId;
|
|
180
|
+
return {
|
|
181
|
+
...context,
|
|
182
|
+
scopeId,
|
|
183
|
+
projectId: normalizeNonEmpty(project.id) ?? normalizeNonEmpty(project.projectId) ?? projectId,
|
|
184
|
+
projectName: normalizeNonEmpty(project.name) ?? context.projectName,
|
|
185
|
+
projectSlug: normalizeNonEmpty(project.slug) ?? context.projectSlug,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
function normalizeLookupKey(value) {
|
|
189
|
+
return normalizeNonEmpty(value)?.toLowerCase();
|
|
190
|
+
}
|
|
191
|
+
export async function resolveProjectIdFromServer(value, kind, deps) {
|
|
192
|
+
const normalizedValue = normalizeLookupKey(value);
|
|
193
|
+
if (!normalizedValue) {
|
|
194
|
+
throw new Error(`Project ${kind} is required to resolve a project id from the server.`);
|
|
195
|
+
}
|
|
196
|
+
const payload = await deps.invokeHostedTool({
|
|
197
|
+
toolId: 'agentspace.project.list-projects',
|
|
198
|
+
input: {},
|
|
199
|
+
});
|
|
200
|
+
const rows = unwrapHostedEntityRows(payload);
|
|
201
|
+
const matches = rows.filter((row) => normalizeLookupKey(kind === 'slug' ? row.slug : row.name) === normalizedValue);
|
|
202
|
+
if (matches.length === 0) {
|
|
203
|
+
throw new Error(`Project ${kind} '${value}' not found on the server.`);
|
|
204
|
+
}
|
|
205
|
+
if (matches.length > 1) {
|
|
206
|
+
const candidates = matches
|
|
207
|
+
.map((row) => {
|
|
208
|
+
const id = normalizeNonEmpty(row.id) ?? normalizeNonEmpty(row.projectId) ?? '(no id)';
|
|
209
|
+
const label = normalizeNonEmpty(row.name) ?? normalizeNonEmpty(row.slug) ?? '(unnamed)';
|
|
210
|
+
return `${label} (${id})`;
|
|
211
|
+
})
|
|
212
|
+
.join(', ');
|
|
213
|
+
throw new Error(`Project ${kind} '${value}' is ambiguous on the server. Candidates: ${candidates}.`);
|
|
214
|
+
}
|
|
215
|
+
const project = matches[0];
|
|
216
|
+
const projectId = normalizeNonEmpty(project.id) ?? normalizeNonEmpty(project.projectId);
|
|
217
|
+
if (!projectId) {
|
|
218
|
+
throw new Error(`Project ${kind} '${value}' resolved a record without an id on the server.`);
|
|
219
|
+
}
|
|
220
|
+
const scopeId = normalizeNonEmpty(project.scopeId) ?? projectId;
|
|
221
|
+
return {
|
|
222
|
+
projectId,
|
|
223
|
+
scopeId,
|
|
224
|
+
name: normalizeNonEmpty(project.name),
|
|
225
|
+
slug: normalizeNonEmpty(project.slug),
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
export async function hydrateProjectIdFromServerLookup(context, deps) {
|
|
229
|
+
const pending = context.pendingServerProjectLookup;
|
|
230
|
+
if (!pending || normalizeNonEmpty(context.projectId)) {
|
|
231
|
+
return context;
|
|
232
|
+
}
|
|
233
|
+
const resolved = await resolveProjectIdFromServer(pending.value, pending.kind, deps);
|
|
234
|
+
const { pendingServerProjectLookup: _consumed, ...rest } = context;
|
|
235
|
+
return {
|
|
236
|
+
...rest,
|
|
237
|
+
projectId: resolved.projectId,
|
|
238
|
+
scopeId: resolved.scopeId,
|
|
239
|
+
projectName: resolved.name ?? context.projectName,
|
|
240
|
+
projectSlug: resolved.slug ?? context.projectSlug,
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
export function preferProjectNameBinding(context) {
|
|
244
|
+
const projectName = normalizeNonEmpty(context.projectName);
|
|
245
|
+
return projectName ? { projectName } : {};
|
|
246
|
+
}
|
|
247
|
+
export function resolveOwnerScopeIdFromBinding(context) {
|
|
248
|
+
return normalizeNonEmpty(context.scopeId) ?? normalizeNonEmpty(context.projectId);
|
|
249
|
+
}
|
|
250
|
+
export function resolveOwnerScopeIdFromProjectRecord(project, fallback) {
|
|
251
|
+
return (normalizeNonEmpty(project?.scopeId) ??
|
|
252
|
+
normalizeNonEmpty(fallback) ??
|
|
253
|
+
normalizeNonEmpty(project?.id) ??
|
|
254
|
+
normalizeNonEmpty(project?.projectId));
|
|
255
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import inquirer from 'inquirer';
|
|
2
|
+
const DONE_PREFIX = '\u2714 ';
|
|
3
|
+
const PROMPT_THEME = { prefix: { idle: '?', done: DONE_PREFIX } };
|
|
4
|
+
const normalizePromptMessage = (message) => message.trimStart();
|
|
5
|
+
export async function promptInput(opts) {
|
|
6
|
+
const { value } = await inquirer.prompt([
|
|
7
|
+
{
|
|
8
|
+
type: 'input',
|
|
9
|
+
name: 'value',
|
|
10
|
+
message: normalizePromptMessage(opts.message),
|
|
11
|
+
default: opts.default,
|
|
12
|
+
validate: opts.validate,
|
|
13
|
+
theme: PROMPT_THEME,
|
|
14
|
+
},
|
|
15
|
+
]);
|
|
16
|
+
return String(value ?? '');
|
|
17
|
+
}
|
|
18
|
+
export async function promptPassword(opts) {
|
|
19
|
+
const { value } = await inquirer.prompt([
|
|
20
|
+
{
|
|
21
|
+
type: 'password',
|
|
22
|
+
name: 'value',
|
|
23
|
+
message: normalizePromptMessage(opts.message),
|
|
24
|
+
default: opts.default,
|
|
25
|
+
mask: '*',
|
|
26
|
+
validate: opts.validate,
|
|
27
|
+
theme: PROMPT_THEME,
|
|
28
|
+
},
|
|
29
|
+
]);
|
|
30
|
+
return String(value ?? '');
|
|
31
|
+
}
|
|
32
|
+
export async function promptConfirm(opts) {
|
|
33
|
+
const { confirm } = await inquirer.prompt([
|
|
34
|
+
{
|
|
35
|
+
type: 'confirm',
|
|
36
|
+
name: 'confirm',
|
|
37
|
+
message: normalizePromptMessage(opts.message),
|
|
38
|
+
default: opts.default ?? true,
|
|
39
|
+
theme: PROMPT_THEME,
|
|
40
|
+
},
|
|
41
|
+
]);
|
|
42
|
+
return Boolean(confirm);
|
|
43
|
+
}
|
|
44
|
+
export async function promptSelect(opts) {
|
|
45
|
+
const resolvedType = opts.type === 'list' || !opts.type ? 'select' : opts.type;
|
|
46
|
+
const { value } = await inquirer.prompt([
|
|
47
|
+
{
|
|
48
|
+
type: resolvedType,
|
|
49
|
+
name: 'value',
|
|
50
|
+
message: normalizePromptMessage(opts.message),
|
|
51
|
+
choices: opts.choices,
|
|
52
|
+
default: opts.default,
|
|
53
|
+
pageSize: opts.pageSize,
|
|
54
|
+
theme: PROMPT_THEME,
|
|
55
|
+
},
|
|
56
|
+
]);
|
|
57
|
+
return String(value);
|
|
58
|
+
}
|
|
59
|
+
export async function promptMultiSelect(opts) {
|
|
60
|
+
const { values } = await inquirer.prompt([
|
|
61
|
+
{
|
|
62
|
+
type: 'checkbox',
|
|
63
|
+
name: 'values',
|
|
64
|
+
message: normalizePromptMessage(opts.message),
|
|
65
|
+
choices: opts.choices,
|
|
66
|
+
theme: PROMPT_THEME,
|
|
67
|
+
},
|
|
68
|
+
]);
|
|
69
|
+
return Array.isArray(values) ? values.map((v) => String(v)) : [];
|
|
70
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export function createRefreshRaceRecovery(options) {
|
|
2
|
+
const maxAttempts = Math.max(1, Math.floor(options.maxAttempts ?? 5));
|
|
3
|
+
const baseDelay = Math.max(0, Math.floor(options.delayMs ?? 50));
|
|
4
|
+
const sleep = options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
5
|
+
return async () => {
|
|
6
|
+
const previousRefresh = options.getCurrentRefreshToken();
|
|
7
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
8
|
+
let latest = null;
|
|
9
|
+
try {
|
|
10
|
+
latest = await options.readLatestTokens();
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
latest = null;
|
|
14
|
+
}
|
|
15
|
+
if (latest &&
|
|
16
|
+
latest.accessToken &&
|
|
17
|
+
latest.refreshToken &&
|
|
18
|
+
latest.refreshToken !== previousRefresh) {
|
|
19
|
+
options.applyTokens(latest);
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
if (attempt < maxAttempts - 1 && baseDelay > 0) {
|
|
23
|
+
await sleep(baseDelay * (attempt + 1));
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return false;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import { buildOperatorRepoConfig, findWorkspaceRoot, normalizeOperatorRepoConfig, resolveRepoConfigPath, } from './operator-runtime.js';
|
|
4
|
+
export { resolveRepoConfigPath };
|
|
5
|
+
export function serializeAopsRepoConfig(config) {
|
|
6
|
+
return `${JSON.stringify(config, null, 2)}\n`;
|
|
7
|
+
}
|
|
8
|
+
export function buildAopsRepoConfig(params) {
|
|
9
|
+
return buildOperatorRepoConfig(params);
|
|
10
|
+
}
|
|
11
|
+
function normalizeString(value) {
|
|
12
|
+
return typeof value === 'string' && value.trim().length > 0 ? value.trim() : undefined;
|
|
13
|
+
}
|
|
14
|
+
function normalizeProjectAuthoringMode(value) {
|
|
15
|
+
return value === 'local' || value === 'hosted-only' ? value : undefined;
|
|
16
|
+
}
|
|
17
|
+
function rawProjectKey(raw) {
|
|
18
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw))
|
|
19
|
+
return undefined;
|
|
20
|
+
const record = raw;
|
|
21
|
+
return normalizeString(record.projectId) ?? normalizeString(record.slug) ?? normalizeString(record.name);
|
|
22
|
+
}
|
|
23
|
+
function mergeAopsProjectExtensions(normalized, rawProjects) {
|
|
24
|
+
const candidates = [
|
|
25
|
+
normalized.projectId,
|
|
26
|
+
normalized.slug,
|
|
27
|
+
normalized.name,
|
|
28
|
+
].filter((entry) => Boolean(entry));
|
|
29
|
+
const raw = rawProjects.find((entry) => {
|
|
30
|
+
const key = rawProjectKey(entry);
|
|
31
|
+
return key ? candidates.includes(key) : false;
|
|
32
|
+
});
|
|
33
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw))
|
|
34
|
+
return normalized;
|
|
35
|
+
const record = raw;
|
|
36
|
+
return {
|
|
37
|
+
...normalized,
|
|
38
|
+
...(normalizeProjectAuthoringMode(record.authoringMode) ? { authoringMode: normalizeProjectAuthoringMode(record.authoringMode) } : {}),
|
|
39
|
+
...(normalizeString(record.localRoot) ? { localRoot: normalizeString(record.localRoot) } : {}),
|
|
40
|
+
...(normalizeString(record.ownerRepo) ? { ownerRepo: normalizeString(record.ownerRepo) } : {}),
|
|
41
|
+
...(normalizeString(record.parentProjectSlug) ? { parentProjectSlug: normalizeString(record.parentProjectSlug) } : {}),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
export function normalizeAopsRepoConfig(raw) {
|
|
45
|
+
const normalized = normalizeOperatorRepoConfig(raw);
|
|
46
|
+
if (!normalized)
|
|
47
|
+
return null;
|
|
48
|
+
const rawProjects = raw && typeof raw === 'object' && !Array.isArray(raw) && Array.isArray(raw.projects)
|
|
49
|
+
? raw.projects
|
|
50
|
+
: [];
|
|
51
|
+
return {
|
|
52
|
+
...normalized,
|
|
53
|
+
projects: normalized.projects.map((project) => mergeAopsProjectExtensions(project, rawProjects)),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
export async function readAopsRepoConfig(rootDir) {
|
|
57
|
+
const configPath = resolveRepoConfigPath(rootDir);
|
|
58
|
+
if (!existsSync(configPath))
|
|
59
|
+
return null;
|
|
60
|
+
try {
|
|
61
|
+
const rawContent = await fs.readFile(configPath, 'utf8');
|
|
62
|
+
const parsed = JSON.parse(rawContent);
|
|
63
|
+
const normalized = normalizeAopsRepoConfig(parsed);
|
|
64
|
+
if (!normalized)
|
|
65
|
+
return null;
|
|
66
|
+
const serialized = serializeAopsRepoConfig(normalized);
|
|
67
|
+
if (rawContent !== serialized) {
|
|
68
|
+
await fs.writeFile(configPath, serialized, 'utf8');
|
|
69
|
+
}
|
|
70
|
+
return normalized;
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
export async function readAopsRepoConfigReadOnly(rootDir) {
|
|
77
|
+
const configPath = resolveRepoConfigPath(rootDir);
|
|
78
|
+
if (!existsSync(configPath))
|
|
79
|
+
return null;
|
|
80
|
+
try {
|
|
81
|
+
const rawContent = await fs.readFile(configPath, 'utf8');
|
|
82
|
+
const parsed = JSON.parse(rawContent);
|
|
83
|
+
return normalizeAopsRepoConfig(parsed);
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
export async function writeAopsRepoConfig(configPath, config) {
|
|
90
|
+
await fs.writeFile(configPath, serializeAopsRepoConfig(config), 'utf8');
|
|
91
|
+
}
|
|
92
|
+
export async function loadAopsRepoConfig(startDir = process.cwd()) {
|
|
93
|
+
const rootDir = findWorkspaceRoot(startDir);
|
|
94
|
+
const configPath = resolveRepoConfigPath(rootDir);
|
|
95
|
+
return {
|
|
96
|
+
rootDir,
|
|
97
|
+
configPath,
|
|
98
|
+
config: await readAopsRepoConfig(rootDir),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
export async function loadAopsRepoConfigReadOnly(startDir = process.cwd()) {
|
|
102
|
+
const rootDir = findWorkspaceRoot(startDir);
|
|
103
|
+
const configPath = resolveRepoConfigPath(rootDir);
|
|
104
|
+
return {
|
|
105
|
+
rootDir,
|
|
106
|
+
configPath,
|
|
107
|
+
config: await readAopsRepoConfigReadOnly(rootDir),
|
|
108
|
+
};
|
|
109
|
+
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { compactPayload, normalizeNonEmpty } from './command.js';
|
|
5
|
+
import { parseFrontmatterDocument, renderFrontmatterDocument, } from './memory-workspace.js';
|
|
6
|
+
import { resolveRepoFirstWorkspaceRelativeRoot, resolveRepoFirstWorkspaceRoot } from './repo-first-storage.js';
|
|
7
|
+
import { writeFileWithRetry } from './transient-fs.js';
|
|
8
|
+
const SYNC_STATES = new Set(['local', 'synced', 'dirty', 'deleted', 'conflict']);
|
|
9
|
+
function hashContent(value) {
|
|
10
|
+
return createHash('sha256').update(value).digest('hex');
|
|
11
|
+
}
|
|
12
|
+
function isAopsArchivePath(filePath) {
|
|
13
|
+
const parts = filePath.split(path.sep);
|
|
14
|
+
const aopsIndex = parts.lastIndexOf('.aops');
|
|
15
|
+
return aopsIndex >= 0 && parts[aopsIndex + 1] === 'archive';
|
|
16
|
+
}
|
|
17
|
+
function comparableRecordHash(record) {
|
|
18
|
+
const frontmatter = { ...record.frontmatter };
|
|
19
|
+
delete frontmatter.baseHash;
|
|
20
|
+
return hashContent(renderFrontmatterDocument(frontmatter, record.body));
|
|
21
|
+
}
|
|
22
|
+
function isProjectmanTombstoneRecord(record) {
|
|
23
|
+
return record.filePath.includes(`${path.sep}tombstones${path.sep}`);
|
|
24
|
+
}
|
|
25
|
+
function effectiveSyncState(record) {
|
|
26
|
+
const syncState = ensureSyncState(record.frontmatter.syncState);
|
|
27
|
+
if (syncState === 'synced' && isProjectmanTombstoneRecord(record))
|
|
28
|
+
return 'synced';
|
|
29
|
+
const baseHash = normalizeNonEmpty(record.frontmatter.baseHash);
|
|
30
|
+
if (syncState === 'synced' && baseHash && comparableRecordHash(record) !== baseHash)
|
|
31
|
+
return 'dirty';
|
|
32
|
+
return syncState;
|
|
33
|
+
}
|
|
34
|
+
function ensureSyncState(value) {
|
|
35
|
+
const normalized = normalizeNonEmpty(value);
|
|
36
|
+
return normalized && SYNC_STATES.has(normalized) ? normalized : 'local';
|
|
37
|
+
}
|
|
38
|
+
export function resolveRepoFirstProjectmanPaths(context) {
|
|
39
|
+
const root = path.join(resolveRepoFirstWorkspaceRoot(context), 'projectman');
|
|
40
|
+
return {
|
|
41
|
+
root,
|
|
42
|
+
boards: path.join(root, 'boards'),
|
|
43
|
+
tasks: path.join(root, 'kanban-tasks'),
|
|
44
|
+
sprints: path.join(root, 'sprints'),
|
|
45
|
+
utasks: path.join(root, 'utasks'),
|
|
46
|
+
issues: path.join(root, 'issues'),
|
|
47
|
+
feedback: path.join(root, 'feedback'),
|
|
48
|
+
reviewRequests: path.join(root, 'review-requests'),
|
|
49
|
+
tombstones: path.join(root, 'tombstones'),
|
|
50
|
+
views: path.join(root, 'views'),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
export function resolveRepoFirstAgentspacePaths(context) {
|
|
54
|
+
const root = path.join(resolveRepoFirstWorkspaceRoot(context), 'agentspace');
|
|
55
|
+
const discussions = path.join(root, 'discussions');
|
|
56
|
+
const memory = path.join(root, 'memory');
|
|
57
|
+
const experience = path.join(root, 'experience');
|
|
58
|
+
return {
|
|
59
|
+
root,
|
|
60
|
+
discussions,
|
|
61
|
+
discussionTopics: path.join(discussions, 'topics'),
|
|
62
|
+
experience,
|
|
63
|
+
experienceItems: path.join(experience, 'items'),
|
|
64
|
+
experienceViews: path.join(experience, 'views'),
|
|
65
|
+
memory,
|
|
66
|
+
memoryItems: path.join(memory, 'items'),
|
|
67
|
+
memoryViews: path.join(memory, 'views'),
|
|
68
|
+
synopsis: path.join(root, 'synopsis.md'),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function entityDir(context, entityType) {
|
|
72
|
+
const paths = resolveRepoFirstProjectmanPaths(context);
|
|
73
|
+
switch (entityType) {
|
|
74
|
+
case 'projectman.board':
|
|
75
|
+
return paths.boards;
|
|
76
|
+
case 'projectman.kanban-task':
|
|
77
|
+
return paths.tasks;
|
|
78
|
+
case 'projectman.sprint':
|
|
79
|
+
return paths.sprints;
|
|
80
|
+
case 'projectman.issue':
|
|
81
|
+
return paths.issues;
|
|
82
|
+
case 'projectman.feedback':
|
|
83
|
+
return paths.feedback;
|
|
84
|
+
case 'projectman.review-request':
|
|
85
|
+
return paths.reviewRequests;
|
|
86
|
+
default:
|
|
87
|
+
throw new Error(`Unsupported Projectman entity type "${entityType}".`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
async function readMarkdownRecords(rootDir, params = {}) {
|
|
91
|
+
try {
|
|
92
|
+
const entries = await fs.readdir(rootDir, { withFileTypes: true });
|
|
93
|
+
const records = [];
|
|
94
|
+
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
95
|
+
const filePath = path.join(rootDir, entry.name);
|
|
96
|
+
if (isAopsArchivePath(filePath))
|
|
97
|
+
continue;
|
|
98
|
+
if (entry.isDirectory() && params.recursive === true) {
|
|
99
|
+
records.push(...await readMarkdownRecords(filePath, params));
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (!entry.isFile() || !entry.name.endsWith('.md'))
|
|
103
|
+
continue;
|
|
104
|
+
const raw = await fs.readFile(filePath, 'utf8');
|
|
105
|
+
const parsed = parseFrontmatterDocument(raw);
|
|
106
|
+
records.push({ filePath, frontmatter: parsed.frontmatter, body: parsed.body });
|
|
107
|
+
}
|
|
108
|
+
return records;
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
return [];
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
async function readEntityRecords(context, entityType) {
|
|
115
|
+
if (entityType === 'projectman.utask') {
|
|
116
|
+
return readMarkdownRecords(resolveRepoFirstProjectmanPaths(context).utasks, { recursive: true });
|
|
117
|
+
}
|
|
118
|
+
return readMarkdownRecords(entityDir(context, entityType));
|
|
119
|
+
}
|
|
120
|
+
function materializeRecord(record) {
|
|
121
|
+
const archivedAt = normalizeNonEmpty(record.frontmatter.archivedAt);
|
|
122
|
+
return compactPayload({
|
|
123
|
+
...record.frontmatter,
|
|
124
|
+
id: normalizeNonEmpty(record.frontmatter.localId) ?? normalizeNonEmpty(record.frontmatter.id),
|
|
125
|
+
archivedAt,
|
|
126
|
+
archived: Boolean(archivedAt),
|
|
127
|
+
body: record.body,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
async function listEntities(context, entityType) {
|
|
131
|
+
return (await readEntityRecords(context, entityType)).map(materializeRecord);
|
|
132
|
+
}
|
|
133
|
+
export async function rebuildProjectmanViews(context) {
|
|
134
|
+
const paths = resolveRepoFirstProjectmanPaths(context);
|
|
135
|
+
const [boards, tasks, sprints, issues, feedback, reviewRequests] = await Promise.all([
|
|
136
|
+
listEntities(context, 'projectman.board'),
|
|
137
|
+
listEntities(context, 'projectman.kanban-task'),
|
|
138
|
+
listEntities(context, 'projectman.sprint'),
|
|
139
|
+
listEntities(context, 'projectman.issue'),
|
|
140
|
+
listEntities(context, 'projectman.feedback'),
|
|
141
|
+
listEntities(context, 'projectman.review-request'),
|
|
142
|
+
]);
|
|
143
|
+
const sourceRoot = `${resolveRepoFirstWorkspaceRelativeRoot(context)}/projectman/**`;
|
|
144
|
+
const lines = [
|
|
145
|
+
'# Projectman Workspace (read-only local cache)',
|
|
146
|
+
'',
|
|
147
|
+
`> Read-only local cache of hosted Projectman records, mirrored under \`${sourceRoot}\`. The server is the source of truth.`,
|
|
148
|
+
'',
|
|
149
|
+
`- Boards: ${boards.length}`,
|
|
150
|
+
`- Kanban Tasks: ${tasks.length}`,
|
|
151
|
+
`- Sprints: ${sprints.length}`,
|
|
152
|
+
`- Issues: ${issues.length}`,
|
|
153
|
+
`- Feedback: ${feedback.length}`,
|
|
154
|
+
`- Review requests: ${reviewRequests.length}`,
|
|
155
|
+
'',
|
|
156
|
+
'## Boards',
|
|
157
|
+
...boards.map((board) => `- ${normalizeNonEmpty(board.name) ?? normalizeNonEmpty(board.slug) ?? normalizeNonEmpty(board.id)}`),
|
|
158
|
+
'',
|
|
159
|
+
'## Active Tasks',
|
|
160
|
+
...tasks.map((task) => `- ${normalizeNonEmpty(task.title) ?? normalizeNonEmpty(task.id)} — ${normalizeNonEmpty(task.columnSlug) ?? 'unknown'}`),
|
|
161
|
+
];
|
|
162
|
+
await fs.mkdir(paths.views, { recursive: true });
|
|
163
|
+
await writeFileWithRetry(path.join(paths.views, 'index.md'), `${lines.join('\n')}\n`, 'utf8');
|
|
164
|
+
}
|
|
165
|
+
export async function collectRepoFirstSyncRecords(context) {
|
|
166
|
+
const repoRoot = typeof context === 'string' ? context : context.repoRoot;
|
|
167
|
+
const projectman = resolveRepoFirstProjectmanPaths(context);
|
|
168
|
+
const agentspace = resolveRepoFirstAgentspacePaths(context);
|
|
169
|
+
const records = [
|
|
170
|
+
...await readMarkdownRecords(projectman.boards),
|
|
171
|
+
...await readMarkdownRecords(projectman.tasks),
|
|
172
|
+
...await readMarkdownRecords(projectman.sprints),
|
|
173
|
+
...await readMarkdownRecords(projectman.utasks, { recursive: true }),
|
|
174
|
+
...await readMarkdownRecords(projectman.issues),
|
|
175
|
+
...await readMarkdownRecords(projectman.feedback),
|
|
176
|
+
...await readMarkdownRecords(projectman.reviewRequests),
|
|
177
|
+
...await readMarkdownRecords(projectman.tombstones, { recursive: true }),
|
|
178
|
+
...await readMarkdownRecords(agentspace.experienceItems),
|
|
179
|
+
...await readMarkdownRecords(agentspace.memoryItems),
|
|
180
|
+
];
|
|
181
|
+
return records.map((record) => {
|
|
182
|
+
const declaredSyncState = ensureSyncState(record.frontmatter.syncState);
|
|
183
|
+
const currentSyncState = effectiveSyncState(record);
|
|
184
|
+
return compactPayload({
|
|
185
|
+
path: path.relative(repoRoot, record.filePath).split(path.sep).join('/'),
|
|
186
|
+
entityType: normalizeNonEmpty(record.frontmatter.entityType),
|
|
187
|
+
localId: normalizeNonEmpty(record.frontmatter.localId),
|
|
188
|
+
remoteId: normalizeNonEmpty(record.frontmatter.remoteId),
|
|
189
|
+
syncState: currentSyncState,
|
|
190
|
+
declaredSyncState: declaredSyncState !== currentSyncState ? declaredSyncState : undefined,
|
|
191
|
+
hash: hashContent(renderFrontmatterDocument(record.frontmatter, record.body)),
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { normalizeNonEmpty } from './command.js';
|
|
3
|
+
export function resolveRepoFirstWorkspaceRoot(context) {
|
|
4
|
+
const repoRoot = typeof context === 'string' ? context : context.repoRoot;
|
|
5
|
+
const localRoot = typeof context === 'string' ? undefined : normalizeNonEmpty(context.localRoot);
|
|
6
|
+
if (!localRoot)
|
|
7
|
+
return path.join(repoRoot, '.aops');
|
|
8
|
+
return path.isAbsolute(localRoot) ? localRoot : path.join(repoRoot, localRoot);
|
|
9
|
+
}
|
|
10
|
+
export function resolveRepoFirstWorkspaceRelativeRoot(context) {
|
|
11
|
+
const repoRoot = typeof context === 'string' ? context : context.repoRoot;
|
|
12
|
+
return path.relative(repoRoot, resolveRepoFirstWorkspaceRoot(context)).split(path.sep).join('/') || '.';
|
|
13
|
+
}
|