@code-partner/codepipe-hub 0.14.1-dev.389.g16ca7a21 → 0.14.1-dev.392.g2ee041d5

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.
@@ -27,7 +27,7 @@
27
27
  localStorage.getItem('theme') ?? 'dark'
28
28
  );
29
29
  </script>
30
- <script type="module" crossorigin src="/assets/index-Bgq2ZBdp.js"></script>
30
+ <script type="module" crossorigin src="/assets/index-Cpy8aKZb.js"></script>
31
31
  <link rel="stylesheet" crossorigin href="/assets/index-CD8JrtmW.css">
32
32
  </head>
33
33
  <body>
package/hub/dist/index.js CHANGED
@@ -13,7 +13,7 @@ var PLATFORM_VERSION;
13
13
  var init_version_generated = __esm({
14
14
  "../packages/shared/dist/version.generated.js"() {
15
15
  "use strict";
16
- PLATFORM_VERSION = "0.14.1-dev.389.g16ca7a21";
16
+ PLATFORM_VERSION = "0.14.1-dev.392.g2ee041d5";
17
17
  }
18
18
  });
19
19
 
@@ -41,6 +41,15 @@ var init_hub_url = __esm({
41
41
  }
42
42
  });
43
43
 
44
+ // ../packages/shared/dist/local-hub.js
45
+ var LOCAL_HUB_OWNER_EMAIL;
46
+ var init_local_hub = __esm({
47
+ "../packages/shared/dist/local-hub.js"() {
48
+ "use strict";
49
+ LOCAL_HUB_OWNER_EMAIL = "local-owner@localhost";
50
+ }
51
+ });
52
+
44
53
  // ../packages/shared/dist/context-template.js
45
54
  var init_context_template = __esm({
46
55
  "../packages/shared/dist/context-template.js"() {
@@ -1287,20 +1296,24 @@ var init_project_settings = __esm({
1287
1296
  {
1288
1297
  id: "llm",
1289
1298
  label: "LLM",
1290
- // DEV-510 (ADR-0025): the project's LLM provider a strict enum with a
1291
- // single value for now; the control is the anchor for future providers
1292
- // (codex). Rendered above the per-role model sub-group.
1299
+ // DEV-510 (ADR-0025): the project's LLM provider. DEV-760 made the second
1300
+ // harness real, so this is a genuine choice — the enum carries every
1301
+ // provider the runtime supports. Rendered above the per-role model
1302
+ // sub-group.
1293
1303
  fields: [
1294
1304
  {
1295
1305
  id: "llm.provider",
1296
1306
  file: "local.yaml",
1297
1307
  path: ["llm", "provider"],
1298
1308
  type: "enum",
1299
- label: "Provider",
1300
- // Always a concrete provider (claude) — never cleared.
1309
+ label: "Default provider",
1310
+ // Always a concrete provider — never cleared.
1301
1311
  nullable: false,
1302
1312
  suggestions: [...LLM_PROVIDER_SUGGESTIONS],
1303
- help: "LLM backend for this project. Currently only claude is available."
1313
+ // DEV-896: the old text ("currently only claude is available") outlived
1314
+ // its truth and read as "there is nothing to choose" — the one thing
1315
+ // help must never say about a live control.
1316
+ help: "The agent harness every role runs on by default. Per-role overrides (and the fallback agent) live in the shared config's routing block."
1304
1317
  }
1305
1318
  ],
1306
1319
  groups: [
@@ -2301,6 +2314,13 @@ var init_runner = __esm({
2301
2314
  }
2302
2315
  });
2303
2316
 
2317
+ // ../packages/shared/dist/git-transport.js
2318
+ var init_git_transport = __esm({
2319
+ "../packages/shared/dist/git-transport.js"() {
2320
+ "use strict";
2321
+ }
2322
+ });
2323
+
2304
2324
  // ../packages/shared/dist/delivery-config.js
2305
2325
  function localFirstDefaults() {
2306
2326
  return {
@@ -3001,6 +3021,7 @@ var init_dist = __esm({
3001
3021
  "use strict";
3002
3022
  init_version();
3003
3023
  init_hub_url();
3024
+ init_local_hub();
3004
3025
  init_context_template();
3005
3026
  init_project_map();
3006
3027
  init_phase();
@@ -3026,6 +3047,7 @@ var init_dist = __esm({
3026
3047
  init_farm();
3027
3048
  init_runner();
3028
3049
  init_project();
3050
+ init_git_transport();
3029
3051
  init_delivery_config();
3030
3052
  init_delivery_capabilities();
3031
3053
  init_revision_ref();
@@ -10488,10 +10510,26 @@ async function loadSourceSnapshots(db, projectId) {
10488
10510
  async function dirtySourceRepos(db, projectId) {
10489
10511
  return (await loadSourceSnapshots(db, projectId)).filter((s) => s.dirty).map((s) => s.repoName);
10490
10512
  }
10513
+ async function unpinnedSourceRepos(db, projectId) {
10514
+ const axes = await loadDeliveryAxes(db, projectId, {
10515
+ localFirstDefault: deliveryProfileLocalFirst()
10516
+ });
10517
+ if (axes.source.kind !== "local_snapshot") return [];
10518
+ const row = await db.get(
10519
+ `SELECT repos_json as reposJson FROM projects WHERE id = ?`,
10520
+ projectId
10521
+ );
10522
+ if (!row) return [];
10523
+ const repos = JSON.parse(row.reposJson);
10524
+ const pinned = new Set((await loadSourceSnapshots(db, projectId)).map((s) => s.repoName));
10525
+ return repos.map((r) => r.name).filter((name) => !pinned.has(name));
10526
+ }
10491
10527
  var SHA_RE;
10492
10528
  var init_source_snapshots = __esm({
10493
10529
  "src/projects/source-snapshots.ts"() {
10494
10530
  "use strict";
10531
+ init_delivery_plan();
10532
+ init_publication();
10495
10533
  SHA_RE = /^[0-9a-f]{40}$/;
10496
10534
  }
10497
10535
  });
@@ -10839,18 +10877,32 @@ async function claimNextJob(db, sandboxId, capabilities, lane) {
10839
10877
  // produced a delivery whose diff carried the whole branch's work and could
10840
10878
  // never cherry-pick back. The conflict source (the default branch) is now
10841
10879
  // merged INTO the tip worktree by the SANDBOX (metadata.mergeBranchByRepo).
10880
+ // DEV-875: the SOURCE is re-resolved here too, for every role. A spec is
10881
+ // built when the job is queued, and a local-first project's pin may not
10882
+ // exist yet at that moment (the CLI reports it on start) — the spec then
10883
+ // froze the remote URL, which such a project may have no credential for.
10884
+ // Re-resolving at claim time hands the worker wherever the code actually
10885
+ // is now: the relay pin, or the mirror a project gained since.
10842
10886
  rewriteSpec: async (tx, row) => {
10843
- if (row.role !== "implement" && row.role !== "analyze" && row.role !== "estimate" && row.role !== "resolve-conflict") {
10844
- return null;
10845
- }
10887
+ const roleTakesChainRef = row.role === "implement" || row.role === "analyze" || row.role === "estimate" || row.role === "resolve-conflict";
10846
10888
  const txDb = dbOf(tx);
10847
- const chainRef = await loadChainTaskRef(txDb, row.taskId);
10848
- const baseByRepo = chainRef ? await clusterBranchByRepo(txDb, chainRef) : {};
10849
- const defaultRefByRepo = new Map(
10850
- (await loadProjectRepoRefs(txDb, row.projectId)).map((r) => [r.name, r.ref])
10851
- );
10889
+ const current = await loadProjectRepoRefs(txDb, row.projectId);
10890
+ const currentByRepo = new Map(current.map((r) => [r.name, r]));
10852
10891
  const spec2 = JSON.parse(row.specJson);
10892
+ if (spec2.metadata?.["externalReview"] === true) return null;
10853
10893
  let changed = false;
10894
+ for (const repo of spec2.projectRepos) {
10895
+ const fresh = currentByRepo.get(repo.name);
10896
+ if (!fresh || fresh.cloneUrl === repo.cloneUrl) continue;
10897
+ repo.cloneUrl = fresh.cloneUrl;
10898
+ repo.ref = fresh.ref;
10899
+ if (fresh.defaultBranch) repo.defaultBranch = fresh.defaultBranch;
10900
+ changed = true;
10901
+ }
10902
+ if (!roleTakesChainRef) return changed ? JSON.stringify(spec2) : null;
10903
+ const chainRef = await loadChainTaskRef(txDb, row.taskId);
10904
+ const baseByRepo = chainRef ? await clusterBranchByRepo(txDb, chainRef) : {};
10905
+ const defaultRefByRepo = new Map(current.map((r) => [r.name, r.ref]));
10854
10906
  for (const repo of spec2.projectRepos) {
10855
10907
  const next = baseByRepo[repo.name] ?? defaultRefByRepo.get(repo.name) ?? repo.ref;
10856
10908
  if (next !== repo.ref) {
@@ -18485,6 +18537,17 @@ async function developFromQueue(db, task, log, reason = "queue \u2192 develop")
18485
18537
  async function startTaskWorkflow(db, task, log) {
18486
18538
  await beginPipeline(db, task, log);
18487
18539
  }
18540
+ async function localSourceHold(db, projectId) {
18541
+ const dirty = await dirtySourceRepos(db, projectId);
18542
+ if (dirty.length > 0) {
18543
+ return `LOCAL_SOURCE_DIRTY: uncommitted changes in ${dirty.join(", ")} \u2014 commit or stash them, then retry`;
18544
+ }
18545
+ const unpinned = await unpinnedSourceRepos(db, projectId);
18546
+ if (unpinned.length > 0) {
18547
+ return `LOCAL_SOURCE_UNPINNED: no snapshot of ${unpinned.join(", ")} yet \u2014 start this project's CLI (\`codepipe up\`) so it publishes your current commit, then retry`;
18548
+ }
18549
+ return null;
18550
+ }
18488
18551
  async function beginPipeline(db, task, log) {
18489
18552
  if (task.phase !== "INTAKE") return;
18490
18553
  const deliveryPlan = await resolveProjectDeliveryPlan(db, task.projectId);
@@ -18494,11 +18557,10 @@ async function beginPipeline(db, task, log) {
18494
18557
  return;
18495
18558
  }
18496
18559
  if (deliveryPlan.plan.transport === "context_repo" && deliveryPlan.plan.target === "working_repo") {
18497
- const dirty = await dirtySourceRepos(db, task.projectId);
18498
- if (dirty.length > 0) {
18499
- const reason = `LOCAL_SOURCE_DIRTY: uncommitted changes in ${dirty.join(", ")} \u2014 commit or stash them, then retry`;
18500
- log.warn({ taskId: task.id, dirty }, "local source dirty \u2014 execution held");
18501
- await transitionPhase(db, task.id, "NEEDS_MANUAL", reason);
18560
+ const hold = await localSourceHold(db, task.projectId);
18561
+ if (hold) {
18562
+ log.warn({ taskId: task.id, hold }, "local source not usable \u2014 execution held");
18563
+ await transitionPhase(db, task.id, "NEEDS_MANUAL", hold);
18502
18564
  return;
18503
18565
  }
18504
18566
  }
@@ -19663,11 +19725,10 @@ async function triggerRework(db, task, instruction, source, log) {
19663
19725
  return;
19664
19726
  }
19665
19727
  if (deliveryPlan.plan.target === "working_repo") {
19666
- const dirty = await dirtySourceRepos(db, task.projectId);
19667
- if (dirty.length > 0) {
19668
- const reason = `LOCAL_SOURCE_DIRTY: uncommitted changes in ${dirty.join(", ")} \u2014 commit or stash them, then retry`;
19669
- log.warn({ taskId: task.id, dirty }, "local source dirty \u2014 rework held");
19670
- await transitionPhase(db, task.id, "NEEDS_MANUAL", reason);
19728
+ const hold = await localSourceHold(db, task.projectId);
19729
+ if (hold) {
19730
+ log.warn({ taskId: task.id, hold }, "local source not usable \u2014 rework held");
19731
+ await transitionPhase(db, task.id, "NEEDS_MANUAL", hold);
19671
19732
  return;
19672
19733
  }
19673
19734
  }
@@ -22178,6 +22239,7 @@ var init_ws = __esm({
22178
22239
  init_dist();
22179
22240
 
22180
22241
  // src/config.ts
22242
+ init_dist();
22181
22243
  import { randomBytes } from "node:crypto";
22182
22244
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
22183
22245
  import { resolve } from "node:path";
@@ -22305,8 +22367,8 @@ function loadLocalConfig() {
22305
22367
  baseUrl: process.env["HUB_BASE_URL"] ?? `http://127.0.0.1:${port}`,
22306
22368
  consoleBaseUrl: "",
22307
22369
  databaseUrl: `sqlite:${resolve(dataDir, "codepipe.db")}`,
22308
- adminEmail: "local-owner@localhost",
22309
- registrationNotifyEmail: "local-owner@localhost",
22370
+ adminEmail: LOCAL_HUB_OWNER_EMAIL,
22371
+ registrationNotifyEmail: LOCAL_HUB_OWNER_EMAIL,
22310
22372
  sessionCookieSecret: process.env["HUB_SESSION_COOKIE_SECRET"] ?? localSecret(dataDir, "session-secret", 32),
22311
22373
  // The break-glass ownerless registration token must never be well-known:
22312
22374
  // any process reaching the loopback port could otherwise register a
@@ -27208,10 +27270,12 @@ async function authRoutes(app, deps) {
27208
27270
  const session = await readSession(db, req);
27209
27271
  if (!session) return reply.status(401).send({ error: "no_session" });
27210
27272
  const user = await findUser(db, session.email);
27273
+ const singleOwner = profileAxes(config.profile ?? "server").authProvider === "single-owner";
27211
27274
  return {
27212
27275
  email: session.email,
27213
27276
  isSuperAdmin: isSuperUser(config, session.email),
27214
- passwordSet: user === null || user.passwordHash !== null
27277
+ passwordSet: singleOwner || user === null || user.passwordHash !== null,
27278
+ singleOwner
27215
27279
  };
27216
27280
  });
27217
27281
  app.post("/auth/logout", async (req, reply) => {
@@ -29626,7 +29690,10 @@ var createProjectBodySchema = {
29626
29690
  type: "object",
29627
29691
  required: ["host", "owner", "name", "cloneUrl", "branch"],
29628
29692
  properties: {
29629
- host: { type: "string", enum: ["github", "bitbucket", "gitlab"] },
29693
+ // DEV-875: `local` = a bare repository on the CLI's machine, reported
29694
+ // with its filesystem path as `cloneUrl`. The HUB records it like any
29695
+ // other location — it has no git access to either kind.
29696
+ host: { type: "string", enum: ["github", "bitbucket", "gitlab", "local"] },
29630
29697
  owner: { type: "string", minLength: 1 },
29631
29698
  name: { type: "string", minLength: 1 },
29632
29699
  cloneUrl: { type: "string", minLength: 1 },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@code-partner/codepipe-hub",
3
- "version": "0.14.1-dev.389.g16ca7a21",
3
+ "version": "0.14.1-dev.392.g2ee041d5",
4
4
  "description": "The CodePipe hub, packaged for one machine: the CLI installs it on demand for the local profile.",
5
5
  "type": "module",
6
6
  "main": "./hub/dist/index.js",