@nurix/nustack 0.10.0-dev.12 → 0.10.0-dev.14

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 +3 -3
  52. package/dist/lib/local_store/workspace_events_cache.js +0 -54
@@ -7,24 +7,24 @@ import { runRepositoryOnboarding, } from "./binding.js";
7
7
  export async function attachExistingGit(ctx, options) {
8
8
  const state = classifyFolder(ctx.cwd);
9
9
  if (state.class !== "git_worktree" || !state.repository || !state.git) {
10
- return { ok: false, code: "NOT_A_WORKTREE", message: "`nustack workspace attach` needs an existing Git worktree — run `nustack init` to establish one." };
10
+ return { ok: false, code: "NOT_A_WORKTREE", message: "`nustack project attach` needs an existing Git worktree — run `nustack init` to establish one." };
11
11
  }
12
12
  const rejection = attachRejection(state.git);
13
13
  if (rejection) {
14
14
  return { ok: false, code: rejection.toUpperCase(), message: rejectionMessage(rejection) };
15
15
  }
16
- if (state.git.markerWorkspaceId && state.git.markerWorkspaceId !== options.workspaceId && !options.confirmRebind) {
16
+ if (state.git.markerProjectId && state.git.markerProjectId !== options.projectId && !options.confirmRebind) {
17
17
  return {
18
18
  ok: false,
19
19
  code: "REBIND_CONFIRMATION_REQUIRED",
20
- message: `This repository is bound to Workspace ${state.git.markerWorkspaceId}. Re-run with confirmation to rebind it to ${options.workspaceId}.`,
20
+ message: `This repository is bound to Project ${state.git.markerProjectId}. Re-run with confirmation to rebind it to ${options.projectId}.`,
21
21
  };
22
22
  }
23
23
  const boundBranch = readSymbolicBranch(state.repository);
24
24
  if (!boundBranch) {
25
25
  return { ok: false, code: "DETACHED_HEAD", message: "HEAD is detached — check out a branch before attaching." };
26
26
  }
27
- const profile = await fetchToolkitProfile(ctx.baseUrl, ctx.accessToken, options.workspaceId);
27
+ const profile = await fetchToolkitProfile(ctx.baseUrl, ctx.accessToken, options.projectId);
28
28
  const baselineCommitId = readHeadCommit(state.repository);
29
29
  const result = await runRepositoryOnboarding({
30
30
  store: ctx.store,
@@ -32,7 +32,7 @@ export async function attachExistingGit(ctx, options) {
32
32
  accessToken: ctx.accessToken,
33
33
  deviceRef: ctx.deviceRef,
34
34
  flow: "existing_git",
35
- workspaceId: options.workspaceId,
35
+ projectId: options.projectId,
36
36
  organizationId: options.organizationId,
37
37
  repository: state.repository,
38
38
  boundBranch,
@@ -55,8 +55,8 @@ async function reconcileLatest(ctx, bindingId) {
55
55
  }
56
56
  function rejectionMessage(rejection) {
57
57
  switch (rejection) {
58
- case "nested_workspace":
59
- return "This repository is nested inside another NuStack Workspace — one Workspace has one logical repository root (F-014).";
58
+ case "nested_project":
59
+ return "This repository is nested inside another NuStack Project — one Project has one logical repository root (F-014).";
60
60
  case "operation_in_progress":
61
61
  return "A merge, rebase, cherry-pick, or revert is in progress — finish or abort it, then re-attach.";
62
62
  case "detached_head":
@@ -3,21 +3,21 @@ import { createBinding } from "../nustack_client.js";
3
3
  import { getBindingById, getBindingByRepositoryRoot, insertBinding, updateBindingCursor, updateBindingStatus, } from "../local_store/bindings.js";
4
4
  import { ensureBindingTelemetry } from "../session_telemetry.js";
5
5
  import { readHeadCommit } from "../git-sync/core/repository.js";
6
- import { writeWorkspaceManifests } from "./manifest_writer.js";
6
+ import { writeProjectManifests } from "./manifest_writer.js";
7
7
  import { installToolkit } from "../etna/manager.js";
8
8
  import { serializeProfileForBinding } from "../etna/profile.js";
9
9
  import { SYNC_PROTOCOL_VERSION } from "../git-sync/whitelist/types.js";
10
10
  export function computeRepositoryFingerprint(repository) {
11
11
  return createHash("sha256").update(repository.commonDir).digest("hex");
12
12
  }
13
- function bindingIdempotencyKey(workspaceId, deviceRef, fingerprint) {
14
- return createHash("sha256").update(`binding:${workspaceId}:${deviceRef}:${fingerprint}`).digest("hex");
13
+ function bindingIdempotencyKey(projectId, deviceRef, fingerprint) {
14
+ return createHash("sha256").update(`binding:${projectId}:${deviceRef}:${fingerprint}`).digest("hex");
15
15
  }
16
16
  export async function establishBinding(input) {
17
17
  const { store, repository } = input;
18
18
  const fingerprint = computeRepositoryFingerprint(repository);
19
19
  const existing = getBindingByRepositoryRoot(store, repository.repositoryRoot);
20
- if (existing && existing.workspaceId === input.workspaceId) {
20
+ if (existing && existing.projectId === input.projectId) {
21
21
  if (input.baselineCommitId && existing.lastProcessedCommitId === null) {
22
22
  updateBindingCursor(store, existing.id, { lastProcessedCommitId: input.baselineCommitId });
23
23
  }
@@ -26,11 +26,11 @@ export async function establishBinding(input) {
26
26
  await maybeRunTelemetry(input, existing);
27
27
  return { binding: getBindingById(store, existing.id), created: false };
28
28
  }
29
- const cloudBinding = await createBinding(input.baseUrl, input.accessToken, input.workspaceId, { repositoryFingerprint: fingerprint, boundBranch: input.boundBranch, protocolVersion: SYNC_PROTOCOL_VERSION }, bindingIdempotencyKey(input.workspaceId, input.deviceRef, fingerprint));
29
+ const cloudBinding = await createBinding(input.baseUrl, input.accessToken, input.projectId, { repositoryFingerprint: fingerprint, boundBranch: input.boundBranch, protocolVersion: SYNC_PROTOCOL_VERSION }, bindingIdempotencyKey(input.projectId, input.deviceRef, fingerprint));
30
30
  const row = getBindingById(store, cloudBinding.id) ??
31
31
  insertBinding(store, {
32
32
  id: cloudBinding.id,
33
- workspaceId: input.workspaceId,
33
+ projectId: input.projectId,
34
34
  organizationId: input.organizationId,
35
35
  repositoryRoot: repository.repositoryRoot,
36
36
  gitDir: repository.gitDir,
@@ -55,7 +55,7 @@ export async function runRepositoryOnboarding(input) {
55
55
  store: input.store,
56
56
  baseUrl: input.baseUrl,
57
57
  accessToken: input.accessToken,
58
- workspaceId: input.workspaceId,
58
+ projectId: input.projectId,
59
59
  organizationId: input.organizationId,
60
60
  repository: input.repository,
61
61
  boundBranch: input.boundBranch,
@@ -65,8 +65,8 @@ export async function runRepositoryOnboarding(input) {
65
65
  runTelemetry: input.runTelemetry,
66
66
  });
67
67
  const install = await installToolkit(input.store, established.binding.id, input.profile, input.repository.repositoryRoot, input.toolkitDeps ?? {});
68
- const workspaceManifest = await writeWorkspaceManifests(input.repository.repositoryRoot, {
69
- workspaceId: input.workspaceId,
68
+ const projectManifest = await writeProjectManifests(input.repository.repositoryRoot, {
69
+ projectId: input.projectId,
70
70
  organizationId: input.organizationId,
71
71
  toolkit: {
72
72
  name: input.profile.name,
@@ -77,7 +77,7 @@ export async function runRepositoryOnboarding(input) {
77
77
  });
78
78
  return {
79
79
  flow: input.flow,
80
- workspaceId: input.workspaceId,
80
+ projectId: input.projectId,
81
81
  organizationId: input.organizationId,
82
82
  bindingId: established.binding.id,
83
83
  boundBranch: input.boundBranch,
@@ -85,7 +85,7 @@ export async function runRepositoryOnboarding(input) {
85
85
  toolkit: { name: input.profile.name, version: input.profile.version },
86
86
  reused: !established.created,
87
87
  install,
88
- workspaceManifest,
88
+ projectManifest,
89
89
  noPush: true,
90
90
  };
91
91
  }
@@ -3,7 +3,7 @@ import path from "node:path";
3
3
  import { resolveRepository, readSymbolicBranch } from "../git-sync/core/repository.js";
4
4
  import { readGitOperationState } from "../git-sync/core/operation_state.js";
5
5
  import { NotARepositoryError } from "../git-sync/core/git_runner.js";
6
- import { legacyAppJsonPath, workspaceJsonPath } from "../workspace_context.js";
6
+ import { legacyAppJsonPath, projectJsonPath } from "../project_context.js";
7
7
  const SAFE_OS_METADATA = new Set([".DS_Store", "Thumbs.db", "desktop.ini", ".localized"]);
8
8
  function significantEntries(dir) {
9
9
  let names;
@@ -15,8 +15,8 @@ function significantEntries(dir) {
15
15
  }
16
16
  return names.filter((name) => name !== ".git" && !SAFE_OS_METADATA.has(name));
17
17
  }
18
- function readMarkerWorkspaceId(root) {
19
- const v2 = read(workspaceJsonPath(root));
18
+ function readMarkerProjectId(root) {
19
+ const v2 = read(projectJsonPath(root));
20
20
  if (v2 && v2.schemaVersion === 2) {
21
21
  const id = trimmedField(v2.workspaceId);
22
22
  if (id)
@@ -38,7 +38,7 @@ function read(filePath) {
38
38
  function trimmedField(value) {
39
39
  return typeof value === "string" && value.trim() !== "" ? value.trim() : null;
40
40
  }
41
- function isNestedUnderWorkspace(root) {
41
+ function isNestedUnderProject(root) {
42
42
  let dir = path.dirname(root);
43
43
  while (dir && dir !== path.dirname(dir)) {
44
44
  const nustack = path.join(dir, ".nustack");
@@ -66,7 +66,7 @@ export function classifyFolder(dir) {
66
66
  const symbolicBranch = readSymbolicBranch(repository);
67
67
  const operation = readGitOperationState(repository);
68
68
  const pointerFileGit = lstatSync(gitPath).isFile();
69
- const markerWorkspaceId = readMarkerWorkspaceId(repository.repositoryRoot);
69
+ const markerProjectId = readMarkerProjectId(repository.repositoryRoot);
70
70
  return {
71
71
  class: "git_worktree",
72
72
  entries,
@@ -77,9 +77,9 @@ export function classifyFolder(dir) {
77
77
  operationKind: operation.kind,
78
78
  operationInProgress: operation.kind !== "normal",
79
79
  pointerFileGit,
80
- hasWorkspaceMarker: markerWorkspaceId !== null,
81
- markerWorkspaceId,
82
- nestedUnderWorkspace: isNestedUnderWorkspace(repository.repositoryRoot),
80
+ hasProjectMarker: markerProjectId !== null,
81
+ markerProjectId,
82
+ nestedUnderProject: isNestedUnderProject(repository.repositoryRoot),
83
83
  symbolicBranch,
84
84
  },
85
85
  };
@@ -89,8 +89,8 @@ export function classifyFolder(dir) {
89
89
  return { class: "populated_non_git", entries };
90
90
  }
91
91
  export function attachRejection(git) {
92
- if (git.nestedUnderWorkspace)
93
- return "nested_workspace";
92
+ if (git.nestedUnderProject)
93
+ return "nested_project";
94
94
  if (git.operationInProgress)
95
95
  return "operation_in_progress";
96
96
  if (git.detachedHead)
@@ -35,7 +35,7 @@ export function decideOnboardingApply(repo, targets) {
35
35
  return { kind: "apply" };
36
36
  return { kind: "overwrite_required", divergent: decision.divergent };
37
37
  }
38
- export function composeOnboardingCommit(store, repo, binding, handoffId, workspaceId, entries) {
38
+ export function composeOnboardingCommit(store, repo, binding, handoffId, projectId, entries) {
39
39
  const oldHead = readHeadCommit(repo);
40
40
  const targets = entries.map((entry) => ({
41
41
  path: entry.path,
@@ -49,7 +49,7 @@ export function composeOnboardingCommit(store, repo, binding, handoffId, workspa
49
49
  if (entry.operation === "upsert" && entry.bytes)
50
50
  bytesByPath.set(entry.path, entry.bytes);
51
51
  }
52
- const message = buildCommitMessage(handoffId, workspaceId, null);
52
+ const message = buildCommitMessage(handoffId, projectId, null);
53
53
  const built = buildCommit(repo, oldHead, targets, bytesByPath, message, {
54
54
  authorName: NUSTACK_SYNC_IDENTITY.name,
55
55
  authorEmail: NUSTACK_SYNC_IDENTITY.email,
@@ -1,6 +1,6 @@
1
1
  import { readFileSync } from "node:fs";
2
2
  import path from "node:path";
3
- import { acknowledgeBindingCursor, createWorkspaceViaAsk, getWorkspace, latestHandoff, } from "../nustack_client.js";
3
+ import { acknowledgeBindingCursor, createProjectViaAsk, getProject, latestHandoff, } from "../nustack_client.js";
4
4
  import { getBindingById } from "../local_store/bindings.js";
5
5
  import { hasManifest, scaffoldManifest } from "../manifest.js";
6
6
  import { fetchToolkitProfile, toolkitProfileFromManifest, ToolkitProfileUnavailableError } from "../etna/profile.js";
@@ -13,7 +13,7 @@ export const MANAGED_ONBOARDING_FILES = [".nustack/workspace.json", "nustack.ser
13
13
  export async function initFreshFolder(ctx, options) {
14
14
  const state = classifyFolder(ctx.cwd);
15
15
  if (state.class === "git_worktree") {
16
- return { ok: false, code: "ALREADY_A_WORKTREE", message: "This folder is already a Git worktree — use `nustack workspace attach` instead of `nustack init`." };
16
+ return { ok: false, code: "ALREADY_A_WORKTREE", message: "This folder is already a Git worktree — use `nustack project attach` instead of `nustack init`." };
17
17
  }
18
18
  if (state.class === "populated_non_git" && options.confirmPopulated !== true) {
19
19
  return {
@@ -28,13 +28,13 @@ export async function initFreshFolder(ctx, options) {
28
28
  }
29
29
  const repository = initGitRepository(ctx.cwd);
30
30
  const boundBranch = "main";
31
- const workspaceId = options.workspaceId ?? (await createFreshWorkspace(ctx, state.class, options));
32
- if (workspaceId === null) {
33
- return { ok: false, code: "WORKSPACE_INITIALIZATION_FAILED", message: "The Workspace failed to initialize — check the Organization and try again." };
31
+ const projectId = options.projectId ?? (await createFreshProject(ctx, state.class, options));
32
+ if (projectId === null) {
33
+ return { ok: false, code: "PROJECT_INITIALIZATION_FAILED", message: "The Project failed to initialize — check the Organization and try again." };
34
34
  }
35
- const profile = await resolveProfile(ctx, workspaceId);
35
+ const profile = await resolveProfile(ctx, projectId);
36
36
  if (!profile) {
37
- return { ok: false, code: "HARNESS_PROFILE_UNAVAILABLE", message: "The Workspace did not project a Toolkit Profile pin — the owning server plan must expose it." };
37
+ return { ok: false, code: "HARNESS_PROFILE_UNAVAILABLE", message: "The Project did not project a Toolkit Profile pin — the owning server plan must expose it." };
38
38
  }
39
39
  const result = await runRepositoryOnboarding({
40
40
  store: ctx.store,
@@ -42,7 +42,7 @@ export async function initFreshFolder(ctx, options) {
42
42
  accessToken: ctx.accessToken,
43
43
  deviceRef: ctx.deviceRef,
44
44
  flow: "fresh_folder",
45
- workspaceId,
45
+ projectId,
46
46
  organizationId: options.organizationId,
47
47
  repository,
48
48
  boundBranch,
@@ -51,7 +51,7 @@ export async function initFreshFolder(ctx, options) {
51
51
  toolkitDeps: ctx.toolkitDeps,
52
52
  runTelemetry: ctx.runTelemetry,
53
53
  });
54
- const seed = await latestHandoff(ctx.baseUrl, ctx.accessToken, workspaceId);
54
+ const seed = await latestHandoff(ctx.baseUrl, ctx.accessToken, projectId);
55
55
  const entries = MANAGED_ONBOARDING_FILES.map((rel) => ({
56
56
  path: rel,
57
57
  operation: "upsert",
@@ -67,8 +67,8 @@ export async function initFreshFolder(ctx, options) {
67
67
  }
68
68
  }
69
69
  const binding = getBindingById(ctx.store, result.bindingId);
70
- const handoffId = seed?.id ?? `onboarding_seed:${workspaceId}`;
71
- const commit = composeOnboardingCommit(ctx.store, repository, binding, handoffId, workspaceId, entries);
70
+ const handoffId = seed?.id ?? `onboarding_seed:${projectId}`;
71
+ const commit = composeOnboardingCommit(ctx.store, repository, binding, handoffId, projectId, entries);
72
72
  if (seed) {
73
73
  await acknowledgeBindingCursor(ctx.baseUrl, ctx.accessToken, { bindingId: result.bindingId, lastAppliedSequence: 0 }).catch(() => { });
74
74
  }
@@ -77,22 +77,22 @@ export async function initFreshFolder(ctx, options) {
77
77
  result: { ...result, baselineCommitId: commit.commitId, rootCommitId: commit.commitId, changedPaths: commit.changedPaths, handoffId: seed?.id ?? null },
78
78
  };
79
79
  }
80
- async function createFreshWorkspace(ctx, folderClass, options) {
80
+ async function createFreshProject(ctx, folderClass, options) {
81
81
  void folderClass;
82
- const outcome = await createWorkspaceViaAsk(ctx.baseUrl, ctx.accessToken, options.organizationId, `Create a workspace named "${options.name}".`);
83
- const final = await getWorkspace(ctx.baseUrl, ctx.accessToken, outcome.workspaceId);
82
+ const outcome = await createProjectViaAsk(ctx.baseUrl, ctx.accessToken, options.organizationId, `Create a project named "${options.name}".`);
83
+ const final = await getProject(ctx.baseUrl, ctx.accessToken, outcome.projectId);
84
84
  const failed = final.status === "initialization_failed" || final.initStatus === "failed";
85
- return failed ? null : outcome.workspaceId;
85
+ return failed ? null : outcome.projectId;
86
86
  }
87
- async function resolveProfile(ctx, workspaceId) {
87
+ async function resolveProfile(ctx, projectId) {
88
88
  try {
89
- return await fetchToolkitProfile(ctx.baseUrl, ctx.accessToken, workspaceId);
89
+ return await fetchToolkitProfile(ctx.baseUrl, ctx.accessToken, projectId);
90
90
  }
91
91
  catch (error) {
92
92
  if (!(error instanceof ToolkitProfileUnavailableError))
93
93
  throw error;
94
94
  }
95
- const seed = await latestHandoff(ctx.baseUrl, ctx.accessToken, workspaceId);
95
+ const seed = await latestHandoff(ctx.baseUrl, ctx.accessToken, projectId);
96
96
  if (!seed)
97
97
  return null;
98
98
  const manifest = await fetchHandoffManifest(ctx.baseUrl, ctx.accessToken, { handoffId: seed.id, bindingId: "" }).catch(() => null);
@@ -1,14 +1,14 @@
1
1
  import { readFile, writeFile, rename, unlink, mkdir } from "node:fs/promises";
2
2
  import { existsSync } from "node:fs";
3
3
  import path from "node:path";
4
- import { legacyAppJsonPath, readBindingMarker, workspaceJsonPath } from "../workspace_context.js";
4
+ import { legacyAppJsonPath, readBindingMarker, projectJsonPath } from "../project_context.js";
5
5
  import { manifestPath } from "../manifest.js";
6
6
  const CURRENT_BINDING_PROTOCOL_VERSION = 1;
7
7
  const CURRENT_DOCUMENT_PROTOCOL_VERSION = 1;
8
- function buildWorkspaceManifest(identity) {
8
+ function buildProjectManifest(identity) {
9
9
  return {
10
10
  schemaVersion: 2,
11
- workspaceId: identity.workspaceId,
11
+ workspaceId: identity.projectId,
12
12
  organizationId: identity.organizationId,
13
13
  bindingProtocolVersion: identity.bindingProtocolVersion ?? CURRENT_BINDING_PROTOCOL_VERSION,
14
14
  documentProtocolVersion: identity.documentProtocolVersion ?? CURRENT_DOCUMENT_PROTOCOL_VERSION,
@@ -35,10 +35,10 @@ async function restore(target, prior) {
35
35
  await writeFile(target, prior, "utf8");
36
36
  }
37
37
  }
38
- export async function writeWorkspaceManifests(root, identity, hooks = {}) {
38
+ export async function writeProjectManifests(root, identity, hooks = {}) {
39
39
  const servicePath = manifestPath(root);
40
40
  if (!existsSync(servicePath)) {
41
- throw new Error("nustack.service.json is required before writing the Workspace binding — scaffold it first.");
41
+ throw new Error("nustack.service.json is required before writing the Project binding — scaffold it first.");
42
42
  }
43
43
  const serviceRaw = await readFile(servicePath, "utf8");
44
44
  let serviceManifest;
@@ -50,66 +50,66 @@ export async function writeWorkspaceManifests(root, identity, hooks = {}) {
50
50
  }
51
51
  const nextService = {
52
52
  ...serviceManifest,
53
- nustackWorkspace: { v: 2, workspaceId: identity.workspaceId, organizationId: identity.organizationId },
53
+ nustackWorkspace: { v: 2, workspaceId: identity.projectId, organizationId: identity.organizationId },
54
54
  toolkit: { name: identity.toolkit.name, version: identity.toolkit.version },
55
55
  };
56
56
  delete nextService.nustackApp;
57
- const workspaceManifest = buildWorkspaceManifest(identity);
58
- const workspaceDir = path.dirname(workspaceJsonPath(root));
59
- await mkdir(workspaceDir, { recursive: true });
60
- const workspacePrior = await atomicWrite(workspaceJsonPath(root), JSON.stringify(workspaceManifest, null, 2) + "\n");
57
+ const projectManifest = buildProjectManifest(identity);
58
+ const projectDir = path.dirname(projectJsonPath(root));
59
+ await mkdir(projectDir, { recursive: true });
60
+ const projectPrior = await atomicWrite(projectJsonPath(root), JSON.stringify(projectManifest, null, 2) + "\n");
61
61
  try {
62
62
  if (hooks.beforeServiceWrite)
63
63
  await hooks.beforeServiceWrite();
64
64
  await atomicWrite(servicePath, JSON.stringify(nextService, null, 4) + "\n");
65
65
  }
66
66
  catch (error) {
67
- await restore(workspaceJsonPath(root), workspacePrior);
67
+ await restore(projectJsonPath(root), projectPrior);
68
68
  throw error;
69
69
  }
70
- return workspaceManifest;
70
+ return projectManifest;
71
71
  }
72
72
  function toolkitEqual(a, b) {
73
73
  return a !== null && b !== null && a.name === b.name && a.version === b.version;
74
74
  }
75
- export async function readWorkspaceManifests(root) {
76
- const workspaceManifest = await readWorkspaceJson(root);
75
+ export async function readProjectManifests(root) {
76
+ const projectManifest = await readProjectJson(root);
77
77
  const service = await readServiceBlocks(root);
78
78
  let localDiagnosis;
79
- if (!workspaceManifest || !service.toolkit) {
79
+ if (!projectManifest || !service.toolkit) {
80
80
  localDiagnosis = "missing";
81
81
  }
82
82
  else {
83
- localDiagnosis = toolkitEqual(workspaceManifest.toolkit, service.toolkit) ? "aligned" : "divergent";
83
+ localDiagnosis = toolkitEqual(projectManifest.toolkit, service.toolkit) ? "aligned" : "divergent";
84
84
  }
85
85
  return {
86
- workspaceManifest,
87
- serviceWorkspaceBinding: service.binding,
86
+ projectManifest,
87
+ serviceProjectBinding: service.binding,
88
88
  serviceToolkit: service.toolkit,
89
89
  localDiagnosis,
90
90
  };
91
91
  }
92
- export function diagnoseManifests(cloud, workspaceJson, serviceJson) {
93
- if (!workspaceJson || !serviceJson)
92
+ export function diagnoseManifests(cloud, projectJson, serviceJson) {
93
+ if (!projectJson || !serviceJson)
94
94
  return "missing";
95
95
  if (!cloud) {
96
- return toolkitEqual(workspaceJson, serviceJson) ? "aligned" : "divergent";
96
+ return toolkitEqual(projectJson, serviceJson) ? "aligned" : "divergent";
97
97
  }
98
- const workspaceOk = toolkitEqual(cloud, workspaceJson);
98
+ const projectOk = toolkitEqual(cloud, projectJson);
99
99
  const serviceOk = toolkitEqual(cloud, serviceJson);
100
- if (workspaceOk && serviceOk)
100
+ if (projectOk && serviceOk)
101
101
  return "aligned";
102
- if (!workspaceOk && serviceOk)
103
- return "workspace_json_stale";
104
- if (workspaceOk && !serviceOk)
102
+ if (!projectOk && serviceOk)
103
+ return "project_json_stale";
104
+ if (projectOk && !serviceOk)
105
105
  return "service_json_stale";
106
106
  return "divergent";
107
107
  }
108
- async function readWorkspaceJson(root) {
108
+ async function readProjectJson(root) {
109
109
  const marker = await readBindingMarker(root);
110
110
  if (!marker)
111
111
  return null;
112
- const filePath = marker.version === 2 ? workspaceJsonPath(root) : legacyAppJsonPath(root);
112
+ const filePath = marker.version === 2 ? projectJsonPath(root) : legacyAppJsonPath(root);
113
113
  if (!existsSync(filePath))
114
114
  return null;
115
115
  try {
@@ -121,7 +121,7 @@ async function readWorkspaceJson(root) {
121
121
  return null;
122
122
  return {
123
123
  schemaVersion: 2,
124
- workspaceId: marker.workspaceId,
124
+ workspaceId: marker.projectId,
125
125
  organizationId: marker.organizationId,
126
126
  bindingProtocolVersion: typeof raw.bindingProtocolVersion === "number" ? raw.bindingProtocolVersion : 1,
127
127
  documentProtocolVersion: typeof raw.documentProtocolVersion === "number" ? raw.documentProtocolVersion : 1,
@@ -146,9 +146,9 @@ async function readServiceBlocks(root) {
146
146
  const v2 = manifest.nustackWorkspace;
147
147
  const v1 = manifest.nustackApp;
148
148
  const binding = typeof v2?.workspaceId === "string" && typeof v2?.organizationId === "string"
149
- ? { workspaceId: v2.workspaceId, organizationId: v2.organizationId }
149
+ ? { projectId: v2.workspaceId, organizationId: v2.organizationId }
150
150
  : typeof v1?.appId === "string" && typeof v1?.workspaceId === "string"
151
- ? { workspaceId: v1.appId, organizationId: v1.workspaceId }
151
+ ? { projectId: v1.appId, organizationId: v1.workspaceId }
152
152
  : null;
153
153
  const toolkit = typeof manifest.toolkit?.name === "string" && typeof manifest.toolkit?.version === "string"
154
154
  ? { name: manifest.toolkit.name, version: manifest.toolkit.version }
@@ -42,9 +42,9 @@ export async function webFirstAttach(ctx, options) {
42
42
  if (!hasManifest(repository.repositoryRoot)) {
43
43
  await scaffoldManifest(repository.repositoryRoot, deriveScaffoldSlug(repository.repositoryRoot));
44
44
  }
45
- const profile = await resolveProfile(ctx, options.workspaceId);
45
+ const profile = await resolveProfile(ctx, options.projectId);
46
46
  if (!profile)
47
- return { ok: false, code: "HARNESS_PROFILE_UNAVAILABLE", message: "The Workspace did not project a Toolkit Profile pin — the owning server plan must expose it." };
47
+ return { ok: false, code: "HARNESS_PROFILE_UNAVAILABLE", message: "The Project did not project a Toolkit Profile pin — the owning server plan must expose it." };
48
48
  const baselineCommitId = isFresh ? null : readHeadCommit(repository);
49
49
  const result = await runRepositoryOnboarding({
50
50
  store: ctx.store,
@@ -52,7 +52,7 @@ export async function webFirstAttach(ctx, options) {
52
52
  accessToken: ctx.accessToken,
53
53
  deviceRef: ctx.deviceRef,
54
54
  flow: "web_first",
55
- workspaceId: options.workspaceId,
55
+ projectId: options.projectId,
56
56
  organizationId: options.organizationId,
57
57
  repository,
58
58
  boundBranch,
@@ -62,10 +62,10 @@ export async function webFirstAttach(ctx, options) {
62
62
  runTelemetry: ctx.runTelemetry,
63
63
  });
64
64
  const binding = getBindingById(ctx.store, result.bindingId);
65
- const latest = await latestHandoff(ctx.baseUrl, ctx.accessToken, options.workspaceId).catch(() => null);
65
+ const latest = await latestHandoff(ctx.baseUrl, ctx.accessToken, options.projectId).catch(() => null);
66
66
  if (!latest) {
67
67
  const applied = isFresh
68
- ? composeOnboardingCommit(ctx.store, repository, binding, `onboarding_seed:${options.workspaceId}`, options.workspaceId, managedEntries(repository.repositoryRoot))
68
+ ? composeOnboardingCommit(ctx.store, repository, binding, `onboarding_seed:${options.projectId}`, options.projectId, managedEntries(repository.repositoryRoot))
69
69
  : null;
70
70
  return { ok: true, result: baseResult(result, applied, null, false, []) };
71
71
  }
@@ -86,7 +86,7 @@ export async function webFirstAttach(ctx, options) {
86
86
  entries.push({ path: target.path, operation: target.operation, bytes: bytesByPath.get(target.path) ?? null });
87
87
  }
88
88
  }
89
- const applied = entries.length > 0 ? composeOnboardingCommit(ctx.store, repository, binding, latest.id, options.workspaceId, entries) : null;
89
+ const applied = entries.length > 0 ? composeOnboardingCommit(ctx.store, repository, binding, latest.id, options.projectId, entries) : null;
90
90
  await acknowledgeBindingCursor(ctx.baseUrl, ctx.accessToken, { bindingId: result.bindingId, lastAppliedSequence: 0 }).catch(() => { });
91
91
  return { ok: true, result: baseResult(result, applied, latest.id, false, []) };
92
92
  }
@@ -104,27 +104,27 @@ function baseResult(result, applied, handoffId, overwriteRequired, divergent) {
104
104
  divergent,
105
105
  };
106
106
  }
107
- async function resolveProfile(ctx, workspaceId) {
107
+ async function resolveProfile(ctx, projectId) {
108
108
  try {
109
- return await fetchToolkitProfile(ctx.baseUrl, ctx.accessToken, workspaceId);
109
+ return await fetchToolkitProfile(ctx.baseUrl, ctx.accessToken, projectId);
110
110
  }
111
111
  catch (error) {
112
112
  if (!(error instanceof ToolkitProfileUnavailableError))
113
113
  throw error;
114
114
  }
115
- const seed = await latestHandoff(ctx.baseUrl, ctx.accessToken, workspaceId).catch(() => null);
115
+ const seed = await latestHandoff(ctx.baseUrl, ctx.accessToken, projectId).catch(() => null);
116
116
  if (!seed)
117
117
  return null;
118
118
  const manifest = await fetchHandoffManifest(ctx.baseUrl, ctx.accessToken, { handoffId: seed.id, bindingId: "" }).catch(() => null);
119
119
  return manifest ? toolkitProfileFromManifest(manifest) : null;
120
120
  }
121
121
  function deriveScaffoldSlug(root) {
122
- return path.basename(root).toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "") || "workspace";
122
+ return path.basename(root).toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "") || "project";
123
123
  }
124
124
  function rejectionMessage(rejection) {
125
125
  switch (rejection) {
126
- case "nested_workspace":
127
- return "This repository is nested inside another NuStack Workspace — one Workspace has one logical repository root (F-014).";
126
+ case "nested_project":
127
+ return "This repository is nested inside another NuStack Project — one Project has one logical repository root (F-014).";
128
128
  case "operation_in_progress":
129
129
  return "A merge, rebase, cherry-pick, or revert is in progress — finish or abort it, then re-attach.";
130
130
  case "detached_head":
@@ -4,22 +4,22 @@ import { existsSync } from "node:fs";
4
4
  import { readManagedValues } from "./env_file.js";
5
5
  import { manifestPath } from "./manifest.js";
6
6
  import { assertSafeOpaqueId } from "./guards.js";
7
- const NO_WORKSPACE_MESSAGE = "No Workspace id — run inside a bound repository (`.nustack/workspace.json`), pass `--workspace <id>`, add an `nustackWorkspace.workspaceId` to nustack.service.json, or set NUSTACK_WORKSPACE_ID in the managed .env block.";
8
- export function workspaceJsonPath(cwd) {
7
+ const NO_PROJECT_MESSAGE = "No Project id — run inside a bound repository (`.nustack/workspace.json`), pass `--project <id>`, add an `nustackWorkspace.projectId` to nustack.service.json, or set NUSTACK_WORKSPACE_ID in the managed .env block.";
8
+ export function projectJsonPath(cwd) {
9
9
  return path.join(cwd, ".nustack", "workspace.json");
10
10
  }
11
11
  export function legacyAppJsonPath(cwd) {
12
12
  return path.join(cwd, ".nustack", "app.json");
13
13
  }
14
14
  export async function readBindingMarker(cwd) {
15
- const v2Path = workspaceJsonPath(cwd);
15
+ const v2Path = projectJsonPath(cwd);
16
16
  if (existsSync(v2Path)) {
17
17
  try {
18
18
  const marker = JSON.parse(await readFile(v2Path, "utf8"));
19
19
  if (marker.schemaVersion === 2) {
20
- const workspaceId = trimmed(marker.workspaceId);
21
- if (workspaceId) {
22
- return { workspaceId, organizationId: trimmed(marker.organizationId), version: 2 };
20
+ const projectId = trimmed(marker.workspaceId);
21
+ if (projectId) {
22
+ return { projectId, organizationId: trimmed(marker.organizationId), version: 2 };
23
23
  }
24
24
  }
25
25
  }
@@ -33,10 +33,10 @@ export async function readBindingMarker(cwd) {
33
33
  const marker = JSON.parse(await readFile(v1Path, "utf8"));
34
34
  if (marker.schemaVersion !== undefined && marker.schemaVersion !== 1)
35
35
  return null;
36
- const workspaceId = trimmed(marker.appId);
37
- if (!workspaceId)
36
+ const projectId = trimmed(marker.appId);
37
+ if (!projectId)
38
38
  return null;
39
- return { workspaceId, organizationId: trimmed(marker.workspaceId), version: 1 };
39
+ return { projectId, organizationId: trimmed(marker.workspaceId), version: 1 };
40
40
  }
41
41
  catch {
42
42
  return null;
@@ -45,10 +45,10 @@ export async function readBindingMarker(cwd) {
45
45
  function trimmed(value) {
46
46
  return typeof value === "string" && value.trim() !== "" ? value.trim() : null;
47
47
  }
48
- export async function readWorkspaceJsonWorkspaceId(cwd) {
49
- return (await readBindingMarker(cwd))?.workspaceId ?? null;
48
+ export async function readProjectJsonProjectId(cwd) {
49
+ return (await readBindingMarker(cwd))?.projectId ?? null;
50
50
  }
51
- async function readManifestWorkspaceId(cwd) {
51
+ async function readManifestProjectId(cwd) {
52
52
  const filePath = manifestPath(cwd);
53
53
  if (!existsSync(filePath))
54
54
  return null;
@@ -60,24 +60,24 @@ async function readManifestWorkspaceId(cwd) {
60
60
  return null;
61
61
  }
62
62
  }
63
- async function readEnvWorkspaceId(cwd) {
63
+ async function readEnvProjectId(cwd) {
64
64
  const managed = await readManagedValues(path.join(cwd, ".env"));
65
- const workspaceId = managed.NUSTACK_WORKSPACE_ID?.trim();
66
- return workspaceId ? workspaceId : null;
65
+ const projectId = managed.NUSTACK_WORKSPACE_ID?.trim();
66
+ return projectId ? projectId : null;
67
67
  }
68
- export async function resolveWorkspaceId(cwd, options) {
69
- const flag = options.workspace?.trim();
70
- const workspaceJsonId = await readWorkspaceJsonWorkspaceId(cwd);
71
- const candidate = workspaceJsonId
72
- ? { value: workspaceJsonId, source: "workspace_json" }
68
+ export async function resolveProjectId(cwd, options) {
69
+ const flag = options.project?.trim();
70
+ const projectJsonId = await readProjectJsonProjectId(cwd);
71
+ const candidate = projectJsonId
72
+ ? { value: projectJsonId, source: "project_json" }
73
73
  : flag
74
74
  ? { value: flag, source: "flag" }
75
- : (await readManifestWorkspaceId(cwd).then((id) => (id ? { value: id, source: "manifest" } : null))) ??
76
- (await readEnvWorkspaceId(cwd).then((id) => (id ? { value: id, source: "env" } : null)));
75
+ : (await readManifestProjectId(cwd).then((id) => (id ? { value: id, source: "manifest" } : null))) ??
76
+ (await readEnvProjectId(cwd).then((id) => (id ? { value: id, source: "env" } : null)));
77
77
  if (!candidate)
78
- return { ok: false, message: NO_WORKSPACE_MESSAGE };
78
+ return { ok: false, message: NO_PROJECT_MESSAGE };
79
79
  try {
80
- return { ok: true, workspaceId: assertSafeOpaqueId("The Workspace id", candidate.value), source: candidate.source };
80
+ return { ok: true, projectId: assertSafeOpaqueId("The Project id", candidate.value), source: candidate.source };
81
81
  }
82
82
  catch (error) {
83
83
  return { ok: false, message: error instanceof Error ? error.message : String(error) };
@@ -41,7 +41,7 @@ export function makeReviewClient(baseUrl, accessToken) {
41
41
  method: "POST",
42
42
  body: JSON.stringify({
43
43
  kind: "blocking_approval",
44
- workspaceId: input.workspaceId,
44
+ projectId: input.projectId,
45
45
  sessionId: input.sessionId,
46
46
  subject: input.subject,
47
47
  ...(input.answerableByUserId ? { answerableByUserId: input.answerableByUserId } : {}),
@@ -44,7 +44,7 @@ function toTelemetryBinding(binding, deviceRef, userId) {
44
44
  bindingId: binding.id,
45
45
  repoRoot: binding.repositoryRoot,
46
46
  organizationId: binding.organizationId,
47
- workspaceId: binding.workspaceId,
47
+ projectId: binding.projectId,
48
48
  deviceRef,
49
49
  userId,
50
50
  };
@@ -122,7 +122,7 @@ export async function ensureBindingTelemetry(opts) {
122
122
  }
123
123
  let brokerFailure = null;
124
124
  const credential = await fetchBrokeredCredential(baseUrl, auth.accessToken, {
125
- workspaceId: binding.workspaceId,
125
+ projectId: binding.projectId,
126
126
  service: TELEMETRY_SERVICE,
127
127
  }).catch((error) => {
128
128
  brokerFailure = error instanceof Error ? error.message : String(error);