@nurix/nustack 0.10.0-dev.13 → 0.10.0-dev.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/dist/commands/bootstrap.js +4 -4
  2. package/dist/commands/docs.js +13 -13
  3. package/dist/commands/doctor.js +13 -13
  4. package/dist/commands/handoff.js +8 -8
  5. package/dist/commands/init.js +43 -43
  6. package/dist/commands/{workspace-sync.js → project-sync.js} +5 -5
  7. package/dist/commands/{workspace.js → project.js} +133 -133
  8. package/dist/commands/review.js +5 -5
  9. package/dist/commands/search.js +7 -7
  10. package/dist/commands/sessions.js +1 -1
  11. package/dist/commands/sync.js +10 -10
  12. package/dist/commands/toolkit.js +19 -19
  13. package/dist/index.js +29 -29
  14. package/dist/lib/etna/profile.js +9 -9
  15. package/dist/lib/etna/reconcile.js +11 -11
  16. package/dist/lib/git-sync/core/engine.js +4 -4
  17. package/dist/lib/git-sync/inbound/apply_commit.js +3 -3
  18. package/dist/lib/git-sync/inbound/approval.js +1 -1
  19. package/dist/lib/git-sync/inbound/download.js +8 -8
  20. package/dist/lib/git-sync/inbound/event_sync.js +13 -13
  21. package/dist/lib/git-sync/inbound/notification.js +1 -1
  22. package/dist/lib/git-sync/outbound/extraction.js +4 -4
  23. package/dist/lib/git-sync/outbound/history_decision.js +2 -2
  24. package/dist/lib/git-sync/outbound/publisher.js +2 -2
  25. package/dist/lib/git-sync/outbound/queue.js +3 -3
  26. package/dist/lib/git-sync/outbound/upload_client.js +4 -4
  27. package/dist/lib/git-sync/whitelist/index.js +1 -1
  28. package/dist/lib/git-sync/whitelist/types.js +1 -1
  29. package/dist/lib/git-sync/whitelist/validation.js +5 -5
  30. package/dist/lib/lanes/supervisor.js +8 -8
  31. package/dist/lib/local_store/accepted_uploads.js +5 -5
  32. package/dist/lib/local_store/bindings.js +11 -11
  33. package/dist/lib/local_store/index.js +1 -1
  34. package/dist/lib/local_store/migrations.js +13 -0
  35. package/dist/lib/local_store/project_events_cache.js +54 -0
  36. package/dist/lib/nustack_client.js +45 -45
  37. package/dist/lib/onboarding/attach_existing_git.js +7 -7
  38. package/dist/lib/onboarding/binding.js +11 -11
  39. package/dist/lib/onboarding/folder_state.js +10 -10
  40. package/dist/lib/onboarding/handoff_apply.js +2 -2
  41. package/dist/lib/onboarding/init_fresh_folder.js +18 -18
  42. package/dist/lib/onboarding/manifest_writer.js +31 -31
  43. package/dist/lib/onboarding/web_first_attach.js +12 -12
  44. package/dist/lib/{workspace_context.js → project_context.js} +24 -24
  45. package/dist/lib/review/client.js +1 -1
  46. package/dist/lib/session_telemetry.js +2 -2
  47. package/dist/lib/sessions/client.js +8 -8
  48. package/dist/lib/sessions/outsideSessions.js +3 -3
  49. package/dist/lib/telemetry/config_renderer.js +3 -3
  50. package/dist/lib/telemetry/health_report.js +1 -1
  51. package/package.json +1 -1
  52. package/dist/lib/local_store/workspace_events_cache.js +0 -54
@@ -22,7 +22,7 @@ export async function runBootstrap(options) {
22
22
  return;
23
23
  await ensureDeviceTelemetry({ baseUrl, log: (message) => p.log.info(message) });
24
24
  const state = classifyFolder(cwd);
25
- const bound = state.class === "git_worktree" && state.repository && state.git?.markerWorkspaceId
25
+ const bound = state.class === "git_worktree" && state.repository && state.git?.markerProjectId
26
26
  ? await printBoundStatus(state.repository.repositoryRoot)
27
27
  : false;
28
28
  if (bound)
@@ -42,10 +42,10 @@ async function printBoundStatus(root) {
42
42
  "decision_required",
43
43
  "approved",
44
44
  ]);
45
- p.log.success(`This repository is bound to Workspace ${binding.workspaceId} on branch ${binding.boundBranch}.`);
46
- p.log.info(`Binding ${binding.id} · ${binding.status} · applied Workspace sequence ${binding.lastAppliedWorkspaceSequence}` +
45
+ p.log.success(`This repository is bound to Project ${binding.projectId} on branch ${binding.boundBranch}.`);
46
+ p.log.info(`Binding ${binding.id} · ${binding.status} · applied Project sequence ${binding.lastAppliedProjectSequence}` +
47
47
  (pending.length > 0 ? ` · ${pending.length} pending inbound` : ""));
48
- p.outro("Up to date locally — `nustack workspace status` for the cloud view, `nustack workspace sync now` to reconcile.");
48
+ p.outro("Up to date locally — `nustack project status` for the cloud view, `nustack project sync now` to reconcile.");
49
49
  return true;
50
50
  }
51
51
  finally {
@@ -1,13 +1,13 @@
1
1
  import { getDocument, listDocuments, resolveNuStackUrl, } from "../lib/nustack_client.js";
2
2
  import { ensureAuthWithRecovery, lastAuthFailure } from "../lib/auth_recovery.js";
3
- import { resolveWorkspaceId } from "../lib/workspace_context.js";
3
+ import { resolveProjectId } from "../lib/project_context.js";
4
4
  import { printJsonError, printJsonSuccess } from "../lib/json_output.js";
5
5
  const INLINE_BODY_LIMIT = 16 * 1024;
6
6
  async function resolveContext(command, json, options, globals) {
7
7
  const baseUrl = resolveNuStackUrl(globals);
8
- const workspace = await resolveWorkspaceId(process.cwd(), options);
9
- if (!workspace.ok) {
10
- emitFail(command, json, "WORKSPACE_CONTEXT_MISSING", workspace.message);
8
+ const project = await resolveProjectId(process.cwd(), options);
9
+ if (!project.ok) {
10
+ emitFail(command, json, "PROJECT_CONTEXT_MISSING", project.message);
11
11
  return null;
12
12
  }
13
13
  const deviceAuth = await ensureAuthWithRecovery(baseUrl, json);
@@ -16,7 +16,7 @@ async function resolveContext(command, json, options, globals) {
16
16
  emitFail(command, json, failure.code, failure.message);
17
17
  return null;
18
18
  }
19
- return { baseUrl, accessToken: deviceAuth.accessToken, workspaceId: workspace.workspaceId };
19
+ return { baseUrl, accessToken: deviceAuth.accessToken, projectId: project.projectId };
20
20
  }
21
21
  function emitFail(command, json, code, message) {
22
22
  if (json)
@@ -31,13 +31,13 @@ export async function runDocsList(options, globals) {
31
31
  const ctx = await resolveContext("docs list", json, options, globals);
32
32
  if (!ctx)
33
33
  return;
34
- const documents = await listDocuments(ctx.baseUrl, ctx.accessToken, ctx.workspaceId);
34
+ const documents = await listDocuments(ctx.baseUrl, ctx.accessToken, ctx.projectId);
35
35
  if (json) {
36
- printJsonSuccess("docs list", { workspaceId: ctx.workspaceId, documents });
36
+ printJsonSuccess("docs list", { projectId: ctx.projectId, documents });
37
37
  return;
38
38
  }
39
39
  if (documents.length === 0) {
40
- console.log("This Workspace has no Documents yet.");
40
+ console.log("This Project has no Documents yet.");
41
41
  return;
42
42
  }
43
43
  for (const doc of documents) {
@@ -50,10 +50,10 @@ export async function runDocsShow(target, options, globals) {
50
50
  const ctx = await resolveContext("docs show", json, options, globals);
51
51
  if (!ctx)
52
52
  return;
53
- const documents = await listDocuments(ctx.baseUrl, ctx.accessToken, ctx.workspaceId);
53
+ const documents = await listDocuments(ctx.baseUrl, ctx.accessToken, ctx.projectId);
54
54
  const match = documents.find((d) => d.id === target || d.slug === target);
55
55
  if (!match) {
56
- emitFail("docs show", json, "DOCUMENT_NOT_FOUND", `No Document "${target}" in this Workspace.`);
56
+ emitFail("docs show", json, "DOCUMENT_NOT_FOUND", `No Document "${target}" in this Project.`);
57
57
  return;
58
58
  }
59
59
  const payload = await getDocument(ctx.baseUrl, ctx.accessToken, match.id);
@@ -97,7 +97,7 @@ export async function runDocsStatus(options, globals) {
97
97
  const ctx = await resolveContext("docs status", json, options, globals);
98
98
  if (!ctx)
99
99
  return;
100
- const documents = await listDocuments(ctx.baseUrl, ctx.accessToken, ctx.workspaceId);
100
+ const documents = await listDocuments(ctx.baseUrl, ctx.accessToken, ctx.projectId);
101
101
  const rows = documents.map((doc) => ({
102
102
  slug: doc.slug,
103
103
  documentId: doc.id,
@@ -106,11 +106,11 @@ export async function runDocsStatus(options, globals) {
106
106
  convergence: "unmapped",
107
107
  }));
108
108
  if (json) {
109
- printJsonSuccess("docs status", { workspaceId: ctx.workspaceId, paths: rows });
109
+ printJsonSuccess("docs status", { projectId: ctx.projectId, paths: rows });
110
110
  return;
111
111
  }
112
112
  if (rows.length === 0) {
113
- console.log("This Workspace has no Documents to converge.");
113
+ console.log("This Project has no Documents to converge.");
114
114
  return;
115
115
  }
116
116
  for (const row of rows) {
@@ -2,9 +2,9 @@ import path from "node:path";
2
2
  import { resolveNuStackUrl } from "../lib/nustack_client.js";
3
3
  import { ensureAccessToken } from "../lib/auth_grant.js";
4
4
  import { isKeychainAvailable } from "../lib/device_keychain.js";
5
- import { resolveWorkspaceId } from "../lib/workspace_context.js";
5
+ import { resolveProjectId } from "../lib/project_context.js";
6
6
  import { readManagedValues } from "../lib/env_file.js";
7
- import { readWorkspaceManifests, diagnoseManifests } from "../lib/onboarding/manifest_writer.js";
7
+ import { readProjectManifests, diagnoseManifests } from "../lib/onboarding/manifest_writer.js";
8
8
  import { fetchToolkitProfile, ToolkitProfileUnavailableError } from "../lib/etna/profile.js";
9
9
  import { toolkitCompatibility } from "../lib/etna/reconcile.js";
10
10
  import { openLocalStore } from "../lib/local_store/database.js";
@@ -62,9 +62,9 @@ export async function runDoctor(options) {
62
62
  addRepositoryRows(add, repository);
63
63
  }
64
64
  const contextRoot = repository?.repositoryRoot ?? cwd;
65
- const workspaceResolution = await resolveWorkspaceId(contextRoot, { workspace: options.workspace });
66
- const workspaceId = workspaceResolution.ok ? workspaceResolution.workspaceId : null;
67
- const cloudProfile = deviceAuth && workspaceId ? await fetchCloudProfile(baseUrl, deviceAuth.accessToken, workspaceId) : null;
65
+ const projectResolution = await resolveProjectId(contextRoot, { project: options.project });
66
+ const projectId = projectResolution.ok ? projectResolution.projectId : null;
67
+ const cloudProfile = deviceAuth && projectId ? await fetchCloudProfile(baseUrl, deviceAuth.accessToken, projectId) : null;
68
68
  const binding = store && repository ? getBindingByRepositoryRoot(store, repository.repositoryRoot) : null;
69
69
  await addManifestRows(add, contextRoot, cloudProfile);
70
70
  addBindingRows(add, store, binding, cloudProfile);
@@ -127,9 +127,9 @@ function addRepositoryRows(add, repository) {
127
127
  else
128
128
  add("repository", "HEAD", "info", "unborn branch — no commits yet");
129
129
  }
130
- async function fetchCloudProfile(baseUrl, accessToken, workspaceId) {
130
+ async function fetchCloudProfile(baseUrl, accessToken, projectId) {
131
131
  try {
132
- return await fetchToolkitProfile(baseUrl, accessToken, workspaceId);
132
+ return await fetchToolkitProfile(baseUrl, accessToken, projectId);
133
133
  }
134
134
  catch (error) {
135
135
  if (error instanceof ToolkitProfileUnavailableError)
@@ -138,12 +138,12 @@ async function fetchCloudProfile(baseUrl, accessToken, workspaceId) {
138
138
  }
139
139
  }
140
140
  async function addManifestRows(add, root, cloud) {
141
- const manifests = await readWorkspaceManifests(root);
142
- if (!manifests.workspaceManifest && !manifests.serviceToolkit) {
143
- add("manifests", "Workspace binding markers", "info", "no `.nustack/app.json` or Service toolkit block — repository not attached");
141
+ const manifests = await readProjectManifests(root);
142
+ if (!manifests.projectManifest && !manifests.serviceToolkit) {
143
+ add("manifests", "Project binding markers", "info", "no `.nustack/app.json` or Service toolkit block — repository not attached");
144
144
  return;
145
145
  }
146
- const diagnosis = diagnoseManifests(cloud ? { name: cloud.name, version: cloud.version } : null, manifests.workspaceManifest?.toolkit ?? null, manifests.serviceToolkit);
146
+ const diagnosis = diagnoseManifests(cloud ? { name: cloud.name, version: cloud.version } : null, manifests.projectManifest?.toolkit ?? null, manifests.serviceToolkit);
147
147
  const status = diagnosis === "aligned" ? "ok" : diagnosis === "missing" ? "warn" : "warn";
148
148
  const suffix = diagnosis === "aligned" || diagnosis === "missing" ? "" : " — run `nustack toolkit reconcile`";
149
149
  add("manifests", "Marker / Service agreement", status, `${diagnosis}${cloud ? "" : " (no cloud pin reachable)"}${suffix}`);
@@ -154,7 +154,7 @@ function addBindingRows(add, store, binding, cloud) {
154
154
  return;
155
155
  }
156
156
  add("sync", "Sync Whitelist cache", binding.whitelistManifestSha256 ? "ok" : "info", binding.whitelistManifestSha256 ? "cached" : "not cached yet");
157
- add("sync", "Workspace cursor", "ok", `applied Workspace sequence ${binding.lastAppliedWorkspaceSequence}`);
157
+ add("sync", "Project cursor", "ok", `applied Project sequence ${binding.lastAppliedProjectSequence}`);
158
158
  if (store) {
159
159
  const pending = listApplicationsForBinding(store, binding.id, [
160
160
  "received",
@@ -208,7 +208,7 @@ function addTelemetryRows(add, store) {
208
208
  async function addRecoveryRows(add, store) {
209
209
  await sweepExpiredBundles(store).catch(() => { });
210
210
  const active = listBundles(store).filter((bundle) => bundle.state === "active");
211
- add("recovery", "Recovery bundles", active.length > 0 ? "info" : "ok", active.length > 0 ? `${active.length} active — \`nustack workspace sync recovery list\`` : "none");
211
+ add("recovery", "Recovery bundles", active.length > 0 ? "info" : "ok", active.length > 0 ? `${active.length} active — \`nustack project sync recovery list\`` : "none");
212
212
  const deviceRef = readDeviceState(store)?.deviceRef ?? null;
213
213
  if (!deviceRef)
214
214
  return;
@@ -1,15 +1,15 @@
1
1
  import { fetchHandoffManifest, getHandoff, listHandoffs, resolveNuStackUrl, } from "../lib/nustack_client.js";
2
2
  import { ensureAuthWithRecovery, lastAuthFailure } from "../lib/auth_recovery.js";
3
- import { resolveWorkspaceId } from "../lib/workspace_context.js";
3
+ import { resolveProjectId } from "../lib/project_context.js";
4
4
  import { openLocalStore } from "../lib/local_store/database.js";
5
5
  import { getBindingByRepositoryRoot } from "../lib/local_store/bindings.js";
6
6
  import { resolveRepository } from "../lib/git-sync/core/repository.js";
7
7
  import { printJsonError, printJsonSuccess } from "../lib/json_output.js";
8
8
  async function resolveContext(command, json, options, globals) {
9
9
  const baseUrl = resolveNuStackUrl(globals);
10
- const workspace = await resolveWorkspaceId(process.cwd(), options);
11
- if (!workspace.ok) {
12
- emitFail(command, json, "WORKSPACE_CONTEXT_MISSING", workspace.message);
10
+ const project = await resolveProjectId(process.cwd(), options);
11
+ if (!project.ok) {
12
+ emitFail(command, json, "PROJECT_CONTEXT_MISSING", project.message);
13
13
  return null;
14
14
  }
15
15
  const deviceAuth = await ensureAuthWithRecovery(baseUrl, json);
@@ -18,7 +18,7 @@ async function resolveContext(command, json, options, globals) {
18
18
  emitFail(command, json, failure.code, failure.message);
19
19
  return null;
20
20
  }
21
- return { baseUrl, accessToken: deviceAuth.accessToken, workspaceId: workspace.workspaceId };
21
+ return { baseUrl, accessToken: deviceAuth.accessToken, projectId: project.projectId };
22
22
  }
23
23
  function emitFail(command, json, code, message) {
24
24
  if (json)
@@ -33,13 +33,13 @@ export async function runHandoffList(options, globals) {
33
33
  const ctx = await resolveContext("handoff list", json, options, globals);
34
34
  if (!ctx)
35
35
  return;
36
- const handoffs = await listHandoffs(ctx.baseUrl, ctx.accessToken, ctx.workspaceId);
36
+ const handoffs = await listHandoffs(ctx.baseUrl, ctx.accessToken, ctx.projectId);
37
37
  if (json) {
38
- printJsonSuccess("handoff list", { workspaceId: ctx.workspaceId, handoffs });
38
+ printJsonSuccess("handoff list", { projectId: ctx.projectId, handoffs });
39
39
  return;
40
40
  }
41
41
  if (handoffs.length === 0) {
42
- console.log("This Workspace has no Handoffs yet.");
42
+ console.log("This Project has no Handoffs yet.");
43
43
  return;
44
44
  }
45
45
  for (const h of handoffs) {
@@ -1,15 +1,15 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import path from "node:path";
3
3
  import * as p from "@clack/prompts";
4
- import { createWorkspaceViaAsk, createOrganization, getWorkspace, listWorkspaces, listOrganizations, resolveNuStackUrl, } from "../lib/nustack_client.js";
4
+ import { createProjectViaAsk, createOrganization, getProject, listProjects, listOrganizations, resolveNuStackUrl, } from "../lib/nustack_client.js";
5
5
  import { ensureAccessToken } from "../lib/auth_grant.js";
6
6
  import { getOrCreateDeviceId } from "../lib/device_identity.js";
7
7
  import { isInteractive } from "../lib/interactive.js";
8
- import { resolveOrganizationId } from "../lib/workspace_context.js";
8
+ import { resolveOrganizationId } from "../lib/project_context.js";
9
9
  import { openLocalStore } from "../lib/local_store/database.js";
10
10
  import { readDeviceState, upsertDeviceState } from "../lib/local_store/device_state.js";
11
11
  import { hasManifest, scaffoldManifest } from "../lib/manifest.js";
12
- import { readWorkspaceManifests } from "../lib/onboarding/manifest_writer.js";
12
+ import { readProjectManifests } from "../lib/onboarding/manifest_writer.js";
13
13
  import { attachRejection, classifyFolder } from "../lib/onboarding/folder_state.js";
14
14
  import { attachExistingGit } from "../lib/onboarding/attach_existing_git.js";
15
15
  import { initFreshFolder } from "../lib/onboarding/init_fresh_folder.js";
@@ -30,7 +30,7 @@ export async function runInit(options) {
30
30
  return;
31
31
  await runOnboardingFlow(baseUrl, accessToken, {
32
32
  yes: options.yes,
33
- workspace: options.workspace,
33
+ project: options.project,
34
34
  organization: options.organization,
35
35
  });
36
36
  }
@@ -75,18 +75,18 @@ export async function runOnboardingFlow(baseUrl, accessToken, options) {
75
75
  }
76
76
  }
77
77
  async function routeOnboarding(ctx, options) {
78
- if (options.workspaceId) {
79
- await routeWebFirst(ctx, options, options.workspaceId);
78
+ if (options.projectId) {
79
+ await routeWebFirst(ctx, options, options.projectId);
80
80
  return;
81
81
  }
82
82
  const state = classifyFolder(ctx.cwd);
83
83
  if (state.class === "git_worktree" && state.repository && state.git) {
84
- await routeExistingGit(ctx, options, state.repository.repositoryRoot, state.git.markerWorkspaceId);
84
+ await routeExistingGit(ctx, options, state.repository.repositoryRoot, state.git.markerProjectId);
85
85
  return;
86
86
  }
87
87
  await routeFreshFolder(ctx, options, state.class === "populated_non_git");
88
88
  }
89
- async function routeExistingGit(ctx, options, root, markerWorkspaceId) {
89
+ async function routeExistingGit(ctx, options, root, markerProjectId) {
90
90
  const state = classifyFolder(ctx.cwd);
91
91
  const rejection = state.git ? attachRejection(state.git) : null;
92
92
  if (rejection) {
@@ -96,12 +96,12 @@ async function routeExistingGit(ctx, options, root, markerWorkspaceId) {
96
96
  if (!hasManifest(root)) {
97
97
  await scaffoldManifest(root, deriveRepoName(path.basename(root)));
98
98
  }
99
- let workspaceId;
99
+ let projectId;
100
100
  let organizationId;
101
- if (markerWorkspaceId) {
102
- workspaceId = markerWorkspaceId;
103
- const manifests = await readWorkspaceManifests(root);
104
- const recorded = manifests.workspaceManifest?.organizationId ?? manifests.serviceWorkspaceBinding?.organizationId ?? null;
101
+ if (markerProjectId) {
102
+ projectId = markerProjectId;
103
+ const manifests = await readProjectManifests(root);
104
+ const recorded = manifests.projectManifest?.organizationId ?? manifests.serviceProjectBinding?.organizationId ?? null;
105
105
  const resolved = recorded ?? (await resolveOrganizationInteractive(ctx, options));
106
106
  if (!resolved)
107
107
  return;
@@ -112,14 +112,14 @@ async function routeExistingGit(ctx, options, root, markerWorkspaceId) {
112
112
  if (!resolved)
113
113
  return;
114
114
  organizationId = resolved;
115
- const chosen = await chooseOrCreateWorkspace(ctx, organizationId, root, "existing_local");
115
+ const chosen = await chooseOrCreateProject(ctx, organizationId, root, "existing_local");
116
116
  if (!chosen)
117
117
  return;
118
- workspaceId = chosen;
118
+ projectId = chosen;
119
119
  }
120
120
  const spinner = p.spinner();
121
121
  spinner.start("Attaching this repository");
122
- const outcome = await attachExistingGit(ctx, { workspaceId, organizationId, confirmRebind: true });
122
+ const outcome = await attachExistingGit(ctx, { projectId, organizationId, confirmRebind: true });
123
123
  spinner.stop(outcome.ok ? "Attached" : "Attach failed", outcome.ok ? 0 : 1);
124
124
  if (outcome.ok)
125
125
  reportSuccess(outcome.result);
@@ -130,7 +130,7 @@ async function routeFreshFolder(ctx, options, populated) {
130
130
  const organizationId = await resolveOrganizationInteractive(ctx, options);
131
131
  if (!organizationId)
132
132
  return;
133
- const identity = await promptWorkspaceIdentity(ctx);
133
+ const identity = await promptProjectIdentity(ctx);
134
134
  if (!identity)
135
135
  return;
136
136
  const input = { organizationId, name: identity.name, slug: identity.slug, hasUi: true };
@@ -148,26 +148,26 @@ async function routeFreshFolder(ctx, options, populated) {
148
148
  else
149
149
  reportFailure(outcome);
150
150
  }
151
- async function routeWebFirst(ctx, options, workspaceId) {
151
+ async function routeWebFirst(ctx, options, projectId) {
152
152
  const organizationId = await resolveOrganizationInteractive(ctx, options);
153
153
  if (!organizationId)
154
154
  return;
155
- let outcome = await webFirstAttach(ctx, { workspaceId, organizationId, confirmPopulated: ctx.yes || undefined });
155
+ let outcome = await webFirstAttach(ctx, { projectId, organizationId, confirmPopulated: ctx.yes || undefined });
156
156
  if (!outcome.ok && outcome.code === "CONFIRMATION_REQUIRED") {
157
157
  const confirmed = ctx.yes || (ctx.interactive && (await confirm(outcome.message)));
158
158
  if (!confirmed) {
159
159
  p.outro("No changes made — the folder was left untouched.");
160
160
  return;
161
161
  }
162
- outcome = await webFirstAttach(ctx, { workspaceId, organizationId, confirmPopulated: true });
162
+ outcome = await webFirstAttach(ctx, { projectId, organizationId, confirmPopulated: true });
163
163
  }
164
164
  if (!outcome.ok) {
165
165
  reportFailure(outcome);
166
166
  return;
167
167
  }
168
168
  if (outcome.result.overwriteRequired) {
169
- p.log.warn(`Local content diverges from the Workspace on ${outcome.result.divergent.map((d) => d.path).join(", ")}. ` +
170
- "Nothing was changed — review and approve with `nustack workspace sync approve` (a recovery bundle is kept).");
169
+ p.log.warn(`Local content diverges from the Project on ${outcome.result.divergent.map((d) => d.path).join(", ")}. ` +
170
+ "Nothing was changed — review and approve with `nustack project sync approve` (a recovery bundle is kept).");
171
171
  }
172
172
  reportSuccess(outcome.result);
173
173
  }
@@ -192,7 +192,7 @@ async function resolveOrganizationInteractive(ctx, options) {
192
192
  return organization.id;
193
193
  }
194
194
  const pick = await p.select({
195
- message: "Which Organization should this Workspace live in?",
195
+ message: "Which Organization should this Project live in?",
196
196
  options: memberships.map((m) => ({ value: m.organization.id, label: `${m.organization.label} (${m.organization.slug})` })),
197
197
  });
198
198
  if (p.isCancel(pick))
@@ -200,15 +200,15 @@ async function resolveOrganizationInteractive(ctx, options) {
200
200
  upsertDeviceState(ctx.store, { activeOrganizationId: String(pick) });
201
201
  return String(pick);
202
202
  }
203
- async function chooseOrCreateWorkspace(ctx, organizationId, root, entryMode) {
203
+ async function chooseOrCreateProject(ctx, organizationId, root, entryMode) {
204
204
  const CREATE = "__create__";
205
- const workspaces = ctx.interactive ? await listWorkspaces(ctx.baseUrl, ctx.accessToken, organizationId) : [];
205
+ const projects = ctx.interactive ? await listProjects(ctx.baseUrl, ctx.accessToken, organizationId) : [];
206
206
  const pick = ctx.interactive
207
207
  ? await p.select({
208
- message: "Attach this repository to which Workspace?",
208
+ message: "Attach this repository to which Project?",
209
209
  options: [
210
- { value: CREATE, label: "Create a new Workspace" },
211
- ...workspaces.map((a) => ({ value: a.id, label: `${a.label} (${a.id})` })),
210
+ { value: CREATE, label: "Create a new Project" },
211
+ ...projects.map((a) => ({ value: a.id, label: `${a.label} (${a.id})` })),
212
212
  ],
213
213
  })
214
214
  : CREATE;
@@ -216,33 +216,33 @@ async function chooseOrCreateWorkspace(ctx, organizationId, root, entryMode) {
216
216
  return null;
217
217
  if (pick !== CREATE)
218
218
  return String(pick);
219
- const identity = await promptWorkspaceIdentity(ctx, deriveRepoName(path.basename(root)));
219
+ const identity = await promptProjectIdentity(ctx, deriveRepoName(path.basename(root)));
220
220
  if (!identity)
221
221
  return null;
222
- return createAndAwaitWorkspace(ctx, organizationId, identity, entryMode);
222
+ return createAndAwaitProject(ctx, organizationId, identity, entryMode);
223
223
  }
224
- async function createAndAwaitWorkspace(ctx, organizationId, identity, entryMode) {
224
+ async function createAndAwaitProject(ctx, organizationId, identity, entryMode) {
225
225
  void entryMode;
226
226
  const spinner = p.spinner();
227
- spinner.start(`Creating Workspace "${identity.name}"`);
227
+ spinner.start(`Creating Project "${identity.name}"`);
228
228
  try {
229
- const outcome = await createWorkspaceViaAsk(ctx.baseUrl, ctx.accessToken, organizationId, `Create a workspace named "${identity.name}".`);
230
- const final = await getWorkspace(ctx.baseUrl, ctx.accessToken, outcome.workspaceId);
229
+ const outcome = await createProjectViaAsk(ctx.baseUrl, ctx.accessToken, organizationId, `Create a project named "${identity.name}".`);
230
+ const final = await getProject(ctx.baseUrl, ctx.accessToken, outcome.projectId);
231
231
  const failed = final.status === "initialization_failed" || final.initStatus === "failed";
232
- spinner.stop(failed ? "Workspace initialization failed" : `Workspace ${outcome.workspaceId} ready`, failed ? 1 : 0);
233
- return failed ? null : outcome.workspaceId;
232
+ spinner.stop(failed ? "Project initialization failed" : `Project ${outcome.projectId} ready`, failed ? 1 : 0);
233
+ return failed ? null : outcome.projectId;
234
234
  }
235
235
  catch (error) {
236
- spinner.stop("Workspace creation failed", 1);
236
+ spinner.stop("Project creation failed", 1);
237
237
  p.log.error(error instanceof Error ? error.message : String(error));
238
238
  return null;
239
239
  }
240
240
  }
241
- async function promptWorkspaceIdentity(ctx, fallback) {
241
+ async function promptProjectIdentity(ctx, fallback) {
242
242
  const defaultName = fallback ?? deriveRepoName(path.basename(ctx.cwd));
243
243
  if (!ctx.interactive)
244
244
  return { name: defaultName, slug: deriveRepoName(defaultName) };
245
- const name = await p.text({ message: "Name this Workspace:", defaultValue: defaultName, placeholder: defaultName });
245
+ const name = await p.text({ message: "Name this Project:", defaultValue: defaultName, placeholder: defaultName });
246
246
  if (p.isCancel(name))
247
247
  return null;
248
248
  const resolved = String(name).trim() || defaultName;
@@ -253,9 +253,9 @@ async function confirm(message) {
253
253
  return !p.isCancel(answer) && answer === true;
254
254
  }
255
255
  function reportSuccess(result) {
256
- p.log.success(`Bound to Workspace ${result.workspaceId} on branch ${result.boundBranch} (binding ${result.bindingId}).`);
256
+ p.log.success(`Bound to Project ${result.projectId} on branch ${result.boundBranch} (binding ${result.bindingId}).`);
257
257
  p.log.info(`Toolkit ${result.toolkit.name}@${result.toolkit.version}. No Git push, pull, or remote was created.`);
258
- p.outro("Ready — `nustack workspace status` · `nustack docs list` · `nustack workspace sync now`.");
258
+ p.outro("Ready — `nustack project status` · `nustack docs list` · `nustack project sync now`.");
259
259
  }
260
260
  function reportFailure(failure) {
261
261
  p.log.error(failure.message);
@@ -264,8 +264,8 @@ function reportFailure(failure) {
264
264
  }
265
265
  function rejectionMessage(rejection) {
266
266
  switch (rejection) {
267
- case "nested_workspace":
268
- return "This repository is nested inside another NuStack Workspace — one Workspace has one logical repository root (F-014).";
267
+ case "nested_project":
268
+ return "This repository is nested inside another NuStack Project — one Project has one logical repository root (F-014).";
269
269
  case "operation_in_progress":
270
270
  return "A merge, rebase, cherry-pick, or revert is in progress — finish or abort it, then re-run.";
271
271
  case "detached_head":
@@ -12,13 +12,13 @@ import { listBundles, restoreBundle, deleteBundle, sweepExpiredBundles, } from "
12
12
  import { resolveRepository } from "../lib/git-sync/core/repository.js";
13
13
  import { registerOutbound, createUploadClient, deviceTokenProvider, describeHistoryDecision, publishCurrentState, adoptWithoutPublishing, } from "../lib/git-sync/outbound/index.js";
14
14
  import { latestAcceptedUpload } from "../lib/local_store/accepted_uploads.js";
15
- const ONBOARDING_POINTER = "No NuStack Workspace bindings on this machine. Run `nustack workspace attach` inside a Git repository to bind it.";
15
+ const ONBOARDING_POINTER = "No NuStack Project bindings on this machine. Run `nustack project attach` inside a Git repository to bind it.";
16
16
  function writeLine(line) {
17
17
  process.stdout.write(`${line}\n`);
18
18
  }
19
- export function registerWorkspaceSyncCommands(workspaceCommand, getGlobalOptions) {
19
+ export function registerProjectSyncCommands(projectCommand, getGlobalOptions) {
20
20
  void getGlobalOptions;
21
- const sync = workspaceCommand
21
+ const sync = projectCommand
22
22
  .command("sync")
23
23
  .description("Synchronize NuStack Documents with your local Git repository");
24
24
  sync
@@ -393,7 +393,7 @@ function outboundStatusFor(store, binding) {
393
393
  nextRetryAt: row.next != null ? new Date(row.next).toISOString() : null,
394
394
  blockedErrorCode: binding.status === "failed" ? binding.lastErrorCode : null,
395
395
  lastAcceptedHandoffId: accepted?.handoffId ?? null,
396
- lastAcceptedWorkspaceSequence: accepted?.workspaceSequence ?? null,
396
+ lastAcceptedProjectSequence: accepted?.projectSequence ?? null,
397
397
  historyDecisionPending,
398
398
  };
399
399
  }
@@ -527,7 +527,7 @@ async function runStatus(options) {
527
527
  const outbound = observation.outbound;
528
528
  const outboundNote = (outbound.pendingUploadCount > 0 ? ` queued=${outbound.pendingUploadCount}` : "") +
529
529
  (outbound.blockedErrorCode ? ` blocked=${outbound.blockedErrorCode}` : "") +
530
- (outbound.lastAcceptedWorkspaceSequence != null ? ` accepted@${outbound.lastAcceptedWorkspaceSequence}` : "") +
530
+ (outbound.lastAcceptedProjectSequence != null ? ` accepted@${outbound.lastAcceptedProjectSequence}` : "") +
531
531
  (outbound.historyDecisionPending ? " history-decision-required" : "");
532
532
  writeLine(`${observation.bindingId} ${observation.status} branch=${branchNote} ` +
533
533
  `head=${head} processed=${processed} pending=${observation.pendingCommitCount}` +