@treeseed/sdk 0.6.16 → 0.6.18

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 (47) hide show
  1. package/dist/db/d1.d.ts +3493 -0
  2. package/dist/db/d1.js +8 -0
  3. package/dist/db/index.d.ts +2 -0
  4. package/dist/db/index.js +2 -0
  5. package/dist/db/node-sqlite.d.ts +3544 -0
  6. package/dist/db/node-sqlite.js +119 -0
  7. package/dist/db/schema.d.ts +6272 -0
  8. package/dist/db/schema.js +231 -0
  9. package/dist/index.d.ts +1 -0
  10. package/dist/index.js +1 -0
  11. package/dist/operations/providers/default.js +1 -0
  12. package/dist/operations/services/commit-message-provider.d.ts +33 -1
  13. package/dist/operations/services/commit-message-provider.js +228 -51
  14. package/dist/operations/services/config-runtime.js +0 -1
  15. package/dist/operations/services/deploy.d.ts +19 -5
  16. package/dist/operations/services/deploy.js +75 -36
  17. package/dist/operations/services/github-actions-verification.d.ts +123 -0
  18. package/dist/operations/services/github-actions-verification.js +440 -0
  19. package/dist/operations/services/mailpit-runtime.d.ts +5 -0
  20. package/dist/operations/services/mailpit-runtime.js +2 -2
  21. package/dist/operations/services/repository-save-orchestrator.js +64 -8
  22. package/dist/operations/services/runtime-tools.d.ts +6 -0
  23. package/dist/operations/services/runtime-tools.js +11 -0
  24. package/dist/operations-registry.js +1 -0
  25. package/dist/platform/contracts.d.ts +6 -0
  26. package/dist/platform/deploy-config.js +17 -0
  27. package/dist/reconcile/builtin-adapters.js +2 -16
  28. package/dist/reconcile/contracts.d.ts +1 -1
  29. package/dist/reconcile/desired-state.d.ts +6 -0
  30. package/dist/reconcile/desired-state.js +1 -13
  31. package/dist/reconcile/engine.d.ts +12 -0
  32. package/dist/reconcile/state.js +2 -1
  33. package/dist/reconcile/units.js +0 -1
  34. package/dist/scripts/tenant-d1-migrate-local.js +5 -2
  35. package/dist/scripts/tenant-destroy.js +3 -1
  36. package/dist/sdk.js +2 -6
  37. package/dist/types/cloudflare.d.ts +0 -1
  38. package/dist/workflow/operations.d.ts +2 -1
  39. package/dist/workflow/operations.js +115 -35
  40. package/dist/workflow-support.d.ts +1 -0
  41. package/dist/workflow-support.js +6 -0
  42. package/dist/workflow.d.ts +24 -2
  43. package/dist/workflow.js +6 -0
  44. package/package.json +19 -5
  45. package/templates/github/deploy.workflow.yml +4 -0
  46. package/dist/wrangler-d1.d.ts +0 -25
  47. package/dist/wrangler-d1.js +0 -89
@@ -57,6 +57,9 @@ const webSurfaceCacheFieldAliases = {
57
57
  contentPages: { key: "contentPages", aliases: ["content_pages"] },
58
58
  r2PublishedObjects: { key: "r2PublishedObjects", aliases: ["r2_published_objects"] }
59
59
  };
60
+ const localRuntimeFieldAliases = {
61
+ runtime: { key: "runtime", aliases: ["runtime", "runtime_mode", "runtimeMode"] }
62
+ };
60
63
  const webCachePolicyFieldAliases = {
61
64
  browserTtlSeconds: { key: "browserTtlSeconds", aliases: ["browser_ttl_seconds"] },
62
65
  edgeTtlSeconds: { key: "edgeTtlSeconds", aliases: ["edge_ttl_seconds"] },
@@ -326,6 +329,18 @@ function parseServiceEnvironmentConfig(value, label) {
326
329
  railwayEnvironment: optionalString(record.railwayEnvironment)
327
330
  };
328
331
  }
332
+ function parseLocalRuntimeConfig(value, label) {
333
+ const record = normalizeAliasedRecord(
334
+ localRuntimeFieldAliases,
335
+ optionalRecord(value, label) ?? {}
336
+ );
337
+ if (!value || Object.keys(record).length === 0) {
338
+ return void 0;
339
+ }
340
+ return {
341
+ runtime: optionalEnum(record.runtime, `${label}.runtime`, ["auto", "provider", "local"])
342
+ };
343
+ }
329
344
  function parseManagedServiceConfig(value, label) {
330
345
  const record = optionalRecord(value, label);
331
346
  if (!record) {
@@ -359,6 +374,7 @@ function parseManagedServiceConfig(value, label) {
359
374
  runtimeMode: optionalString(railway.runtimeMode),
360
375
  schedule: Array.isArray(railway.schedule) ? railway.schedule.map((entry) => optionalString(entry)).filter(Boolean) : optionalString(railway.schedule)
361
376
  },
377
+ local: parseLocalRuntimeConfig(record.local, `${label}.local`),
362
378
  environments: {
363
379
  local: parseServiceEnvironmentConfig(environments.local, `${label}.environments.local`),
364
380
  staging: parseServiceEnvironmentConfig(environments.staging, `${label}.environments.staging`),
@@ -394,6 +410,7 @@ function parsePlatformSurfaceConfig(value, label) {
394
410
  rootDir: optionalString(record.rootDir),
395
411
  publicBaseUrl: optionalString(record.publicBaseUrl),
396
412
  localBaseUrl: optionalString(record.localBaseUrl),
413
+ local: parseLocalRuntimeConfig(record.local, `${label}.local`),
397
414
  environments: (() => {
398
415
  const environments = optionalRecord(record.environments, `${label}.environments`);
399
416
  if (!environments) {
@@ -835,7 +835,6 @@ function reconcileCloudflareTarget(input, { dryRun = false } = {}) {
835
835
  current.url = `https://${current.projectName}.pages.dev`;
836
836
  };
837
837
  runStep("kv-form-guard", () => ensureKv("FORM_GUARD_KV"));
838
- runStep("kv-session", () => ensureKv("SESSION"));
839
838
  runStep("d1", ensureD1);
840
839
  runStep("queue", ensureQueue);
841
840
  runStep("r2", ensureR2Bucket);
@@ -932,16 +931,6 @@ function observeCloudflareUnit(input) {
932
931
  warnings: []
933
932
  };
934
933
  }
935
- case "kv-session": {
936
- const liveNamespace = kvNamespaces.find((entry) => entry?.title === state.kvNamespaces?.SESSION?.name);
937
- return {
938
- exists: Boolean(liveNamespace || hasLiveResourceId(state.kvNamespaces?.SESSION?.id)),
939
- status: liveNamespace ? "ready" : "pending",
940
- live: { ...state.kvNamespaces?.SESSION ?? {} },
941
- locators: { id: liveNamespace?.id ?? state.kvNamespaces?.SESSION?.id ?? null },
942
- warnings: []
943
- };
944
- }
945
934
  case "pages-project": {
946
935
  const liveProject = pagesProjects.find((entry) => entry?.name === state.pages?.projectName);
947
936
  return {
@@ -1029,9 +1018,8 @@ function verifyCloudflareUnitOnce(input, postconditions) {
1029
1018
  })
1030
1019
  ]);
1031
1020
  }
1032
- case "kv-form-guard":
1033
- case "kv-session": {
1034
- const binding = input.unit.unitType === "kv-form-guard" ? "FORM_GUARD_KV" : "SESSION";
1021
+ case "kv-form-guard": {
1022
+ const binding = "FORM_GUARD_KV";
1035
1023
  const namespace = state.kvNamespaces?.[binding];
1036
1024
  const live = getCloudflareKvById(env, namespace?.id) ?? kvNamespaces.find((entry) => entry?.title === namespace?.name);
1037
1025
  return summarizeVerification(input.unit.unitId, [
@@ -1209,7 +1197,6 @@ function buildCloudflareAdapter(unitType) {
1209
1197
  { key: "database.binding", description: "D1 binding matches desired config" }
1210
1198
  ];
1211
1199
  case "kv-form-guard":
1212
- case "kv-session":
1213
1200
  return [
1214
1201
  { key: "kv.exists", description: "KV namespace exists by title and id" },
1215
1202
  { key: "kv.binding", description: "KV binding matches desired config" }
@@ -2109,7 +2096,6 @@ function createCloudflareReconcileAdapters() {
2109
2096
  buildCloudflareAdapter("database"),
2110
2097
  buildCloudflareAdapter("content-store"),
2111
2098
  buildCloudflareAdapter("kv-form-guard"),
2112
- buildCloudflareAdapter("kv-session"),
2113
2099
  buildCloudflareAdapter("pages-project"),
2114
2100
  buildCloudflareAdapter("edge-worker"),
2115
2101
  buildCustomDomainAdapter("custom-domain:web", "cloudflare"),
@@ -3,7 +3,7 @@ export type TreeseedReconcileProviderId = string;
3
3
  export type TreeseedReconcileActionKind = 'noop' | 'create' | 'update' | 'reuse' | 'drift_correct' | 'destroy';
4
4
  export type TreeseedReconcileStatusKind = 'pending' | 'ready' | 'drifted' | 'error';
5
5
  export type TreeseedReconcileVerificationSource = 'cli' | 'api' | 'sdk' | 'derived';
6
- export type TreeseedReconcileUnitType = 'web-ui' | 'api-runtime' | 'manager-runtime' | 'worker-runtime' | 'workday-start-runtime' | 'workday-report-runtime' | 'edge-worker' | 'content-store' | 'queue' | 'database' | 'kv-form-guard' | 'kv-session' | 'pages-project' | 'custom-domain:web' | 'custom-domain:api' | 'dns-record' | 'railway-service:api' | 'railway-service:manager' | 'railway-service:worker' | 'railway-service:workday-start' | 'railway-service:workday-report';
6
+ export type TreeseedReconcileUnitType = 'web-ui' | 'api-runtime' | 'manager-runtime' | 'worker-runtime' | 'workday-start-runtime' | 'workday-report-runtime' | 'edge-worker' | 'content-store' | 'queue' | 'database' | 'kv-form-guard' | 'pages-project' | 'custom-domain:web' | 'custom-domain:api' | 'dns-record' | 'railway-service:api' | 'railway-service:manager' | 'railway-service:worker' | 'railway-service:workday-start' | 'railway-service:workday-report';
7
7
  export type TreeseedReconcileTarget = {
8
8
  kind: 'persistent';
9
9
  scope: 'local' | 'staging' | 'prod';
@@ -84,6 +84,9 @@ export declare function deriveTreeseedDesiredUnits({ tenantRoot, target, }: {
84
84
  rootDir: string | undefined;
85
85
  publicBaseUrl: string | undefined;
86
86
  localBaseUrl: string | undefined;
87
+ local: {
88
+ runtime: string | undefined;
89
+ } | undefined;
87
90
  environments: {
88
91
  local: {
89
92
  baseUrl: string | undefined;
@@ -135,6 +138,9 @@ export declare function deriveTreeseedDesiredUnits({ tenantRoot, target, }: {
135
138
  provider: string | undefined;
136
139
  rootDir: string | undefined;
137
140
  publicBaseUrl: string | undefined;
141
+ local: {
142
+ runtime: string | undefined;
143
+ } | undefined;
138
144
  cloudflare: {
139
145
  workerName: string | undefined;
140
146
  };
@@ -78,18 +78,6 @@ function deriveTreeseedDesiredUnits({
78
78
  secrets: {},
79
79
  metadata: { bootstrapSystem: "web" }
80
80
  });
81
- const sessionKvId = add({
82
- unitId: createTreeseedReconcileUnitId("kv-session", legacyState.kvNamespaces.SESSION.name),
83
- unitType: "kv-session",
84
- provider: "cloudflare",
85
- identity,
86
- target,
87
- logicalName: legacyState.kvNamespaces.SESSION.name,
88
- dependencies: [],
89
- spec: { binding: "SESSION", name: legacyState.kvNamespaces.SESSION.name },
90
- secrets: {},
91
- metadata: { bootstrapSystem: "web" }
92
- });
93
81
  const contentStoreId = add({
94
82
  unitId: createTreeseedReconcileUnitId("content-store", legacyState.content.bucketName ?? deployConfig.slug),
95
83
  unitType: "content-store",
@@ -132,7 +120,7 @@ function deriveTreeseedDesiredUnits({
132
120
  identity,
133
121
  target,
134
122
  logicalName: legacyState.workerName,
135
- dependencies: [queueId, databaseId, formGuardKvId, sessionKvId, contentStoreId, pagesProjectId],
123
+ dependencies: [queueId, databaseId, formGuardKvId, contentStoreId, pagesProjectId],
136
124
  spec: {
137
125
  workerName: legacyState.workerName
138
126
  },
@@ -91,6 +91,9 @@ export declare function observeTreeseedUnits({ tenantRoot, target, env, systems,
91
91
  rootDir: string | undefined;
92
92
  publicBaseUrl: string | undefined;
93
93
  localBaseUrl: string | undefined;
94
+ local: {
95
+ runtime: string | undefined;
96
+ } | undefined;
94
97
  environments: {
95
98
  local: {
96
99
  baseUrl: string | undefined;
@@ -142,6 +145,9 @@ export declare function observeTreeseedUnits({ tenantRoot, target, env, systems,
142
145
  provider: string | undefined;
143
146
  rootDir: string | undefined;
144
147
  publicBaseUrl: string | undefined;
148
+ local: {
149
+ runtime: string | undefined;
150
+ } | undefined;
145
151
  cloudflare: {
146
152
  workerName: string | undefined;
147
153
  };
@@ -274,6 +280,9 @@ export declare function planTreeseedReconciliation({ tenantRoot, target, env, sy
274
280
  rootDir: string | undefined;
275
281
  publicBaseUrl: string | undefined;
276
282
  localBaseUrl: string | undefined;
283
+ local: {
284
+ runtime: string | undefined;
285
+ } | undefined;
277
286
  environments: {
278
287
  local: {
279
288
  baseUrl: string | undefined;
@@ -325,6 +334,9 @@ export declare function planTreeseedReconciliation({ tenantRoot, target, env, sy
325
334
  provider: string | undefined;
326
335
  rootDir: string | undefined;
327
336
  publicBaseUrl: string | undefined;
337
+ local: {
338
+ runtime: string | undefined;
339
+ } | undefined;
328
340
  cloudflare: {
329
341
  workerName: string | undefined;
330
342
  };
@@ -118,9 +118,10 @@ function migrateLegacyDeployStateUnits(legacyState, target) {
118
118
  };
119
119
  }
120
120
  for (const [binding, namespace] of Object.entries(legacyState.kvNamespaces ?? {})) {
121
+ if (binding !== "FORM_GUARD_KV") continue;
121
122
  const record = namespace;
122
123
  if (!record?.name) continue;
123
- const unitType = binding === "FORM_GUARD_KV" ? "kv-form-guard" : "kv-session";
124
+ const unitType = "kv-form-guard";
124
125
  units[`${unitType}:${record.name}`] = {
125
126
  unitId: `${unitType}:${record.name}`,
126
127
  unitType,
@@ -10,7 +10,6 @@ const TRESEED_RECONCILE_UNIT_TYPES = [
10
10
  "queue",
11
11
  "database",
12
12
  "kv-form-guard",
13
- "kv-session",
14
13
  "pages-project",
15
14
  "custom-domain:web",
16
15
  "custom-domain:api",
@@ -1,9 +1,12 @@
1
1
  import { resolve } from 'node:path';
2
2
  import { runLocalD1Migrations } from '../operations/services/d1-migration.js';
3
- import { ensureGeneratedWranglerConfig } from '../operations/services/deploy.js';
3
+ import { createPersistentDeployTarget, ensureGeneratedWranglerConfig } from '../operations/services/deploy.js';
4
4
  const tenantRoot = process.cwd();
5
5
  const migrationsRoot = resolve(tenantRoot, 'migrations');
6
- const { wranglerPath: wranglerConfig } = ensureGeneratedWranglerConfig(tenantRoot);
6
+ const { wranglerPath: wranglerConfig } = ensureGeneratedWranglerConfig(tenantRoot, {
7
+ target: createPersistentDeployTarget('local'),
8
+ env: process.env,
9
+ });
7
10
  runLocalD1Migrations({
8
11
  cwd: tenantRoot,
9
12
  wranglerConfig,
@@ -61,7 +61,9 @@ function printDangerMessage(deployConfig, state, expectedConfirmation) {
61
61
  console.error(` Worker: ${state.workerName ?? deriveCloudflareWorkerName(deployConfig)}`);
62
62
  console.error(` D1: ${state.d1Databases.SITE_DATA_DB.databaseName}`);
63
63
  console.error(` KV FORM_GUARD_KV: ${state.kvNamespaces.FORM_GUARD_KV.name}`);
64
- console.error(` KV SESSION: ${state.kvNamespaces.SESSION.name}`);
64
+ if (state.kvNamespaces.SESSION?.name) {
65
+ console.error(` KV SESSION (deprecated): ${state.kvNamespaces.SESSION.name}`);
66
+ }
65
67
  console.error(' This action is irreversible.');
66
68
  console.error(` To continue, type exactly: ${expectedConfirmation}`);
67
69
  }
package/dist/sdk.js CHANGED
@@ -9,7 +9,7 @@ import { findDispatchCapability } from "./dispatch.js";
9
9
  import { RemoteTreeseedClient, RemoteTreeseedDispatchClient } from "./remote.js";
10
10
  import { executeSdkOperation } from "./sdk-dispatch.js";
11
11
  import { TreeseedOperationsSdk } from "./operations/runtime.js";
12
- import { WranglerD1Database } from "./wrangler-d1.js";
12
+ import { NodeSqliteD1Database } from "./db/node-sqlite.js";
13
13
  function normalizeAgentSpec(entry) {
14
14
  if (!entry) {
15
15
  return null;
@@ -59,11 +59,7 @@ class AgentSdk {
59
59
  }
60
60
  static createLocal(options) {
61
61
  const repoRoot = resolveSdkRepoRoot(options.repoRoot);
62
- const d1 = new WranglerD1Database(
63
- options.databaseName ?? "karyon-docs-site-data",
64
- repoRoot,
65
- options.persistTo
66
- );
62
+ const d1 = new NodeSqliteD1Database(options.persistTo ?? options.databaseName ?? ".treeseed/generated/environments/local/site-data.sqlite");
67
63
  return new AgentSdk({
68
64
  repoRoot,
69
65
  database: new CloudflareD1AgentDatabase(d1),
@@ -48,7 +48,6 @@ export interface CloudflareRuntime {
48
48
  env: {
49
49
  FORM_GUARD_KV: KvNamespaceLike;
50
50
  SITE_DATA_DB: D1DatabaseLike;
51
- SESSION: KvNamespaceLike;
52
51
  ASSETS?: CloudflareRuntimeAssets;
53
52
  [key: string]: unknown;
54
53
  };
@@ -1,7 +1,7 @@
1
1
  import { resolveTreeseedWorkflowState, type TreeseedWorkflowStatusOptions } from '../workflow-state.ts';
2
2
  import { type TreeseedWorkflowRunCommand, type TreeseedWorkflowRunJournal } from './runs.ts';
3
3
  import { type TreeseedWorkflowMode } from './session.ts';
4
- import type { TreeseedCloseInput, TreeseedConfigInput, TreeseedDestroyInput, TreeseedExportInput, TreeseedReleaseInput, TreeseedRecoverInput, TreeseedResumeInput, TreeseedSaveInput, TreeseedStageInput, TreeseedSwitchInput, TreeseedTaskBranchMetadata, TreeseedWorkflowContext, TreeseedWorkflowDevInput, TreeseedWorkflowOperationId, TreeseedWorkflowResult, TreeseedWorkflowWorktreeMode } from '../workflow.ts';
4
+ import type { TreeseedCloseInput, TreeseedCiInput, TreeseedConfigInput, TreeseedDestroyInput, TreeseedExportInput, TreeseedReleaseInput, TreeseedRecoverInput, TreeseedResumeInput, TreeseedSaveInput, TreeseedStageInput, TreeseedSwitchInput, TreeseedTaskBranchMetadata, TreeseedWorkflowContext, TreeseedWorkflowDevInput, TreeseedWorkflowOperationId, TreeseedWorkflowResult, TreeseedWorkflowWorktreeMode } from '../workflow.ts';
5
5
  type WorkflowWrite = NonNullable<TreeseedWorkflowContext['write']>;
6
6
  type WorkflowStatePayload = ReturnType<typeof resolveTreeseedWorkflowState>;
7
7
  export type TreeseedWorkflowErrorCode = 'validation_failed' | 'merge_conflict' | 'missing_runtime_auth' | 'deployment_timeout' | 'confirmation_required' | 'unsupported_transport' | 'unsupported_state' | 'workflow_locked' | 'resume_unavailable' | 'workflow_contract_missing' | 'github_workflow_failed' | 'github_auth_unavailable';
@@ -44,6 +44,7 @@ type WorkflowRepoReport = {
44
44
  workflowGates: Array<Record<string, unknown>>;
45
45
  };
46
46
  export declare function workflowStatus(helpers: WorkflowOperationHelpers, input?: TreeseedWorkflowStatusOptions): Promise<TreeseedWorkflowResult<import("../workflow-state.ts").TreeseedWorkflowState>>;
47
+ export declare function workflowCi(helpers: WorkflowOperationHelpers, input?: TreeseedCiInput): Promise<TreeseedWorkflowResult<TreeseedCiResult>>;
47
48
  export declare function workflowTasks(helpers: WorkflowOperationHelpers): Promise<TreeseedWorkflowResult<{
48
49
  tasks: TreeseedTaskBranchMetadata[];
49
50
  workstreams: Array<{
@@ -66,7 +66,13 @@ import {
66
66
  squashMergeBranchIntoStaging,
67
67
  syncBranchWithOrigin
68
68
  } from "../operations/services/git-workflow.js";
69
- import { getGitHubAutomationMode, resolveGitHubRepositorySlug, waitForGitHubWorkflowCompletion } from "../operations/services/github-automation.js";
69
+ import { getGitHubAutomationMode, resolveGitHubRepositorySlug } from "../operations/services/github-automation.js";
70
+ import {
71
+ formatGitHubActionsGateFailure,
72
+ inspectGitHubActionsVerification,
73
+ skippedGitHubActionsGate,
74
+ waitForGitHubActionsGate
75
+ } from "../operations/services/github-actions-verification.js";
70
76
  import { loadCliDeployConfig, packageScriptPath, resolveWranglerBin } from "../operations/services/runtime-tools.js";
71
77
  import { runTenantDeployPreflight, runWorkspaceReleasePreflight, runWorkspaceSavePreflight } from "../operations/services/save-deploy-preflight.js";
72
78
  import { collectCliPreflight } from "../operations/services/workspace-preflight.js";
@@ -309,32 +315,6 @@ function helpersForCwd(helpers, cwd) {
309
315
  function shouldDispatchSwitchToManagedWorktree(root, input, env) {
310
316
  return !isManagedWorkflowWorktree(root) && effectiveWorkflowWorktreeMode(input.worktreeMode, env) === "on";
311
317
  }
312
- function skippedWorkflowGate(gate, reason) {
313
- return {
314
- name: gate.name,
315
- repository: gate.repository ?? null,
316
- workflow: gate.workflow,
317
- branch: gate.branch,
318
- headSha: gate.headSha,
319
- status: "skipped",
320
- reason,
321
- conclusion: null,
322
- runId: null,
323
- url: null
324
- };
325
- }
326
- function workflowGateFailureMessage(gate, result) {
327
- const repository = String(result.repository ?? gate.repository ?? gate.name);
328
- const runId = typeof result.runId === "number" || typeof result.runId === "string" ? String(result.runId) : "";
329
- const url = typeof result.url === "string" && result.url ? `
330
- ${result.url}` : "";
331
- const failedJobs = Array.isArray(result.failedJobs) ? result.failedJobs.map((job) => stringRecord(job)?.name).filter((name) => typeof name === "string" && name.length > 0) : [];
332
- const jobLine = failedJobs.length > 0 ? `
333
- Failed jobs: ${failedJobs.join(", ")}` : "";
334
- const command = runId ? `
335
- Inspect with: gh run view ${runId} --repo ${repository} --log-failed` : "";
336
- return `${gate.name} ${gate.workflow} completed with conclusion ${String(result.conclusion ?? "unknown")} in ${repository}.${url}${jobLine}${command}`;
337
- }
338
318
  function assertHostedGitHubWorkflowAuthReady(operation, root) {
339
319
  if (getGitHubAutomationMode() === "stub") {
340
320
  return null;
@@ -370,7 +350,7 @@ function assertHostedGitHubWorkflowAuthReady(operation, root) {
370
350
  }
371
351
  async function waitForWorkflowGates(operation, gates, ciMode, options = {}) {
372
352
  if (ciMode === "off" || process.env.TREESEED_STAGE_WAIT_MODE === "skip") {
373
- return gates.map((gate) => skippedWorkflowGate(gate, ciMode === "off" ? "disabled" : "stubbed"));
353
+ return gates.map((gate) => skippedGitHubActionsGate(gate, ciMode === "off" ? "disabled" : "stubbed"));
374
354
  }
375
355
  if (gates.length === 0) {
376
356
  return [];
@@ -394,12 +374,7 @@ async function waitForWorkflowGates(operation, gates, ciMode, options = {}) {
394
374
  continue;
395
375
  }
396
376
  }
397
- const result = await waitForGitHubWorkflowCompletion(gate.repoPath, {
398
- repository: gate.repository,
399
- workflow: gate.workflow,
400
- headSha: gate.headSha,
401
- branch: gate.branch
402
- });
377
+ const result = await waitForGitHubActionsGate(gate);
403
378
  const normalized = {
404
379
  name: gate.name,
405
380
  ...result,
@@ -408,7 +383,7 @@ async function waitForWorkflowGates(operation, gates, ciMode, options = {}) {
408
383
  headSha: String(result.headSha ?? gate.headSha)
409
384
  };
410
385
  if (normalized.status === "completed" && normalized.conclusion !== "success") {
411
- workflowError(operation, "github_workflow_failed", workflowGateFailureMessage(gate, normalized), {
386
+ workflowError(operation, "github_workflow_failed", formatGitHubActionsGateFailure(gate, normalized), {
412
387
  details: { gate, workflow: normalized }
413
388
  });
414
389
  }
@@ -711,6 +686,86 @@ function createStatusResult(cwd, options = {}) {
711
686
  includeFinalState: false
712
687
  });
713
688
  }
689
+ function normalizeCiScope(value) {
690
+ return value === "root" || value === "packages" ? value : "workspace";
691
+ }
692
+ function normalizeCiLogLines(value) {
693
+ const parsed = typeof value === "number" ? value : typeof value === "string" ? Number.parseInt(value, 10) : 120;
694
+ return Number.isFinite(parsed) ? Math.max(20, Math.min(1e3, Math.floor(parsed))) : 120;
695
+ }
696
+ function normalizeCiWorkflows(input) {
697
+ const raw = input.workflows ?? input.workflow ?? [];
698
+ return (Array.isArray(raw) ? raw : [raw]).map((workflow) => String(workflow ?? "").trim()).filter(Boolean);
699
+ }
700
+ function defaultCiWorkflows(kind, branch) {
701
+ if (kind === "package") {
702
+ return ["verify.yml"];
703
+ }
704
+ const workflows = ["verify.yml"];
705
+ if (branch === STAGING_BRANCH || branch === PRODUCTION_BRANCH) {
706
+ workflows.push("deploy.yml");
707
+ }
708
+ return workflows;
709
+ }
710
+ function githubRepositoryForRepo(repoDir) {
711
+ try {
712
+ return resolveGitHubRepositorySlug(repoDir);
713
+ } catch {
714
+ return null;
715
+ }
716
+ }
717
+ function ciTargetForRepo(repo, kind, input, workflowOverrides) {
718
+ const branch = typeof input.branch === "string" && input.branch.trim() ? input.branch.trim() : repo.branchName;
719
+ const workflows = workflowOverrides.length > 0 ? workflowOverrides : defaultCiWorkflows(kind, branch);
720
+ return {
721
+ name: repo.name,
722
+ repoPath: repo.path,
723
+ repository: githubRepositoryForRepo(repo.path),
724
+ branch,
725
+ headSha: branch ? headCommit(repo.path) : null,
726
+ workflows,
727
+ kind
728
+ };
729
+ }
730
+ function ciTargetsForSession(session, input) {
731
+ const scope = normalizeCiScope(input.scope);
732
+ const workflows = normalizeCiWorkflows(input);
733
+ const targets = [];
734
+ if (scope === "workspace" || scope === "root") {
735
+ targets.push(ciTargetForRepo(session.rootRepo, "root", input, workflows));
736
+ }
737
+ if (scope === "workspace" || scope === "packages") {
738
+ targets.push(...session.packageRepos.map((repo) => ciTargetForRepo(repo, "package", input, workflows)));
739
+ }
740
+ return { scope, targets };
741
+ }
742
+ async function createCiResult(cwd, input) {
743
+ const session = resolveTreeseedWorkflowSession(cwd);
744
+ const { scope, targets } = ciTargetsForSession(session, input);
745
+ const strict = input.strict === true;
746
+ const includeLogs = input.logs === true || input.includeLogs === true;
747
+ const report = await inspectGitHubActionsVerification(targets, {
748
+ includeLogs,
749
+ logLines: normalizeCiLogLines(input.logLines)
750
+ });
751
+ const hasFailures = report.failures.length > 0;
752
+ const hasPending = report.summary.pending > 0;
753
+ const exitCode = hasFailures || strict && hasPending ? 1 : 0;
754
+ const payload = {
755
+ ...report,
756
+ mode: session.mode,
757
+ branch: typeof input.branch === "string" && input.branch.trim() ? input.branch.trim() : session.branchName,
758
+ scope,
759
+ strict,
760
+ hasFailures,
761
+ hasPending,
762
+ exitCode
763
+ };
764
+ return buildWorkflowResult("ci", cwd, payload, {
765
+ includeFinalState: false,
766
+ summary: hasFailures ? "Treeseed CI found remote GitHub Actions failures." : strict && hasPending ? "Treeseed CI found pending remote GitHub Actions runs." : "Treeseed CI status is clear."
767
+ });
768
+ }
714
769
  function createTasksResult(cwd) {
715
770
  const tenantRoot = cwd;
716
771
  const repoDir = gitWorkflowRoot(tenantRoot);
@@ -1642,6 +1697,30 @@ async function workflowStatus(helpers, input = {}) {
1642
1697
  });
1643
1698
  });
1644
1699
  }
1700
+ async function workflowCi(helpers, input = {}) {
1701
+ return withContextEnv(helpers.context.env, async () => {
1702
+ try {
1703
+ const resolved = resolveTreeseedWorkflowPaths(helpers.cwd());
1704
+ const branch = currentBranch(repoRoot(resolved.cwd)) || null;
1705
+ const scope = branch === PRODUCTION_BRANCH ? "prod" : branch === STAGING_BRANCH ? "staging" : "local";
1706
+ const env = resolved.tenantRoot ? resolveTreeseedLaunchEnvironment({
1707
+ tenantRoot: resolved.cwd,
1708
+ scope,
1709
+ baseEnv: { ...process.env, ...helpers.context.env ?? {} }
1710
+ }) : { ...process.env, ...helpers.context.env ?? {} };
1711
+ return await withContextEnv(env, () => createCiResult(helpers.cwd(), input));
1712
+ } catch (error) {
1713
+ if (error instanceof TreeseedWorkflowError) {
1714
+ throw error;
1715
+ }
1716
+ const message = error instanceof Error ? error.message : String(error);
1717
+ if (/GH_TOKEN|GITHUB_TOKEN|GitHub authentication|authenticated|Bad credentials|Requires authentication/iu.test(message)) {
1718
+ workflowError("ci", "github_auth_unavailable", message, { exitCode: 2 });
1719
+ }
1720
+ workflowError("ci", "validation_failed", message, { exitCode: 2 });
1721
+ }
1722
+ });
1723
+ }
1645
1724
  async function workflowTasks(helpers) {
1646
1725
  return withContextEnv(helpers.context.env, () => createTasksResult(helpers.cwd()));
1647
1726
  }
@@ -3826,6 +3905,7 @@ async function workflowDestroy(helpers, input) {
3826
3905
  }
3827
3906
  export {
3828
3907
  TreeseedWorkflowError,
3908
+ workflowCi,
3829
3909
  workflowClose,
3830
3910
  workflowConfig,
3831
3911
  workflowDestroy,
@@ -2,6 +2,7 @@ export { applyTreeseedConfigValues, applyTreeseedEnvironmentToProcess, applyTree
2
2
  export { exportTreeseedCodebase } from './operations/services/export-runtime.ts';
3
3
  export { assertDeploymentInitialized, cleanupDestroyedState, createBranchPreviewDeployTarget, createPersistentDeployTarget, deployTargetLabel, destroyCloudflareResources, ensureGeneratedWranglerConfig, finalizeDeploymentState, loadDeployState, printDeploySummary, printDestroySummary, provisionCloudflareResources, runRemoteD1Migrations, syncCloudflareSecrets, validateDeployPrerequisites, validateDestroyPrerequisites, } from './operations/services/deploy.ts';
4
4
  export { assertCleanWorktree, assertFeatureBranch, branchExists, checkoutBranch, createDeprecatedTaskTag, createFeatureBranchFromStaging, currentManagedBranch, deleteLocalBranch, deleteRemoteBranch, ensureLocalBranchTracking, gitWorkflowRoot, listTaskBranches, mergeCurrentBranchIntoStaging, mergeStagingIntoMain, prepareReleaseBranches, PRODUCTION_BRANCH, pushBranch, remoteBranchExists, STAGING_BRANCH, syncBranchWithOrigin, waitForStagingAutomation, } from './operations/services/git-workflow.ts';
5
+ export { dockerIsAvailable, stopKnownMailpitContainers, type TreeseedMailpitContainer, } from './operations/services/mailpit-runtime.ts';
5
6
  export { loadCliDeployConfig, packageScriptPath, resolveWranglerBin, } from './operations/services/runtime-tools.ts';
6
7
  export { configuredRailwayServices, deployRailwayService, validateRailwayDeployPrerequisites, } from './operations/services/railway-deploy.ts';
7
8
  export { ensureRailwayEnvironment, ensureRailwayProject, ensureRailwayService, getRailwayAuthProfile, listRailwayEnvironments, listRailwayProjects, listRailwayServices, listRailwayVariables, railwayGraphqlRequest, resolveRailwayApiToken, resolveRailwayApiUrl, resolveRailwayWorkspace, resolveRailwayWorkspaceContext, upsertRailwayVariables, } from './operations/services/railway-api.ts';
@@ -80,6 +80,10 @@ import {
80
80
  syncBranchWithOrigin,
81
81
  waitForStagingAutomation
82
82
  } from "./operations/services/git-workflow.js";
83
+ import {
84
+ dockerIsAvailable,
85
+ stopKnownMailpitContainers
86
+ } from "./operations/services/mailpit-runtime.js";
83
87
  import {
84
88
  loadCliDeployConfig,
85
89
  packageScriptPath,
@@ -206,6 +210,7 @@ export {
206
210
  destroyCloudflareResources,
207
211
  destroyTreeseedTargetUnits,
208
212
  discoverWorkspaceLinks,
213
+ dockerIsAvailable,
209
214
  ensureGeneratedWranglerConfig,
210
215
  ensureLocalBranchTracking,
211
216
  ensureLocalWorkspaceLinks,
@@ -283,6 +288,7 @@ export {
283
288
  runWorkspaceReleasePreflight,
284
289
  runWorkspaceSavePreflight,
285
290
  setTreeseedRemoteSession,
291
+ stopKnownMailpitContainers,
286
292
  syncBranchWithOrigin,
287
293
  syncCloudflareSecrets,
288
294
  unlinkLocalWorkspaceLinks,
@@ -1,7 +1,8 @@
1
1
  import { resolveTreeseedWorkflowState, type TreeseedWorkflowStatusOptions } from './workflow-state.ts';
2
2
  import { listTaskBranches } from './operations/services/git-workflow.ts';
3
+ import type { GitHubActionsVerificationReport } from './operations/services/github-actions-verification.ts';
3
4
  import { TreeseedWorkflowError, type TreeseedWorkflowErrorCode } from './workflow/operations.ts';
4
- export type TreeseedWorkflowOperationId = 'status' | 'config' | 'tasks' | 'switch' | 'dev' | 'save' | 'close' | 'stage' | 'release' | 'resume' | 'recover' | 'destroy' | 'export';
5
+ export type TreeseedWorkflowOperationId = 'status' | 'ci' | 'config' | 'tasks' | 'switch' | 'dev' | 'save' | 'close' | 'stage' | 'release' | 'resume' | 'recover' | 'destroy' | 'export';
5
6
  export type TreeseedWorkflowNextStep = {
6
7
  operation: string;
7
8
  reason?: string;
@@ -112,6 +113,26 @@ export type TreeseedSaveInput = {
112
113
  plan?: boolean;
113
114
  dryRun?: boolean;
114
115
  };
116
+ export type TreeseedCiInput = {
117
+ failed?: boolean;
118
+ logs?: boolean;
119
+ includeLogs?: boolean;
120
+ logLines?: number | string;
121
+ scope?: 'workspace' | 'root' | 'packages';
122
+ workflow?: string | string[];
123
+ workflows?: string | string[];
124
+ branch?: string;
125
+ strict?: boolean;
126
+ };
127
+ export type TreeseedCiResult = GitHubActionsVerificationReport & {
128
+ mode: 'root-only' | 'recursive-workspace';
129
+ branch: string | null;
130
+ scope: 'workspace' | 'root' | 'packages';
131
+ strict: boolean;
132
+ hasFailures: boolean;
133
+ hasPending: boolean;
134
+ exitCode: number;
135
+ };
115
136
  export type TreeseedCloseInput = {
116
137
  message: string;
117
138
  deletePreview?: boolean;
@@ -227,11 +248,12 @@ export declare class TreeseedWorkflowSdk {
227
248
  private readonly context;
228
249
  constructor(context?: TreeseedWorkflowContext);
229
250
  private helpers;
230
- execute(operation: TreeseedWorkflowOperationId, input?: Record<string, unknown>): Promise<TreeseedWorkflowResult<import("./workflow-state.ts").TreeseedWorkflowState> | TreeseedWorkflowResult<{
251
+ execute(operation: TreeseedWorkflowOperationId, input?: Record<string, unknown>): Promise<TreeseedWorkflowResult<import("./workflow-state.ts").TreeseedWorkflowState> | TreeseedWorkflowResult<TreeseedCiResult> | TreeseedWorkflowResult<{
231
252
  tasks: TreeseedTaskBranchMetadata[];
232
253
  workstreams: TreeseedWorkflowWorkstreamSummary[];
233
254
  }> | TreeseedWorkflowResult<Record<string, unknown>>>;
234
255
  status(input?: TreeseedWorkflowStatusOptions): Promise<TreeseedWorkflowResult<ReturnType<typeof resolveTreeseedWorkflowState>>>;
256
+ ci(input?: TreeseedCiInput): Promise<TreeseedWorkflowResult<TreeseedCiResult>>;
235
257
  tasks(): Promise<TreeseedWorkflowResult<{
236
258
  tasks: TreeseedTaskBranchMetadata[];
237
259
  workstreams: TreeseedWorkflowWorkstreamSummary[];
package/dist/workflow.js CHANGED
@@ -2,6 +2,7 @@ import { resolveTreeseedWorkflowPaths } from "./workflow/policy.js";
2
2
  import {
3
3
  TreeseedWorkflowError,
4
4
  workflowClose,
5
+ workflowCi,
5
6
  workflowConfig,
6
7
  workflowDestroy,
7
8
  workflowDev,
@@ -42,6 +43,8 @@ class TreeseedWorkflowSdk {
42
43
  switch (operation) {
43
44
  case "status":
44
45
  return this.status(input);
46
+ case "ci":
47
+ return this.ci(input);
45
48
  case "tasks":
46
49
  return this.tasks();
47
50
  case "config":
@@ -73,6 +76,9 @@ class TreeseedWorkflowSdk {
73
76
  async status(input = {}) {
74
77
  return workflowStatus(this.helpers(), input);
75
78
  }
79
+ async ci(input = {}) {
80
+ return workflowCi(this.helpers(), input);
81
+ }
76
82
  async tasks() {
77
83
  return workflowTasks(this.helpers());
78
84
  }