@aopslabs/aops 0.3.32 → 0.3.34

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.
@@ -220,6 +220,9 @@ is canonical, so the cache is only ever pulled, never pushed back.
220
220
  ```bash
221
221
  aops-cli sync status --project-slug aops --json
222
222
  aops-cli sync pull --project-slug aops --apply --json
223
+ aops-cli sync pull --project-slug aops --only projectman --apply --json
224
+ aops-cli sync pull --project-slug aops --only agentspace --apply --json
225
+ aops-cli sync pull --hosted-project-slug aops --only hosted-skills --apply --json
223
226
  aops-cli sync status --all-projects --json
224
227
  aops-cli sync pull --all-projects --apply --json
225
228
  ```
@@ -238,6 +241,13 @@ Rules:
238
241
  reported/skipped rather than treated as another project's cache.
239
242
  4. Because the server is canonical, conflict/drift resolution is not part of the
240
243
  pull: a refresh simply overwrites the local cache with current server state.
244
+ 5. `sync pull` and `sync bootstrap` accept repeatable `--only` flags with
245
+ `projectman`, `agentspace`, `hosted-skills`, and `hosted-prompts`. Omitting
246
+ `--only` preserves the complete refresh. Prefer a scoped synchronous pull
247
+ when only one cache family changed so completion and errors remain visible.
248
+ 6. Docman is intentionally not a sync partition. Refresh document mirrors with
249
+ `aops-cli doc mirror pull`; `--only docman` is rejected rather than silently
250
+ doing the wrong operation.
241
251
 
242
252
  #### 13.1.3 Archive lifecycle
243
253
 
@@ -17,6 +17,26 @@ import { loadAopsRepoConfig, loadAopsRepoConfigReadOnly, readAopsRepoConfigReadO
17
17
  import { hostedProjectKey, rebuildHostedWorkspace, syncHostedMirrorKind, } from '../utils/hosted-workspace.js';
18
18
  import { GUIDE_PATHS } from '../utils/guide-paths.js';
19
19
  import { writeFileWithRetry } from '../utils/transient-fs.js';
20
+ export const SYNC_PULL_PARTITIONS = [
21
+ 'projectman',
22
+ 'agentspace',
23
+ 'hosted-skills',
24
+ 'hosted-prompts',
25
+ ];
26
+ export function resolveSyncPullPartitions(values = []) {
27
+ if (values.length === 0)
28
+ return [...SYNC_PULL_PARTITIONS];
29
+ const allowed = new Set(SYNC_PULL_PARTITIONS);
30
+ const selected = new Set();
31
+ for (const raw of values) {
32
+ const value = normalizeNonEmpty(raw)?.toLowerCase();
33
+ if (!value || !allowed.has(value)) {
34
+ throw new Error(`Unknown sync partition: ${raw || '(empty)'}. Expected one of: ${SYNC_PULL_PARTITIONS.join(', ')}.`);
35
+ }
36
+ selected.add(value);
37
+ }
38
+ return SYNC_PULL_PARTITIONS.filter((partition) => selected.has(partition));
39
+ }
20
40
  function projectReportBase(project) {
21
41
  return compactPayload({
22
42
  projectSlug: normalizeNonEmpty(project.slug),
@@ -1091,18 +1111,22 @@ async function resolveHostedMirrorTargets(apiState, options, context) {
1091
1111
  }
1092
1112
  return [...deduped.values()];
1093
1113
  }
1094
- async function buildHostedMirrorItemsForProject(apiState, options, project) {
1114
+ async function buildHostedMirrorItemsForProject(apiState, options, project, selection = { skills: true, prompts: true }) {
1095
1115
  const scopeId = normalizeNonEmpty(project.scopeId) ?? normalizeNonEmpty(project.projectId);
1096
1116
  if (!scopeId)
1097
1117
  return { skillItems: [], promptItems: [] };
1098
1118
  const [skills, prompts] = await Promise.all([
1099
- toRecordArray(await invokeRead(apiState, options, 'agentspace.skill.list-skills', {
1100
- filter: compactPayload({ scopeId, scopeResolution: 'cascade' }),
1101
- options: { limit: 500 },
1102
- })),
1103
- toRecordArray(await invokeRead(apiState, options, 'agentspace.prompt.list-prompts', {
1104
- filter: compactPayload({ scopeId, scopeResolution: 'cascade', limit: 500 }),
1105
- })),
1119
+ selection.skills
1120
+ ? toRecordArray(await invokeRead(apiState, options, 'agentspace.skill.list-skills', {
1121
+ filter: compactPayload({ scopeId, scopeResolution: 'cascade' }),
1122
+ options: { limit: 500 },
1123
+ }))
1124
+ : [],
1125
+ selection.prompts
1126
+ ? toRecordArray(await invokeRead(apiState, options, 'agentspace.prompt.list-prompts', {
1127
+ filter: compactPayload({ scopeId, scopeResolution: 'cascade', limit: 500 }),
1128
+ }))
1129
+ : [],
1106
1130
  ]);
1107
1131
  const skillItems = await Promise.all(skills.map(async (skill) => {
1108
1132
  const currentVersionId = normalizeNonEmpty(skill.currentVersionId);
@@ -1224,24 +1248,34 @@ async function runSyncDiff(options) {
1224
1248
  async function collectSyncPullData(options, command, context, apiState) {
1225
1249
  const projectId = normalizeNonEmpty(context.projectId);
1226
1250
  const scopeId = normalizeNonEmpty(context.scopeId) ?? projectId;
1251
+ const partitions = resolveSyncPullPartitions(options.only);
1252
+ const selected = new Set(partitions);
1253
+ const pullProjectman = selected.has('projectman');
1254
+ const pullAgentspace = selected.has('agentspace');
1255
+ const pullHostedSkills = selected.has('hosted-skills');
1256
+ const pullHostedPrompts = selected.has('hosted-prompts');
1227
1257
  const pmPaths = resolveRepoFirstProjectmanPaths(context);
1228
1258
  const agPaths = resolveRepoFirstAgentspacePaths(context);
1229
1259
  const filesWritten = [];
1230
1260
  const [boards, tasks, sprints, issues, feedback, reviewRequests, experiences, memories] = await Promise.all([
1231
- invokeRead(apiState, options, 'projectman.kanban-board.list', compactPayload({ scopeId, project: projectId })),
1232
- invokeRead(apiState, options, 'projectman.kanban-task.list', compactPayload({ scopeId, project: projectId })),
1233
- invokeRead(apiState, options, 'projectman.sprint.list', compactPayload({ scopeId, project: projectId })),
1234
- invokeRead(apiState, options, 'projectman.issue.list', compactPayload({ scopeId })),
1235
- invokeRead(apiState, options, 'projectman.feedback.list', compactPayload({ scopeId })),
1236
- invokeRead(apiState, options, 'projectman.review-request.list', compactPayload({ scopeId })),
1237
- invokeRead(apiState, options, 'agentspace.experience-item.list-experience-items', {
1238
- filter: compactPayload({ scopeId, scopeResolution: 'cascade' }),
1239
- options: { limit: 500 },
1240
- }),
1241
- invokeRead(apiState, options, 'agentspace.memory-item.list-memory-items', {
1242
- filter: compactPayload({ scopeId, scopeResolution: 'cascade', projectId }),
1243
- options: { limit: 500 },
1244
- }),
1261
+ pullProjectman ? invokeRead(apiState, options, 'projectman.kanban-board.list', compactPayload({ scopeId, project: projectId })) : [],
1262
+ pullProjectman ? invokeRead(apiState, options, 'projectman.kanban-task.list', compactPayload({ scopeId, project: projectId })) : [],
1263
+ pullProjectman ? invokeRead(apiState, options, 'projectman.sprint.list', compactPayload({ scopeId, project: projectId })) : [],
1264
+ pullProjectman ? invokeRead(apiState, options, 'projectman.issue.list', compactPayload({ scopeId })) : [],
1265
+ pullProjectman ? invokeRead(apiState, options, 'projectman.feedback.list', compactPayload({ scopeId })) : [],
1266
+ pullProjectman ? invokeRead(apiState, options, 'projectman.review-request.list', compactPayload({ scopeId })) : [],
1267
+ pullAgentspace
1268
+ ? invokeRead(apiState, options, 'agentspace.experience-item.list-experience-items', {
1269
+ filter: compactPayload({ scopeId, scopeResolution: 'cascade' }),
1270
+ options: { limit: 500 },
1271
+ })
1272
+ : [],
1273
+ pullAgentspace
1274
+ ? invokeRead(apiState, options, 'agentspace.memory-item.list-memory-items', {
1275
+ filter: compactPayload({ scopeId, scopeResolution: 'cascade', projectId }),
1276
+ options: { limit: 500 },
1277
+ })
1278
+ : [],
1245
1279
  ]);
1246
1280
  for (const row of toRecordArray(boards)) {
1247
1281
  const remoteId = normalizeNonEmpty(row.id);
@@ -1295,29 +1329,42 @@ async function collectSyncPullData(options, command, context, apiState) {
1295
1329
  for (const row of toRecordArray(memories)) {
1296
1330
  filesWritten.push(await writeMemorySeedFile({ repoRoot: context.repoRoot, dir: agPaths.memoryItems, remote: row, projectId, scopeId }));
1297
1331
  }
1298
- const hostedMirrorTargets = await resolveHostedMirrorTargets(apiState, options, context);
1299
- const touchedHostedProjectKeys = hostedMirrorTargets.map((target) => hostedProjectKey(target));
1300
- const hostedSkillItems = [];
1301
- const hostedPromptItems = [];
1302
- for (const target of hostedMirrorTargets) {
1303
- const mirrorItems = await buildHostedMirrorItemsForProject(apiState, options, target);
1304
- hostedSkillItems.push(...mirrorItems.skillItems);
1305
- hostedPromptItems.push(...mirrorItems.promptItems);
1306
- }
1307
- filesWritten.push(...await syncHostedMirrorKind(context.repoRoot, 'skill', hostedSkillItems, { touchedProjectKeys: touchedHostedProjectKeys }));
1308
- filesWritten.push(...await syncHostedMirrorKind(context.repoRoot, 'prompt', hostedPromptItems, { touchedProjectKeys: touchedHostedProjectKeys }));
1309
- await rebuildProjectmanViews(context);
1310
- const experienceItems = await readExperienceItems(agPaths.experienceItems);
1311
- await rebuildExperienceWorkspace(context, experienceItems);
1312
- const memoryItems = await readLocalMemoryEntries(agPaths.memoryItems);
1313
- await rebuildLocalMemoryWorkspace({
1314
- ...context,
1315
- items: memoryItems,
1316
- });
1317
- filesWritten.push(...await rebuildHostedWorkspace(context.repoRoot));
1332
+ if (pullHostedSkills || pullHostedPrompts) {
1333
+ const hostedMirrorTargets = await resolveHostedMirrorTargets(apiState, options, context);
1334
+ const touchedHostedProjectKeys = hostedMirrorTargets.map((target) => hostedProjectKey(target));
1335
+ const hostedSkillItems = [];
1336
+ const hostedPromptItems = [];
1337
+ for (const target of hostedMirrorTargets) {
1338
+ const mirrorItems = await buildHostedMirrorItemsForProject(apiState, options, target, {
1339
+ skills: pullHostedSkills,
1340
+ prompts: pullHostedPrompts,
1341
+ });
1342
+ hostedSkillItems.push(...mirrorItems.skillItems);
1343
+ hostedPromptItems.push(...mirrorItems.promptItems);
1344
+ }
1345
+ if (pullHostedSkills) {
1346
+ filesWritten.push(...await syncHostedMirrorKind(context.repoRoot, 'skill', hostedSkillItems, { touchedProjectKeys: touchedHostedProjectKeys }));
1347
+ }
1348
+ if (pullHostedPrompts) {
1349
+ filesWritten.push(...await syncHostedMirrorKind(context.repoRoot, 'prompt', hostedPromptItems, { touchedProjectKeys: touchedHostedProjectKeys }));
1350
+ }
1351
+ }
1352
+ if (pullProjectman)
1353
+ await rebuildProjectmanViews(context);
1354
+ if (pullAgentspace) {
1355
+ const experienceItems = await readExperienceItems(agPaths.experienceItems);
1356
+ await rebuildExperienceWorkspace(context, experienceItems);
1357
+ const memoryItems = await readLocalMemoryEntries(agPaths.memoryItems);
1358
+ await rebuildLocalMemoryWorkspace({
1359
+ ...context,
1360
+ items: memoryItems,
1361
+ });
1362
+ }
1363
+ if (pullHostedSkills || pullHostedPrompts)
1364
+ filesWritten.push(...await rebuildHostedWorkspace(context.repoRoot));
1318
1365
  await ensureGitignore(context.repoRoot);
1319
- const statePath = await writeState(context.repoRoot, { lastPull: { command, filesWritten } });
1320
- return { filesWritten, statePath };
1366
+ const statePath = await writeState(context.repoRoot, { lastPull: { command, partitions, filesWritten } });
1367
+ return { partitions, filesWritten, statePath };
1321
1368
  }
1322
1369
  async function runSyncPullOrBootstrap(options, command) {
1323
1370
  try {
@@ -1901,6 +1948,10 @@ function applyAllProjectsOption(cmd) {
1901
1948
  cmd.option('--all-projects', 'Run the sync command once per repo-config project and report project-level results without fail-fast');
1902
1949
  return cmd;
1903
1950
  }
1951
+ function applySyncPullPartitionOptions(cmd) {
1952
+ cmd.option('--only <partition>', `Limit pull/bootstrap to one partition; repeat as needed (${SYNC_PULL_PARTITIONS.join(', ')})`, collectRepeatedOption, []);
1953
+ return cmd;
1954
+ }
1904
1955
  export function makeSyncCommand() {
1905
1956
  const cmd = new Command('sync').description('Server-first sync commands: refresh the read-only local cache of Projectman/Agentspace state plus hosted prompt/skill mirrors');
1906
1957
  applySyncSelectionOptions(applyAllProjectsOption(applySyncOptions(cmd.command('status')
@@ -1909,21 +1960,21 @@ export function makeSyncCommand() {
1909
1960
  applySyncSelectionOptions(applyAllProjectsOption(applySyncOptions(cmd.command('diff')
1910
1961
  .description('Show local records that are not synced')
1911
1962
  .action(async (options) => runSyncDiff(options)))));
1912
- applyAllProjectsOption(applySyncOptions(cmd.command('pull')
1963
+ applySyncPullPartitionOptions(applyAllProjectsOption(applySyncOptions(cmd.command('pull')
1913
1964
  .description('Pull hosted Projectman/Agentspace state into the read-only local cache and refresh read-only prompt/skill mirrors')
1914
1965
  .option('--apply', 'Write pulled records into the local cache workspace')
1915
1966
  .option('--hosted-project-id <id>', 'Also mirror hosted read-only prompts/skills for another project id', collectRepeatedOption, [])
1916
1967
  .option('--hosted-project-name <name>', 'Also mirror hosted read-only prompts/skills for another project name', collectRepeatedOption, [])
1917
1968
  .option('--hosted-project-slug <slug>', 'Also mirror hosted read-only prompts/skills for another project slug', collectRepeatedOption, [])
1918
- .action(async (options) => runSyncPullOrBootstrap(options, 'sync.pull'))));
1919
- applyAllProjectsOption(applySyncOptions(cmd.command('bootstrap')
1969
+ .action(async (options) => runSyncPullOrBootstrap(options, 'sync.pull')))));
1970
+ applySyncPullPartitionOptions(applyAllProjectsOption(applySyncOptions(cmd.command('bootstrap')
1920
1971
  .description('Seed the read-only local cache from hosted server state and refresh read-only prompt/skill mirrors')
1921
1972
  .option('--from-server', 'Bootstrap from hosted AOPS server state')
1922
1973
  .option('--apply', 'Write bootstrapped records into the local cache workspace')
1923
1974
  .option('--hosted-project-id <id>', 'Also mirror hosted read-only prompts/skills for another project id', collectRepeatedOption, [])
1924
1975
  .option('--hosted-project-name <name>', 'Also mirror hosted read-only prompts/skills for another project name', collectRepeatedOption, [])
1925
1976
  .option('--hosted-project-slug <slug>', 'Also mirror hosted read-only prompts/skills for another project slug', collectRepeatedOption, [])
1926
- .action(async (options) => runSyncPullOrBootstrap(options, 'sync.bootstrap'))));
1977
+ .action(async (options) => runSyncPullOrBootstrap(options, 'sync.bootstrap')))));
1927
1978
  applySyncSelectionOptions(applySyncOptions(cmd.command('sidecar')
1928
1979
  .description('Run a localhost-bound cockpit sidecar that exposes read-only local cache status/diff on the client machine')
1929
1980
  .option('--host <host>', 'Bind host for the sidecar HTTP server', DEFAULT_SIDECAR_HOST)
@@ -1950,6 +2001,7 @@ export function makeSyncCommand() {
1950
2001
  'aops-cli sync pull --apply --json',
1951
2002
  'aops-cli sync pull --all-projects --apply --json',
1952
2003
  'aops-cli sync pull --apply --hosted-project-slug aops --json',
2004
+ 'aops-cli sync pull --only hosted-skills --apply --hosted-project-slug aops --json',
1953
2005
  'aops-cli sync resolve --path .aops-cache/projectman/sprints/demo.md --prefer remote --json',
1954
2006
  ],
1955
2007
  guide: GUIDE_PATHS.operator,
@@ -1962,6 +2014,8 @@ export function makeSyncCommand() {
1962
2014
  'Board/task/sprint selection expands to related Projectman records and linked Agentspace memory/experience context. `--record` matches local path, localId, remoteId, slug, name, or title.',
1963
2015
  '`--all-projects` runs status/diff/pull/bootstrap once per repo-config project (server-first; no authoring-mode split), skips only projects without a localRoot in a multi-project repo, and reports project-level errors without fail-fast.',
1964
2016
  '`sync pull` and `sync bootstrap` are project-level merge/mirror commands; use --hosted-project-* there only to refresh hosted prompt/skill mirrors.',
2017
+ '`sync pull|bootstrap --only <partition>` limits work to projectman, agentspace, hosted-skills, or hosted-prompts; repeat --only to combine partitions. Omitting it preserves the full refresh.',
2018
+ 'Docman is intentionally separate: use `aops-cli doc mirror pull`, not a sync --only partition.',
1965
2019
  'Hosted writes are the source of truth, so the local cache never replays back. If a cache file drifts, re-run `sync pull` to refresh it from the server; `sync resolve --prefer remote` re-adopts a single record from hosted state.',
1966
2020
  'Browser/cockpit surfaces must never assume the remote aops-server machine has the repo. Use `aops-cli sync sidecar --allow-origin <cockpit-origin>` on the client machine to expose a localhost-only read-only status/diff bridge.',
1967
2021
  '`localId` is UUIDv4. `remoteId` is filled after hosted sync/bootstrap.',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aopslabs/aops",
3
- "version": "0.3.32",
3
+ "version": "0.3.34",
4
4
  "type": "module",
5
5
  "description": "AOPS CLI and terminal setup application.",
6
6
  "aopsDockerServerVersion": "0.2.17",
@@ -40,12 +40,12 @@
40
40
  "@openai/codex-sdk": "0.149.0",
41
41
  "@anthropic-ai/claude-agent-sdk": "0.3.241",
42
42
  "@opencode-ai/sdk": "1.18.21",
43
- "@aopslabs/aops-agent-runtime": "0.1.1",
44
- "@aopslabs/aops-server": "0.2.26",
45
43
  "@aopslabs/aops-host-registration": "0.2.3",
46
44
  "@aopslabs/aops-pg-bootstrap": "0.2.9",
47
- "@aopslabs/api-client": "0.2.3",
48
- "@aopslabs/aops-runtime-config": "0.2.3"
45
+ "@aopslabs/aops-server": "0.2.28",
46
+ "@aopslabs/aops-agent-runtime": "0.1.1",
47
+ "@aopslabs/aops-runtime-config": "0.2.3",
48
+ "@aopslabs/api-client": "0.2.3"
49
49
  },
50
50
  "devDependencies": {
51
51
  "@types/node": "25.3.1",