@scheduler-systems/gal-run 0.0.408 → 0.0.411

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 (2) hide show
  1. package/dist/index.cjs +1344 -10
  2. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -3970,7 +3970,7 @@ var cliVersion, defaultApiUrl, BUILD_CONSTANTS, constants_default;
3970
3970
  var init_constants = __esm({
3971
3971
  "src/constants.ts"() {
3972
3972
  "use strict";
3973
- cliVersion = true ? "0.0.408" : "0.0.0-dev";
3973
+ cliVersion = true ? "0.0.411" : "0.0.0-dev";
3974
3974
  defaultApiUrl = true ? "https://api.gal.run" : "http://localhost:3000";
3975
3975
  BUILD_CONSTANTS = Object.freeze([cliVersion, defaultApiUrl]);
3976
3976
  constants_default = BUILD_CONSTANTS;
@@ -4880,7 +4880,7 @@ function detectEnvironment() {
4880
4880
  return "dev";
4881
4881
  }
4882
4882
  try {
4883
- const version2 = true ? "0.0.408" : void 0;
4883
+ const version2 = true ? "0.0.411" : void 0;
4884
4884
  if (version2 && version2.includes("-local")) {
4885
4885
  return "dev";
4886
4886
  }
@@ -5249,7 +5249,7 @@ function getId() {
5249
5249
  }
5250
5250
  function getCliVersion() {
5251
5251
  try {
5252
- return true ? "0.0.408" : "0.0.0-dev";
5252
+ return true ? "0.0.411" : "0.0.0-dev";
5253
5253
  } catch {
5254
5254
  return "0.0.0-dev";
5255
5255
  }
@@ -5663,9 +5663,38 @@ var init_llm_analysis = __esm({
5663
5663
  });
5664
5664
 
5665
5665
  // ../../packages/types/dist/feature-flags.js
5666
+ var ALL_PAGE_IDS;
5666
5667
  var init_feature_flags = __esm({
5667
5668
  "../../packages/types/dist/feature-flags.js"() {
5668
5669
  "use strict";
5670
+ ALL_PAGE_IDS = [
5671
+ "dashboard",
5672
+ "discovery",
5673
+ "proposals",
5674
+ "team",
5675
+ "background-agents",
5676
+ "cli",
5677
+ "vscode",
5678
+ "docs",
5679
+ "billing",
5680
+ "settings",
5681
+ "workflow-testing",
5682
+ "project-scope-configs",
5683
+ "enforcement-overrides",
5684
+ "domain-compliance",
5685
+ "tool-compliance",
5686
+ "audit-logs",
5687
+ "enforcement-policies",
5688
+ "enforcement-compliance",
5689
+ "enforcement-audit",
5690
+ "enforcement-domains",
5691
+ "enforcement-hooks",
5692
+ "enforcement-sdlc",
5693
+ "enforcement-security",
5694
+ "enforcement-tools",
5695
+ "enforcement-system",
5696
+ "browser-profiles"
5697
+ ];
5669
5698
  }
5670
5699
  });
5671
5700
 
@@ -5677,6 +5706,49 @@ var init_billing = __esm({
5677
5706
  });
5678
5707
 
5679
5708
  // ../../packages/types/dist/telemetry.js
5709
+ function isEnhancedEvent(event) {
5710
+ return "resource" in event && "severity" in event;
5711
+ }
5712
+ function upgradeToEnhancedEvent(legacy) {
5713
+ return {
5714
+ id: legacy.id,
5715
+ timestamp: legacy.timestamp,
5716
+ severity: legacy.payload.success === false ? "ERROR" : "INFO",
5717
+ resource: {
5718
+ "service.name": "gal-cli",
5719
+ "service.version": legacy.payload.cliVersion || "unknown",
5720
+ "host.os": legacy.payload.platform || "linux",
5721
+ "host.arch": "x64",
5722
+ // Default, not available in legacy
5723
+ "process.runtime.version": legacy.payload.nodeVersion
5724
+ },
5725
+ eventType: legacy.eventType,
5726
+ attributes: {
5727
+ command: legacy.payload.command ?? null,
5728
+ success: legacy.payload.success ?? null,
5729
+ error_type: legacy.payload.errorType ?? null,
5730
+ duration_ms: legacy.payload.durationMs ?? null,
5731
+ notification_type: legacy.payload.notificationType ?? null,
5732
+ rate_limit_hit: legacy.payload.rateLimitHit ?? null,
5733
+ sync_age_hours: legacy.payload.syncAgeHours ?? null,
5734
+ files_synced: legacy.payload.filesTracked ?? null
5735
+ },
5736
+ installationId: legacy.installationId
5737
+ };
5738
+ }
5739
+ function severityToNumber(severity) {
5740
+ const levels = {
5741
+ DEBUG: 100,
5742
+ INFO: 200,
5743
+ NOTICE: 300,
5744
+ WARNING: 400,
5745
+ ERROR: 500,
5746
+ CRITICAL: 600,
5747
+ ALERT: 700,
5748
+ EMERGENCY: 800
5749
+ };
5750
+ return levels[severity];
5751
+ }
5680
5752
  var init_telemetry2 = __esm({
5681
5753
  "../../packages/types/dist/telemetry.js"() {
5682
5754
  "use strict";
@@ -5729,16 +5801,27 @@ function normalizeBackgroundAgentRunnerLabel(value) {
5729
5801
  return value;
5730
5802
  return RETIRED_BACKGROUND_AGENT_RUNNER_LABELS[value];
5731
5803
  }
5732
- var ACTIVE_BACKGROUND_AGENT_RUNNER_LABELS, RETIRED_BACKGROUND_AGENT_RUNNER_LABELS, DEFAULT_RUNNER_LABEL;
5804
+ var SESSION_AGENTS, DEFAULT_SESSION_AGENT, ACTIVE_BACKGROUND_AGENT_RUNNER_LABELS, SECURITY_GATED_RUNNER_LABELS, RETIRED_BACKGROUND_AGENT_RUNNER_LABELS, DEFAULT_RUNNER_LABEL;
5733
5805
  var init_session = __esm({
5734
5806
  "../../packages/types/dist/session.js"() {
5735
5807
  "use strict";
5808
+ SESSION_AGENTS = [
5809
+ { id: "claude", displayName: "Claude Code", icon: "\u{1F916}", description: "Anthropic Claude Code CLI" },
5810
+ { id: "codex", displayName: "Codex CLI", icon: "\u{1F31F}", description: "OpenAI Codex CLI" },
5811
+ { id: "gemini", displayName: "Gemini CLI", icon: "\u{1F48E}", description: "Google Gemini CLI" },
5812
+ { id: "cursor-agent", displayName: "Cursor Agent", icon: "\u{1F3AF}", description: "Cursor AI Agent" },
5813
+ { id: "copilot", displayName: "GitHub Copilot", icon: "\u{1F680}", description: "GitHub Copilot CLI" }
5814
+ ];
5815
+ DEFAULT_SESSION_AGENT = "claude";
5736
5816
  ACTIVE_BACKGROUND_AGENT_RUNNER_LABELS = [
5737
5817
  "agents-standard-runc-x64",
5738
5818
  "agents-medium-runc-x64",
5739
5819
  "agents-high-runc-x64",
5740
5820
  "agents-kali-runc"
5741
5821
  ];
5822
+ SECURITY_GATED_RUNNER_LABELS = /* @__PURE__ */ new Set([
5823
+ "agents-kali-runc"
5824
+ ]);
5742
5825
  RETIRED_BACKGROUND_AGENT_RUNNER_LABELS = {
5743
5826
  "arc-linux-agents": "agents-standard-runc-x64",
5744
5827
  "arc-linux-agents-runc": "agents-standard-runc-x64",
@@ -5761,9 +5844,198 @@ var init_workflow = __esm({
5761
5844
  });
5762
5845
 
5763
5846
  // ../../packages/types/dist/work-item.js
5847
+ function normalizeStringList(values) {
5848
+ if (!values)
5849
+ return values;
5850
+ const normalized = values.map((value) => typeof value === "string" ? value.trim() : "").filter((value) => value.length > 0);
5851
+ return normalized.length > 0 ? normalized : void 0;
5852
+ }
5853
+ function isProcessExecutionMode(value) {
5854
+ return typeof value === "string" && PROCESS_EXECUTION_MODES.includes(value);
5855
+ }
5856
+ function normalizeOperationsProcessStep(raw) {
5857
+ if (!raw)
5858
+ return void 0;
5859
+ const executionPath = typeof raw.executionPath === "string" ? raw.executionPath.trim() : raw.executionPath;
5860
+ const result = {
5861
+ ...raw,
5862
+ processKey: String(raw.processKey ?? "").trim(),
5863
+ title: String(raw.title ?? "").trim(),
5864
+ executionPath
5865
+ };
5866
+ if (raw.updatedBy) {
5867
+ result.updatedBy = raw.updatedBy.trim();
5868
+ }
5869
+ if (raw.source) {
5870
+ result.source = raw.source;
5871
+ }
5872
+ if (raw.approvalGate) {
5873
+ const approvers = normalizeStringList(raw.approvalGate.approvers);
5874
+ result.approvalGate = {
5875
+ ...raw.approvalGate,
5876
+ approvers
5877
+ };
5878
+ }
5879
+ if (raw.automationScope) {
5880
+ const allowedProfiles = normalizeStringList(raw.automationScope.allowedProfiles);
5881
+ result.automationScope = {
5882
+ ...raw.automationScope,
5883
+ allowedProfiles
5884
+ };
5885
+ }
5886
+ if (raw.matchCriteria) {
5887
+ const repositoryPatterns = normalizeStringList(raw.matchCriteria.repositoryPatterns);
5888
+ const commandPatterns = normalizeStringList(raw.matchCriteria.commandPatterns);
5889
+ result.matchCriteria = {
5890
+ ...raw.matchCriteria,
5891
+ repositoryPatterns,
5892
+ commandPatterns
5893
+ };
5894
+ }
5895
+ return result;
5896
+ }
5897
+ function resolveOperationsBoundaryDecision(params) {
5898
+ const processKey = params.processKey?.trim();
5899
+ const boundary = normalizeOperationsProcessStep(params.boundary ?? void 0);
5900
+ if (!boundary) {
5901
+ if (!processKey) {
5902
+ return {
5903
+ canExecute: true,
5904
+ requiresReview: false,
5905
+ boundaryState: "missing"
5906
+ };
5907
+ }
5908
+ return {
5909
+ canExecute: false,
5910
+ requiresReview: false,
5911
+ boundaryState: "missing",
5912
+ processKey,
5913
+ reason: `Missing Operations boundary contract for process "${processKey}". Treating as manual-only and refusing autonomous dispatch.`
5914
+ };
5915
+ }
5916
+ const executionPath = boundary.executionPath;
5917
+ const hasValidExecutionPath = isProcessExecutionMode(executionPath);
5918
+ if (!boundary.processKey || !boundary.title || !hasValidExecutionPath) {
5919
+ const malformedPath = typeof executionPath === "string" && executionPath.length > 0 ? executionPath : "(missing)";
5920
+ const malformedReason = !hasValidExecutionPath ? `Operations boundary contract for process "${processKey || boundary.processKey || "(unknown)"}" has invalid executionPath "${malformedPath}". Treating as manual-only and refusing autonomous dispatch.` : "Operations boundary contract is incomplete or malformed. Treating as manual-only and refusing autonomous dispatch.";
5921
+ return {
5922
+ canExecute: false,
5923
+ requiresReview: false,
5924
+ boundaryState: "invalid",
5925
+ processKey: processKey || boundary.processKey || void 0,
5926
+ reason: malformedReason
5927
+ };
5928
+ }
5929
+ const resolvedProcessKey = boundary.processKey.trim();
5930
+ if (processKey && processKey !== resolvedProcessKey) {
5931
+ return {
5932
+ canExecute: false,
5933
+ requiresReview: false,
5934
+ boundaryState: "mismatch",
5935
+ processKey,
5936
+ executionPath,
5937
+ reason: `Process key "${processKey}" does not match Operations boundary "${resolvedProcessKey}". Refusing dispatch until the contract is aligned.`
5938
+ };
5939
+ }
5940
+ if (!boundary.enabled) {
5941
+ return {
5942
+ canExecute: false,
5943
+ requiresReview: false,
5944
+ boundaryState: "disabled",
5945
+ processKey: resolvedProcessKey,
5946
+ executionPath,
5947
+ reason: `Operations boundary "${resolvedProcessKey}" is disabled.`
5948
+ };
5949
+ }
5950
+ if (executionPath === "manual" || executionPath === "workforce") {
5951
+ return {
5952
+ canExecute: false,
5953
+ requiresReview: false,
5954
+ boundaryState: executionPath,
5955
+ processKey: resolvedProcessKey,
5956
+ executionPath,
5957
+ reason: `Operations boundary "${resolvedProcessKey}" is ${executionPath}-only and cannot be dispatched to a background agent.`
5958
+ };
5959
+ }
5960
+ if (executionPath === "hybrid") {
5961
+ return {
5962
+ canExecute: true,
5963
+ requiresReview: true,
5964
+ boundaryState: "hybrid",
5965
+ processKey: resolvedProcessKey,
5966
+ executionPath,
5967
+ reason: `Operations boundary "${resolvedProcessKey}" allows agent execution but requires human review checkpoints.`
5968
+ };
5969
+ }
5970
+ return {
5971
+ canExecute: true,
5972
+ requiresReview: false,
5973
+ boundaryState: "agent",
5974
+ processKey: resolvedProcessKey,
5975
+ executionPath
5976
+ };
5977
+ }
5978
+ function validateExecutionIdentityEnvelope(envelope) {
5979
+ if (!envelope || typeof envelope !== "object") {
5980
+ return "ExecutionIdentityEnvelope is missing or not an object";
5981
+ }
5982
+ const e = envelope;
5983
+ const requiredStringFields = [
5984
+ "requesterId",
5985
+ "credentialOwnerId",
5986
+ "executionOwnerId",
5987
+ "credentialResolutionMethod",
5988
+ "resolvedAt"
5989
+ ];
5990
+ for (const field of requiredStringFields) {
5991
+ if (typeof e[field] !== "string" || e[field].trim().length === 0) {
5992
+ return `ExecutionIdentityEnvelope.${field} is missing or empty`;
5993
+ }
5994
+ }
5995
+ const validMethods = [
5996
+ "explicit-credential-user",
5997
+ "caller-identity",
5998
+ "token-label",
5999
+ "org-credential-owner",
6000
+ "provider-credential-owner"
6001
+ ];
6002
+ if (!validMethods.includes(e.credentialResolutionMethod)) {
6003
+ return `ExecutionIdentityEnvelope.credentialResolutionMethod "${e.credentialResolutionMethod}" is not a valid resolution method`;
6004
+ }
6005
+ return null;
6006
+ }
6007
+ function normalizeWorkItemExecutionContext(raw) {
6008
+ const result = { ...raw };
6009
+ if (result.relatedProcessKeys) {
6010
+ result.relatedProcessKeys = result.relatedProcessKeys.filter((k) => typeof k === "string" && k.trim().length > 0);
6011
+ }
6012
+ if (result.operationsBoundary) {
6013
+ result.operationsBoundary = normalizeOperationsProcessStep(result.operationsBoundary);
6014
+ }
6015
+ return result;
6016
+ }
6017
+ function serializeWorkItemExecutionContext(ctx) {
6018
+ return JSON.stringify(normalizeWorkItemExecutionContext(ctx));
6019
+ }
6020
+ function parseWorkItemExecutionContext(raw) {
6021
+ if (!raw || raw.trim().length === 0) {
6022
+ return {};
6023
+ }
6024
+ try {
6025
+ const parsed = JSON.parse(raw);
6026
+ if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
6027
+ return { context: normalizeWorkItemExecutionContext(parsed) };
6028
+ }
6029
+ return { legacyText: raw };
6030
+ } catch {
6031
+ return { legacyText: raw };
6032
+ }
6033
+ }
6034
+ var PROCESS_EXECUTION_MODES;
5764
6035
  var init_work_item = __esm({
5765
6036
  "../../packages/types/dist/work-item.js"() {
5766
6037
  "use strict";
6038
+ PROCESS_EXECUTION_MODES = ["agent", "hybrid", "manual", "workforce"];
5767
6039
  }
5768
6040
  });
5769
6041
 
@@ -5796,10 +6068,17 @@ var init_config_governance = __esm({
5796
6068
  });
5797
6069
 
5798
6070
  // ../../packages/types/dist/credentials.js
5799
- var CREDENTIAL_PROVIDER_CONFIGS;
6071
+ function getCredentialProviderConfig(provider) {
6072
+ return CREDENTIAL_PROVIDER_CONFIGS.find((c) => c.id === provider);
6073
+ }
6074
+ function isValidCredentialProvider(value) {
6075
+ return CREDENTIAL_PROVIDERS.includes(value);
6076
+ }
6077
+ var CREDENTIAL_PROVIDERS, CREDENTIAL_PROVIDER_CONFIGS;
5800
6078
  var init_credentials = __esm({
5801
6079
  "../../packages/types/dist/credentials.js"() {
5802
6080
  "use strict";
6081
+ CREDENTIAL_PROVIDERS = ["claude", "codex", "gemini", "cursor"];
5803
6082
  CREDENTIAL_PROVIDER_CONFIGS = [
5804
6083
  {
5805
6084
  id: "claude",
@@ -5895,9 +6174,33 @@ var init_orchestrator_brain = __esm({
5895
6174
  });
5896
6175
 
5897
6176
  // ../../packages/types/dist/provider-usage.js
6177
+ function calculateHealthState(usagePercent, thresholds = DEFAULT_USAGE_THRESHOLDS) {
6178
+ if (usagePercent === null)
6179
+ return "ok";
6180
+ if (usagePercent >= thresholds.criticalThreshold)
6181
+ return "critical";
6182
+ if (usagePercent >= thresholds.warningThreshold)
6183
+ return "warning";
6184
+ return "ok";
6185
+ }
6186
+ function calculateHeadroom(currentUsage, limit) {
6187
+ if (limit === null)
6188
+ return null;
6189
+ return Math.max(0, limit - currentUsage);
6190
+ }
6191
+ function calculateUsagePercent(currentUsage, limit) {
6192
+ if (limit === null || limit === 0)
6193
+ return null;
6194
+ return Math.min(100, currentUsage / limit * 100);
6195
+ }
6196
+ var DEFAULT_USAGE_THRESHOLDS;
5898
6197
  var init_provider_usage = __esm({
5899
6198
  "../../packages/types/dist/provider-usage.js"() {
5900
6199
  "use strict";
6200
+ DEFAULT_USAGE_THRESHOLDS = {
6201
+ warningThreshold: 70,
6202
+ criticalThreshold: 90
6203
+ };
5901
6204
  }
5902
6205
  });
5903
6206
 
@@ -5909,9 +6212,47 @@ var init_worker_pool = __esm({
5909
6212
  });
5910
6213
 
5911
6214
  // ../../packages/types/dist/provider-precedence.js
6215
+ function mapAgentToProvider(agent) {
6216
+ const mapping = {
6217
+ claude: "claude",
6218
+ codex: "codex",
6219
+ gemini: "gemini"
6220
+ };
6221
+ return mapping[agent] ?? "claude";
6222
+ }
6223
+ function mapProviderToAgent(provider) {
6224
+ const mapping = {
6225
+ claude: "claude",
6226
+ codex: "codex",
6227
+ gemini: "gemini"
6228
+ };
6229
+ return mapping[provider];
6230
+ }
6231
+ function determineFallbackReason(requestedChecks, fallbackChecks) {
6232
+ if (!requestedChecks.credentials)
6233
+ return "fallback_credentials";
6234
+ if (!requestedChecks.capacity)
6235
+ return "fallback_capacity";
6236
+ if (!requestedChecks.quota)
6237
+ return "fallback_quota";
6238
+ if (!requestedChecks.health)
6239
+ return "fallback_health";
6240
+ if (!requestedChecks.policy)
6241
+ return "fallback_policy";
6242
+ return "fallback_capacity";
6243
+ }
6244
+ function formatProviderSelection(result) {
6245
+ if (result.reason === "requested") {
6246
+ return `Selected ${result.provider} (requested)`;
6247
+ }
6248
+ const requested = result.requestedProvider ?? "claude";
6249
+ return `Selected ${result.provider} (fallback from ${requested}: ${result.reason})`;
6250
+ }
6251
+ var DEFAULT_PROVIDER_PRECEDENCE;
5912
6252
  var init_provider_precedence = __esm({
5913
6253
  "../../packages/types/dist/provider-precedence.js"() {
5914
6254
  "use strict";
6255
+ DEFAULT_PROVIDER_PRECEDENCE = ["codex", "claude", "gemini"];
5915
6256
  }
5916
6257
  });
5917
6258
 
@@ -5923,23 +6264,78 @@ var init_sdlc_enforcement = __esm({
5923
6264
  });
5924
6265
 
5925
6266
  // ../../packages/types/dist/supervisor-directive.js
6267
+ var DEFAULT_DIRECTIVE_POLICY;
5926
6268
  var init_supervisor_directive = __esm({
5927
6269
  "../../packages/types/dist/supervisor-directive.js"() {
5928
6270
  "use strict";
6271
+ DEFAULT_DIRECTIVE_POLICY = {
6272
+ maxCapacityPerOrg: 10,
6273
+ maxRetryAttempts: 3,
6274
+ minRetryDelayMs: 5e3,
6275
+ // 5 seconds
6276
+ allowedAgents: ["claude", "codex", "gemini", "any"],
6277
+ enforceIdempotency: true,
6278
+ idempotencyWindowMs: 3e5
6279
+ // 5 minutes
6280
+ };
5929
6281
  }
5930
6282
  });
5931
6283
 
5932
6284
  // ../../packages/types/dist/browser-automation.js
6285
+ var BROWSER_BACKEND_FALLBACK_ORDER, BROWSER_BACKEND_CONFIGS, FAILURE_REMEDIATIONS;
5933
6286
  var init_browser_automation = __esm({
5934
6287
  "../../packages/types/dist/browser-automation.js"() {
5935
6288
  "use strict";
6289
+ BROWSER_BACKEND_FALLBACK_ORDER = [
6290
+ "chrome-devtools",
6291
+ "playwright",
6292
+ "claude-chrome"
6293
+ ];
6294
+ BROWSER_BACKEND_CONFIGS = {
6295
+ "chrome-devtools": {
6296
+ displayName: "Chrome DevTools MCP",
6297
+ mcpServerName: "chrome-devtools",
6298
+ description: "Direct Chrome DevTools Protocol via chrome-devtools-mcp"
6299
+ },
6300
+ "playwright": {
6301
+ displayName: "Playwright MCP",
6302
+ mcpServerName: "playwright",
6303
+ description: "Playwright browser automation via @anthropic/playwright-mcp"
6304
+ },
6305
+ "claude-chrome": {
6306
+ displayName: "Claude Chrome",
6307
+ mcpServerName: "claude-in-chrome",
6308
+ description: "Claude built-in Chrome automation via Claude-in-Chrome MCP"
6309
+ }
6310
+ };
6311
+ FAILURE_REMEDIATIONS = {
6312
+ BACKEND_NOT_AVAILABLE: "No browser automation backend is available. Ensure at least one MCP server (chrome-devtools, playwright, or claude-chrome) is configured in approved config or user MCP config.",
6313
+ BACKEND_CONNECTION_FAILED: "Browser backend was detected but connection failed. Check that the MCP server process is running and the browser is accessible.",
6314
+ PERMISSION_DENIED: "Insufficient permissions for this browser action. Check MCP server configuration and ensure the agent has browser automation permissions.",
6315
+ AUTH_REQUIRED: "The target page requires authentication. Navigate to the login page first or provide credentials via environment variables.",
6316
+ BROWSER_NOT_FOUND: "Browser binary not found. Install Chrome/Chromium or set the CHROME_PATH environment variable.",
6317
+ ELEMENT_NOT_FOUND: "Target element not found in the DOM. Verify the selector/UID and ensure the page has fully loaded.",
6318
+ PAGE_TIMEOUT: "Page operation timed out. Check network connectivity, increase timeout, or verify the target URL is reachable.",
6319
+ SCREENSHOT_FAILED: "Screenshot capture failed. Try a different capture mode (viewport instead of full_page) or check browser state.",
6320
+ INVALID_SELECTOR: "The provided CSS/XPath selector is malformed. Verify the selector syntax and try again.",
6321
+ MCP_SERVER_ERROR: "The MCP server returned an error. Check MCP server logs for details and ensure the server version is up to date."
6322
+ };
5936
6323
  }
5937
6324
  });
5938
6325
 
5939
6326
  // ../../packages/types/dist/team-assignment.js
6327
+ var DEFAULT_TEAM_ASSIGNMENT_CONFIG;
5940
6328
  var init_team_assignment = __esm({
5941
6329
  "../../packages/types/dist/team-assignment.js"() {
5942
6330
  "use strict";
6331
+ DEFAULT_TEAM_ASSIGNMENT_CONFIG = {
6332
+ supervisorWorkerRatio: 3,
6333
+ maxTotalWorkers: 12,
6334
+ providerCaps: { claude: 6, codex: 4, gemini: 4 },
6335
+ maxPendingAssignments: 20,
6336
+ maxWorkersPerRepo: 4,
6337
+ fairnessWeight: 0.5
6338
+ };
5943
6339
  }
5944
6340
  });
5945
6341
 
@@ -6111,8 +6507,310 @@ var init_learning = __esm({
6111
6507
  }
6112
6508
  });
6113
6509
 
6510
+ // ../../packages/types/dist/enforcement-mode.js
6511
+ function checkEnforcement(toolName, toolInput, settings, sessionRole, userLogin) {
6512
+ if (!settings.enabled || settings.mode === "off") {
6513
+ return {
6514
+ allowed: true,
6515
+ mode: settings.mode,
6516
+ sessionRole,
6517
+ isWarning: false
6518
+ };
6519
+ }
6520
+ if (sessionRole === "worker") {
6521
+ return {
6522
+ allowed: true,
6523
+ mode: settings.mode,
6524
+ sessionRole,
6525
+ isWarning: false
6526
+ };
6527
+ }
6528
+ if (userLogin && settings.exemptUsers && settings.exemptUsers.includes(userLogin)) {
6529
+ return {
6530
+ allowed: true,
6531
+ mode: settings.mode,
6532
+ sessionRole,
6533
+ isWarning: false
6534
+ };
6535
+ }
6536
+ if (ALWAYS_ALLOWED_TOOLS.includes(toolName)) {
6537
+ return {
6538
+ allowed: true,
6539
+ mode: settings.mode,
6540
+ sessionRole,
6541
+ isWarning: false
6542
+ };
6543
+ }
6544
+ const blockedTools = settings.blockedTools && settings.blockedTools.length > 0 ? settings.blockedTools : DEFAULT_BLOCKED_TOOLS;
6545
+ const isBlockedTool = blockedTools.includes(toolName);
6546
+ let isBlockedBash = false;
6547
+ if (toolName === "Bash") {
6548
+ const command = String(toolInput?.command || "");
6549
+ isBlockedBash = BLOCKED_BASH_PATTERNS.some((pattern) => command.includes(pattern));
6550
+ }
6551
+ const isBlocked = isBlockedTool || isBlockedBash;
6552
+ if (!isBlocked) {
6553
+ return {
6554
+ allowed: true,
6555
+ mode: settings.mode,
6556
+ sessionRole,
6557
+ isWarning: false
6558
+ };
6559
+ }
6560
+ const org = "your organization";
6561
+ const blockMessage = (settings.blockMessage || DEFAULT_BLOCK_MESSAGE).replace(/\{tool\}/g, toolName).replace(/\{org\}/g, org);
6562
+ if (settings.mode === "warn") {
6563
+ return {
6564
+ allowed: true,
6565
+ mode: settings.mode,
6566
+ sessionRole,
6567
+ reason: `\u26A0\uFE0F WARNING: ${blockMessage}`,
6568
+ isWarning: true
6569
+ };
6570
+ }
6571
+ return {
6572
+ allowed: false,
6573
+ mode: settings.mode,
6574
+ sessionRole,
6575
+ reason: blockMessage,
6576
+ isWarning: false
6577
+ };
6578
+ }
6579
+ var DEFAULT_BLOCKED_TOOLS, BLOCKED_BASH_PATTERNS, ALWAYS_ALLOWED_TOOLS, DEFAULT_ENFORCEMENT_MODE_SETTINGS, DEFAULT_BLOCK_MESSAGE;
6580
+ var init_enforcement_mode = __esm({
6581
+ "../../packages/types/dist/enforcement-mode.js"() {
6582
+ "use strict";
6583
+ DEFAULT_BLOCKED_TOOLS = [
6584
+ "Edit",
6585
+ "Write",
6586
+ "MultiEdit",
6587
+ "NotebookEdit"
6588
+ ];
6589
+ BLOCKED_BASH_PATTERNS = [
6590
+ "git push",
6591
+ "git commit",
6592
+ "git add",
6593
+ "git stash",
6594
+ "git rebase",
6595
+ "git merge",
6596
+ "git cherry-pick",
6597
+ "npm publish",
6598
+ "pnpm publish",
6599
+ "make deploy",
6600
+ "make release"
6601
+ ];
6602
+ ALWAYS_ALLOWED_TOOLS = [
6603
+ "Read",
6604
+ "Glob",
6605
+ "Grep",
6606
+ "LSP",
6607
+ "WebFetch",
6608
+ "WebSearch",
6609
+ "AskUserQuestion",
6610
+ "TodoWrite",
6611
+ "Task",
6612
+ "Skill",
6613
+ "EnterPlanMode",
6614
+ "ExitPlanMode"
6615
+ ];
6616
+ DEFAULT_ENFORCEMENT_MODE_SETTINGS = {
6617
+ mode: "off",
6618
+ enabled: false
6619
+ };
6620
+ DEFAULT_BLOCK_MESSAGE = "\u{1F6AB} This action is blocked by your organization's enforcement policy.\nYour org ({org}) requires all implementation work to go through the background agent queue.\n\nTo proceed:\n 1. Create a GitHub issue describing the work\n 2. Use `gal dispatch` or the dashboard to queue a background agent\n 3. Monitor and review the agent's output\n\nBlocked tool: {tool}";
6621
+ }
6622
+ });
6623
+
6624
+ // ../../packages/types/dist/sdlc-evaluation.js
6625
+ var init_sdlc_evaluation = __esm({
6626
+ "../../packages/types/dist/sdlc-evaluation.js"() {
6627
+ "use strict";
6628
+ }
6629
+ });
6630
+
6631
+ // ../../packages/types/dist/oss-model-eval.js
6632
+ var OSS_EVAL_SCHEMA_VERSION, OSS_EVAL_DATASET_PATH, OSS_EVAL_TASK_SET_MODES, OSS_EVAL_CANONICAL_PROVIDERS, OSS_EVAL_METRIC_IDS, DEFAULT_OSS_EVAL_THRESHOLDS, DEFAULT_OSS_EVAL_WEIGHTS, DEFAULT_OSS_EVAL_WIN_CRITERIA, OSS_EVAL_METRICS, DEFAULT_OSS_EVAL_SPEC;
6633
+ var init_oss_model_eval = __esm({
6634
+ "../../packages/types/dist/oss-model-eval.js"() {
6635
+ "use strict";
6636
+ OSS_EVAL_SCHEMA_VERSION = "v1";
6637
+ OSS_EVAL_DATASET_PATH = "/sessions";
6638
+ OSS_EVAL_TASK_SET_MODES = ["intersection", "all"];
6639
+ OSS_EVAL_CANONICAL_PROVIDERS = ["claude", "codex", "oss"];
6640
+ OSS_EVAL_METRIC_IDS = [
6641
+ "pr_merge_rate",
6642
+ "ci_pass_rate_first_attempt",
6643
+ "task_completion_rate",
6644
+ "review_score"
6645
+ ];
6646
+ DEFAULT_OSS_EVAL_THRESHOLDS = {
6647
+ prMergeRate: 0.75,
6648
+ ciPassRateFirstAttempt: 0.65,
6649
+ taskCompletionRate: 0.8,
6650
+ reviewScore: 0.85
6651
+ };
6652
+ DEFAULT_OSS_EVAL_WEIGHTS = {
6653
+ prMergeRate: 0.3,
6654
+ ciPassRateFirstAttempt: 0.2,
6655
+ taskCompletionRate: 0.35,
6656
+ reviewScore: 0.15
6657
+ };
6658
+ DEFAULT_OSS_EVAL_WIN_CRITERIA = {
6659
+ minimumSharedTasks: 20,
6660
+ minimumCompositeScore: 0.75,
6661
+ requireAllMetricThresholds: true
6662
+ };
6663
+ OSS_EVAL_METRICS = {
6664
+ pr_merge_rate: {
6665
+ id: "pr_merge_rate",
6666
+ label: "PR Merge Rate",
6667
+ numeratorDefinition: "PR merged",
6668
+ denominatorDefinition: "Session has mapped PR",
6669
+ thresholdKey: "prMergeRate",
6670
+ weightKey: "prMergeRate"
6671
+ },
6672
+ ci_pass_rate_first_attempt: {
6673
+ id: "ci_pass_rate_first_attempt",
6674
+ label: "CI Pass Rate (First Attempt)",
6675
+ numeratorDefinition: "PR CI passed on first attempt",
6676
+ denominatorDefinition: "Session has mapped PR with CI evidence",
6677
+ thresholdKey: "ciPassRateFirstAttempt",
6678
+ weightKey: "ciPassRateFirstAttempt"
6679
+ },
6680
+ task_completion_rate: {
6681
+ id: "task_completion_rate",
6682
+ label: "Task Completion Rate",
6683
+ numeratorDefinition: "Issue closed with no human reopen",
6684
+ denominatorDefinition: "Session has mapped issue",
6685
+ thresholdKey: "taskCompletionRate",
6686
+ weightKey: "taskCompletionRate"
6687
+ },
6688
+ review_score: {
6689
+ id: "review_score",
6690
+ label: "Review Score",
6691
+ numeratorDefinition: "PR had no requested changes",
6692
+ denominatorDefinition: "Session has mapped PR with review evidence",
6693
+ thresholdKey: "reviewScore",
6694
+ weightKey: "reviewScore"
6695
+ }
6696
+ };
6697
+ DEFAULT_OSS_EVAL_SPEC = {
6698
+ schemaVersion: OSS_EVAL_SCHEMA_VERSION,
6699
+ datasetPath: OSS_EVAL_DATASET_PATH,
6700
+ defaultTaskSetMode: "intersection",
6701
+ metrics: OSS_EVAL_METRICS,
6702
+ thresholds: DEFAULT_OSS_EVAL_THRESHOLDS,
6703
+ weights: DEFAULT_OSS_EVAL_WEIGHTS,
6704
+ winCriteria: DEFAULT_OSS_EVAL_WIN_CRITERIA
6705
+ };
6706
+ }
6707
+ });
6708
+
6709
+ // ../../packages/types/dist/ab-routing.js
6710
+ function laneToEvalProvider(lane, vendorProvider) {
6711
+ if (lane === "oss")
6712
+ return "oss";
6713
+ if (vendorProvider === "codex")
6714
+ return "codex";
6715
+ return "claude";
6716
+ }
6717
+ var DEFAULT_AB_ROUTING_CONFIG, AB_ROUTING_MODES;
6718
+ var init_ab_routing = __esm({
6719
+ "../../packages/types/dist/ab-routing.js"() {
6720
+ "use strict";
6721
+ DEFAULT_AB_ROUTING_CONFIG = {
6722
+ enabled: false,
6723
+ mode: "vendor_only"
6724
+ };
6725
+ AB_ROUTING_MODES = [
6726
+ "vendor_only",
6727
+ "oss_only",
6728
+ "percentage",
6729
+ "round_robin",
6730
+ "conditional"
6731
+ ];
6732
+ }
6733
+ });
6734
+
6114
6735
  // ../../packages/types/dist/index.js
6115
- var GAL_TERMS_URL, GAL_PRIVACY_URL, PLATFORM_PATTERNS;
6736
+ var dist_exports = {};
6737
+ __export(dist_exports, {
6738
+ AB_ROUTING_MODES: () => AB_ROUTING_MODES,
6739
+ ACTIVE_BACKGROUND_AGENT_RUNNER_LABELS: () => ACTIVE_BACKGROUND_AGENT_RUNNER_LABELS,
6740
+ ALL_PAGE_IDS: () => ALL_PAGE_IDS,
6741
+ ALL_PLATFORM_IDS: () => ALL_PLATFORM_IDS,
6742
+ ALWAYS_ALLOWED_TOOLS: () => ALWAYS_ALLOWED_TOOLS,
6743
+ BLOCKED_BASH_PATTERNS: () => BLOCKED_BASH_PATTERNS,
6744
+ BROWSER_BACKEND_CONFIGS: () => BROWSER_BACKEND_CONFIGS,
6745
+ BROWSER_BACKEND_FALLBACK_ORDER: () => BROWSER_BACKEND_FALLBACK_ORDER,
6746
+ CREDENTIAL_PROVIDERS: () => CREDENTIAL_PROVIDERS,
6747
+ CREDENTIAL_PROVIDER_CONFIGS: () => CREDENTIAL_PROVIDER_CONFIGS,
6748
+ CREDENTIAL_SYNC_PLATFORMS: () => CREDENTIAL_SYNC_PLATFORMS,
6749
+ DEFAULT_AB_ROUTING_CONFIG: () => DEFAULT_AB_ROUTING_CONFIG,
6750
+ DEFAULT_BLOCKED_TOOLS: () => DEFAULT_BLOCKED_TOOLS,
6751
+ DEFAULT_BLOCK_MESSAGE: () => DEFAULT_BLOCK_MESSAGE,
6752
+ DEFAULT_DIRECTIVE_POLICY: () => DEFAULT_DIRECTIVE_POLICY,
6753
+ DEFAULT_DISPATCH_CATEGORIES: () => DEFAULT_DISPATCH_CATEGORIES,
6754
+ DEFAULT_ENFORCEMENT_MODE_SETTINGS: () => DEFAULT_ENFORCEMENT_MODE_SETTINGS,
6755
+ DEFAULT_ENFORCEMENT_SETTINGS: () => DEFAULT_ENFORCEMENT_SETTINGS,
6756
+ DEFAULT_OSS_EVAL_SPEC: () => DEFAULT_OSS_EVAL_SPEC,
6757
+ DEFAULT_OSS_EVAL_THRESHOLDS: () => DEFAULT_OSS_EVAL_THRESHOLDS,
6758
+ DEFAULT_OSS_EVAL_WEIGHTS: () => DEFAULT_OSS_EVAL_WEIGHTS,
6759
+ DEFAULT_OSS_EVAL_WIN_CRITERIA: () => DEFAULT_OSS_EVAL_WIN_CRITERIA,
6760
+ DEFAULT_PROVIDER_PRECEDENCE: () => DEFAULT_PROVIDER_PRECEDENCE,
6761
+ DEFAULT_RUNNER_LABEL: () => DEFAULT_RUNNER_LABEL,
6762
+ DEFAULT_SESSION_AGENT: () => DEFAULT_SESSION_AGENT,
6763
+ DEFAULT_TEAM_ASSIGNMENT_CONFIG: () => DEFAULT_TEAM_ASSIGNMENT_CONFIG,
6764
+ DEFAULT_USAGE_THRESHOLDS: () => DEFAULT_USAGE_THRESHOLDS,
6765
+ ERROR_CODE_CATEGORIES: () => ERROR_CODE_CATEGORIES,
6766
+ ErrorCategory: () => ErrorCategory,
6767
+ FAILURE_REMEDIATIONS: () => FAILURE_REMEDIATIONS,
6768
+ GAL_PRIVACY_URL: () => GAL_PRIVACY_URL,
6769
+ GAL_TERMS_URL: () => GAL_TERMS_URL,
6770
+ HOOKABLE_PLATFORMS: () => HOOKABLE_PLATFORMS,
6771
+ OSS_EVAL_CANONICAL_PROVIDERS: () => OSS_EVAL_CANONICAL_PROVIDERS,
6772
+ OSS_EVAL_DATASET_PATH: () => OSS_EVAL_DATASET_PATH,
6773
+ OSS_EVAL_METRICS: () => OSS_EVAL_METRICS,
6774
+ OSS_EVAL_METRIC_IDS: () => OSS_EVAL_METRIC_IDS,
6775
+ OSS_EVAL_SCHEMA_VERSION: () => OSS_EVAL_SCHEMA_VERSION,
6776
+ OSS_EVAL_TASK_SET_MODES: () => OSS_EVAL_TASK_SET_MODES,
6777
+ PLATFORM_DIRECTORIES: () => PLATFORM_DIRECTORIES,
6778
+ PLATFORM_DIRECTORY_MAP: () => PLATFORM_DIRECTORY_MAP,
6779
+ PLATFORM_DISPLAY_MAP: () => PLATFORM_DISPLAY_MAP,
6780
+ PLATFORM_INSTRUCTION_FILE_MAP: () => PLATFORM_INSTRUCTION_FILE_MAP,
6781
+ PLATFORM_PATTERNS: () => PLATFORM_PATTERNS,
6782
+ PLATFORM_REGISTRY: () => PLATFORM_REGISTRY,
6783
+ RETIRED_BACKGROUND_AGENT_RUNNER_LABELS: () => RETIRED_BACKGROUND_AGENT_RUNNER_LABELS,
6784
+ SECURITY_GATED_RUNNER_LABELS: () => SECURITY_GATED_RUNNER_LABELS,
6785
+ SESSION_AGENTS: () => SESSION_AGENTS,
6786
+ SESSION_RUNNER_PLATFORMS: () => SESSION_RUNNER_PLATFORMS,
6787
+ STABLE_PLATFORM_IDS: () => STABLE_PLATFORM_IDS,
6788
+ SessionErrorCode: () => SessionErrorCode,
6789
+ calculateHeadroom: () => calculateHeadroom,
6790
+ calculateHealthState: () => calculateHealthState,
6791
+ calculateUsagePercent: () => calculateUsagePercent,
6792
+ checkEnforcement: () => checkEnforcement,
6793
+ determineFallbackReason: () => determineFallbackReason,
6794
+ formatProviderSelection: () => formatProviderSelection,
6795
+ getCredentialProviderConfig: () => getCredentialProviderConfig,
6796
+ isActiveBackgroundAgentRunnerLabel: () => isActiveBackgroundAgentRunnerLabel,
6797
+ isEnhancedEvent: () => isEnhancedEvent,
6798
+ isValidCredentialProvider: () => isValidCredentialProvider,
6799
+ laneToEvalProvider: () => laneToEvalProvider,
6800
+ mapAgentToProvider: () => mapAgentToProvider,
6801
+ mapProviderToAgent: () => mapProviderToAgent,
6802
+ normalizeBackgroundAgentRunnerLabel: () => normalizeBackgroundAgentRunnerLabel,
6803
+ normalizeOperationsProcessStep: () => normalizeOperationsProcessStep,
6804
+ normalizeWorkItemExecutionContext: () => normalizeWorkItemExecutionContext,
6805
+ parseWorkItemExecutionContext: () => parseWorkItemExecutionContext,
6806
+ platformsWithCapability: () => platformsWithCapability,
6807
+ resolveOperationsBoundaryDecision: () => resolveOperationsBoundaryDecision,
6808
+ serializeWorkItemExecutionContext: () => serializeWorkItemExecutionContext,
6809
+ severityToNumber: () => severityToNumber,
6810
+ upgradeToEnhancedEvent: () => upgradeToEnhancedEvent,
6811
+ validateExecutionIdentityEnvelope: () => validateExecutionIdentityEnvelope
6812
+ });
6813
+ var GAL_TERMS_URL, GAL_PRIVACY_URL, PLATFORM_DIRECTORIES, PLATFORM_PATTERNS, DEFAULT_ENFORCEMENT_SETTINGS, DEFAULT_DISPATCH_CATEGORIES;
6116
6814
  var init_dist2 = __esm({
6117
6815
  "../../packages/types/dist/index.js"() {
6118
6816
  "use strict";
@@ -6169,8 +6867,13 @@ var init_dist2 = __esm({
6169
6867
  init_memory();
6170
6868
  init_secrets();
6171
6869
  init_learning();
6870
+ init_enforcement_mode();
6871
+ init_sdlc_evaluation();
6872
+ init_oss_model_eval();
6873
+ init_ab_routing();
6172
6874
  GAL_TERMS_URL = "https://scheduler-systems.com/legal/en/gal-terms.pdf";
6173
6875
  GAL_PRIVACY_URL = "https://scheduler-systems.com/legal/en/gal-privacy.pdf";
6876
+ PLATFORM_DIRECTORIES = PLATFORM_DIRECTORY_MAP;
6174
6877
  PLATFORM_PATTERNS = {
6175
6878
  claude: {
6176
6879
  platform: "claude",
@@ -6349,6 +7052,24 @@ var init_dist2 = __esm({
6349
7052
  ruleExtensions: []
6350
7053
  }
6351
7054
  };
7055
+ DEFAULT_ENFORCEMENT_SETTINGS = {
7056
+ enabled: false,
7057
+ level: "warn",
7058
+ blockOnMismatch: false,
7059
+ requireSync: false,
7060
+ allowOverrides: true,
7061
+ notifyOnViolation: false
7062
+ };
7063
+ DEFAULT_DISPATCH_CATEGORIES = [
7064
+ { id: "bug-fixes", name: "Bug Fixes", description: "Fix bugs with clear reproduction steps and specific file locations", enabled: true },
7065
+ { id: "test-writing", name: "Test Writing", description: "Write or update tests for existing code", enabled: true },
7066
+ { id: "documentation", name: "Documentation", description: "Update docs, README files, code comments", enabled: true },
7067
+ { id: "ci-fixes", name: "CI/Lint Fixes", description: "Fix CI failures, lint errors, type errors", enabled: true },
7068
+ { id: "refactoring", name: "Code Refactoring", description: "Refactor code patterns across multiple files", enabled: false },
7069
+ { id: "new-features", name: "New Features", description: "Implement new features from specs", enabled: false },
7070
+ { id: "security-fixes", name: "Security Patches", description: "Fix security vulnerabilities", enabled: false },
7071
+ { id: "migrations", name: "Migrations", description: "Database or dependency migrations", enabled: false }
7072
+ ];
6352
7073
  }
6353
7074
  });
6354
7075
 
@@ -13937,7 +14658,8 @@ var init_HttpWorkItemRepository = __esm({
13937
14658
  priority: request.priority,
13938
14659
  source: request.source,
13939
14660
  context: request.context,
13940
- preferredAgent: request.preferredAgent
14661
+ preferredAgent: request.preferredAgent,
14662
+ ...request.runnerLabel ? { runnerLabel: request.runnerLabel } : {}
13941
14663
  };
13942
14664
  const response = await this.fetchJson("/api/work-items", {
13943
14665
  method: "POST",
@@ -46579,7 +47301,17 @@ function rememberSessionStart(inputData) {
46579
47301
  }),
46580
47302
  'utf-8',
46581
47303
  );
46582
- } catch {}
47304
+ } catch (startError) {
47305
+ // Log so session-start tracking failures are diagnosable (#4970)
47306
+ try {
47307
+ const logDir = path.join(os.homedir(), '.gal', 'logs');
47308
+ if (!fs.existsSync(logDir)) fs.mkdirSync(logDir, { recursive: true });
47309
+ fs.appendFileSync(
47310
+ path.join(logDir, 'usage-hook.log'),
47311
+ \`[\${new Date().toISOString()}] rememberSessionStart failed: \${startError.message || startError}\\n\`,
47312
+ );
47313
+ } catch {}
47314
+ }
46583
47315
  }
46584
47316
 
46585
47317
  function selfClean() {
@@ -46951,7 +47683,17 @@ try {
46951
47683
  \`gal report-usage --provider claude --seconds \${durationSeconds} --org \${JSON.stringify(orgName)} --json\`,
46952
47684
  { stdio: 'pipe', timeout: 15000, shell: true },
46953
47685
  );
46954
- } catch {}
47686
+ } catch (reportError) {
47687
+ // Log to a file so usage-reporting failures are diagnosable (#4970)
47688
+ try {
47689
+ const logDir = path.join(os.homedir(), '.gal', 'logs');
47690
+ if (!fs.existsSync(logDir)) fs.mkdirSync(logDir, { recursive: true });
47691
+ const stderr = reportError && reportError.stderr ? reportError.stderr.toString() : '';
47692
+ const stdout = reportError && reportError.stdout ? reportError.stdout.toString() : '';
47693
+ const entry = \`[\${new Date().toISOString()}] report-usage failed (org=\${orgName}, seconds=\${durationSeconds})\\n exit=\${reportError.status || 'unknown'}\\n stderr: \${stderr.trim() || '(none)'}\\n stdout: \${stdout.trim() || '(none)'}\\n\\n\`;
47694
+ fs.appendFileSync(path.join(logDir, 'usage-hook.log'), entry);
47695
+ } catch {}
47696
+ }
46955
47697
 
46956
47698
  process.exit(0);
46957
47699
  `;
@@ -55608,6 +56350,382 @@ var init_system_enforcer = __esm({
55608
56350
  }
55609
56351
  });
55610
56352
 
56353
+ // src/enforcement/workflow-enforcement-hook.ts
56354
+ var workflow_enforcement_hook_exports = {};
56355
+ __export(workflow_enforcement_hook_exports, {
56356
+ generateWorkflowEnforcementHook: () => generateWorkflowEnforcementHook,
56357
+ generateWorkflowEnforcementHookNode: () => generateWorkflowEnforcementHookNode
56358
+ });
56359
+ function generateWorkflowEnforcementHook(options) {
56360
+ const { apiUrl, orgName, sessionRole } = options;
56361
+ const hookScript = `#!/usr/bin/env python3
56362
+ """
56363
+ GAL Workflow Enforcement Hook - Background Agents Only Mode
56364
+ Generated by GAL CLI for org: ${orgName}
56365
+ API: ${apiUrl}
56366
+
56367
+ Enforces the organization's workflow policy:
56368
+ - 'background-only': Blocks implementation tools in local sessions
56369
+ - 'warn': Warns but allows implementation tools in local sessions
56370
+ - 'off': No restrictions
56371
+
56372
+ Fail-open: If GAL API is unreachable, all tools are allowed.
56373
+ """
56374
+ import json
56375
+ import os
56376
+ import sys
56377
+ import time
56378
+
56379
+ try:
56380
+ from urllib.request import Request, urlopen
56381
+ from urllib.error import URLError
56382
+ except ImportError:
56383
+ # Fallback: if urllib is unavailable, fail open
56384
+ def main():
56385
+ print(json.dumps({"decision": "allow"}))
56386
+ sys.exit(0)
56387
+ if __name__ == "__main__":
56388
+ main()
56389
+ sys.exit(0)
56390
+
56391
+ # Configuration
56392
+ API_URL = "${apiUrl}"
56393
+ ORG_NAME = "${orgName}"
56394
+ CACHE_TTL_SECONDS = 300 # 5 minutes
56395
+ CACHE_FILE = os.path.join(os.path.expanduser("~"), ".gal", "enforcement-cache.json")
56396
+
56397
+ # Tool classifications
56398
+ BLOCKED_TOOLS = ${JSON.stringify(DEFAULT_BLOCKED_TOOLS)}
56399
+ BLOCKED_BASH_PATTERNS = ${JSON.stringify(BLOCKED_BASH_PATTERNS)}
56400
+ ALWAYS_ALLOWED = ${JSON.stringify(ALWAYS_ALLOWED_TOOLS)}
56401
+
56402
+ ${sessionRole === "worker" ? `
56403
+ # This session is a worker (background agent) \u2014 skip all enforcement
56404
+ def main():
56405
+ print(json.dumps({"decision": "allow"}))
56406
+
56407
+ if __name__ == "__main__":
56408
+ main()
56409
+ sys.exit(0)
56410
+ ` : ""}
56411
+
56412
+ def load_cached_settings():
56413
+ """Load enforcement settings from local cache if still fresh."""
56414
+ try:
56415
+ if not os.path.exists(CACHE_FILE):
56416
+ return None
56417
+ with open(CACHE_FILE, "r") as f:
56418
+ cache = json.load(f)
56419
+ if cache.get("orgName") != ORG_NAME:
56420
+ return None
56421
+ cached_at = cache.get("cachedAt", 0)
56422
+ if time.time() - cached_at > CACHE_TTL_SECONDS:
56423
+ return None
56424
+ return cache.get("settings")
56425
+ except Exception:
56426
+ return None
56427
+
56428
+
56429
+ def save_cached_settings(settings):
56430
+ """Save enforcement settings to local cache."""
56431
+ try:
56432
+ cache_dir = os.path.dirname(CACHE_FILE)
56433
+ os.makedirs(cache_dir, exist_ok=True)
56434
+ with open(CACHE_FILE, "w") as f:
56435
+ json.dump({
56436
+ "orgName": ORG_NAME,
56437
+ "settings": settings,
56438
+ "cachedAt": time.time(),
56439
+ }, f)
56440
+ except Exception:
56441
+ pass # Non-critical \u2014 next call will re-fetch
56442
+
56443
+
56444
+ def fetch_enforcement_settings():
56445
+ """Fetch enforcement settings from GAL API. Returns None on failure (fail-open)."""
56446
+ # Try cache first
56447
+ cached = load_cached_settings()
56448
+ if cached is not None:
56449
+ return cached
56450
+
56451
+ # Fetch from API
56452
+ url = f"{API_URL}/organizations/{ORG_NAME}/workflow-enforcement"
56453
+ try:
56454
+ # Use GAL auth token if available
56455
+ token = os.environ.get("GAL_AUTH_TOKEN") or os.environ.get("GAL_API_KEY")
56456
+ headers = {"Content-Type": "application/json"}
56457
+ if token:
56458
+ headers["Authorization"] = f"Bearer {token}"
56459
+
56460
+ req = Request(url, headers=headers)
56461
+ with urlopen(req, timeout=5) as resp:
56462
+ data = json.loads(resp.read().decode("utf-8"))
56463
+ save_cached_settings(data)
56464
+ return data
56465
+ except Exception:
56466
+ # Fail open \u2014 if API is unreachable, allow everything
56467
+ return None
56468
+
56469
+
56470
+ def is_worker_session():
56471
+ """Check if this is a background agent (worker) session."""
56472
+ # Background agents set these env vars
56473
+ if os.environ.get("GAL_SESSION_ROLE") == "worker":
56474
+ return True
56475
+ if os.environ.get("GAL_BACKGROUND_AGENT") == "true":
56476
+ return True
56477
+ if os.environ.get("GITHUB_ACTIONS") == "true" and os.environ.get("GAL_SESSION_ID"):
56478
+ return True
56479
+ return False
56480
+
56481
+
56482
+ def check_tool(tool_name, tool_input, settings):
56483
+ """Check if a tool call should be allowed based on enforcement settings."""
56484
+ mode = settings.get("mode", "off")
56485
+ enabled = settings.get("enabled", False)
56486
+
56487
+ if not enabled or mode == "off":
56488
+ return {"decision": "allow"}
56489
+
56490
+ # Always-allowed tools pass through
56491
+ if tool_name in ALWAYS_ALLOWED:
56492
+ return {"decision": "allow"}
56493
+
56494
+ # Check if user is exempt
56495
+ user_login = os.environ.get("GAL_USER_LOGIN", "")
56496
+ exempt_users = settings.get("exemptUsers", [])
56497
+ if user_login and user_login in exempt_users:
56498
+ return {"decision": "allow"}
56499
+
56500
+ # Check blocked tools
56501
+ custom_blocked = settings.get("blockedTools") or BLOCKED_TOOLS
56502
+ is_blocked_tool = tool_name in custom_blocked
56503
+
56504
+ # Check Bash command patterns
56505
+ is_blocked_bash = False
56506
+ if tool_name == "Bash":
56507
+ command = str(tool_input.get("command", ""))
56508
+ is_blocked_bash = any(p in command for p in BLOCKED_BASH_PATTERNS)
56509
+
56510
+ if not is_blocked_tool and not is_blocked_bash:
56511
+ return {"decision": "allow"}
56512
+
56513
+ # Build message
56514
+ block_msg = settings.get("blockMessage") or ${JSON.stringify(DEFAULT_BLOCK_MESSAGE)}
56515
+ block_msg = block_msg.replace("{tool}", tool_name).replace("{org}", ORG_NAME)
56516
+
56517
+ if mode == "warn":
56518
+ # Warn mode: allow but print warning to stderr
56519
+ sys.stderr.write(f"\\n\\033[33m[GAL] WARNING: {block_msg}\\033[0m\\n\\n")
56520
+ return {"decision": "allow"}
56521
+
56522
+ # background-only mode: block
56523
+ return {
56524
+ "decision": "block",
56525
+ "reason": block_msg,
56526
+ }
56527
+
56528
+
56529
+ def main():
56530
+ # Workers (background agents) are never restricted
56531
+ if is_worker_session():
56532
+ print(json.dumps({"decision": "allow"}))
56533
+ return
56534
+
56535
+ # Read hook input
56536
+ try:
56537
+ hook_input = json.load(sys.stdin)
56538
+ except (json.JSONDecodeError, Exception):
56539
+ print(json.dumps({"decision": "allow"}))
56540
+ return
56541
+
56542
+ tool_name = hook_input.get("tool_name", "")
56543
+ tool_input = hook_input.get("tool_input", {}) or {}
56544
+
56545
+ # Fetch enforcement settings (cached)
56546
+ settings = fetch_enforcement_settings()
56547
+ if settings is None:
56548
+ # Fail open
56549
+ print(json.dumps({"decision": "allow"}))
56550
+ return
56551
+
56552
+ result = check_tool(tool_name, tool_input, settings)
56553
+ print(json.dumps(result))
56554
+
56555
+
56556
+ if __name__ == "__main__":
56557
+ main()
56558
+ `;
56559
+ return {
56560
+ platform: "claude",
56561
+ hookType: "pre_tool_use",
56562
+ filename: "gal-workflow-enforcement.py",
56563
+ content: hookScript,
56564
+ settingsSnippet: {
56565
+ hooks: {
56566
+ pre_tool_use: [{
56567
+ command: "python3 .claude/hooks/gal-workflow-enforcement.py",
56568
+ timeout: 1e4
56569
+ }]
56570
+ }
56571
+ }
56572
+ };
56573
+ }
56574
+ function generateWorkflowEnforcementHookNode(options) {
56575
+ const { apiUrl, orgName, sessionRole } = options;
56576
+ const hookScript = `#!/usr/bin/env node
56577
+ /**
56578
+ * GAL Workflow Enforcement Hook - Background Agents Only Mode
56579
+ * Generated by GAL CLI for org: ${orgName}
56580
+ * API: ${apiUrl}
56581
+ *
56582
+ * Enforces the organization's workflow policy for Cursor/Windsurf.
56583
+ * Fail-open: If GAL API is unreachable, all tools are allowed.
56584
+ */
56585
+ const fs = require('fs');
56586
+ const path = require('path');
56587
+ const readline = require('readline');
56588
+
56589
+ const API_URL = '${apiUrl}';
56590
+ const ORG_NAME = '${orgName}';
56591
+ const CACHE_TTL_MS = 300000; // 5 minutes
56592
+ const CACHE_FILE = path.join(require('os').homedir(), '.gal', 'enforcement-cache.json');
56593
+
56594
+ const BLOCKED_TOOLS = ${JSON.stringify(DEFAULT_BLOCKED_TOOLS)};
56595
+ const BLOCKED_BASH_PATTERNS = ${JSON.stringify(BLOCKED_BASH_PATTERNS)};
56596
+ const ALWAYS_ALLOWED = ${JSON.stringify(ALWAYS_ALLOWED_TOOLS)};
56597
+
56598
+ ${sessionRole === "worker" ? `
56599
+ // Worker session \u2014 skip enforcement
56600
+ console.log(JSON.stringify({ decision: 'allow' }));
56601
+ process.exit(0);
56602
+ ` : ""}
56603
+
56604
+ function isWorkerSession() {
56605
+ return (
56606
+ process.env.GAL_SESSION_ROLE === 'worker' ||
56607
+ process.env.GAL_BACKGROUND_AGENT === 'true' ||
56608
+ (process.env.GITHUB_ACTIONS === 'true' && process.env.GAL_SESSION_ID)
56609
+ );
56610
+ }
56611
+
56612
+ function loadCachedSettings() {
56613
+ try {
56614
+ if (!fs.existsSync(CACHE_FILE)) return null;
56615
+ const cache = JSON.parse(fs.readFileSync(CACHE_FILE, 'utf8'));
56616
+ if (cache.orgName !== ORG_NAME) return null;
56617
+ if (Date.now() - (cache.cachedAt || 0) > CACHE_TTL_MS) return null;
56618
+ return cache.settings;
56619
+ } catch { return null; }
56620
+ }
56621
+
56622
+ function saveCachedSettings(settings) {
56623
+ try {
56624
+ const dir = path.dirname(CACHE_FILE);
56625
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
56626
+ fs.writeFileSync(CACHE_FILE, JSON.stringify({
56627
+ orgName: ORG_NAME,
56628
+ settings,
56629
+ cachedAt: Date.now(),
56630
+ }));
56631
+ } catch { /* non-critical */ }
56632
+ }
56633
+
56634
+ async function fetchSettings() {
56635
+ const cached = loadCachedSettings();
56636
+ if (cached) return cached;
56637
+
56638
+ try {
56639
+ const url = \`\${API_URL}/organizations/\${ORG_NAME}/workflow-enforcement\`;
56640
+ const token = process.env.GAL_AUTH_TOKEN || process.env.GAL_API_KEY;
56641
+ const headers = { 'Content-Type': 'application/json' };
56642
+ if (token) headers['Authorization'] = \`Bearer \${token}\`;
56643
+
56644
+ const resp = await fetch(url, { headers, signal: AbortSignal.timeout(5000) });
56645
+ if (!resp.ok) return null;
56646
+ const data = await resp.json();
56647
+ saveCachedSettings(data);
56648
+ return data;
56649
+ } catch { return null; }
56650
+ }
56651
+
56652
+ const rl = readline.createInterface({ input: process.stdin });
56653
+ let input = '';
56654
+ rl.on('line', (line) => { input += line; });
56655
+ rl.on('close', async () => {
56656
+ if (isWorkerSession()) {
56657
+ console.log(JSON.stringify({ decision: 'allow' }));
56658
+ return;
56659
+ }
56660
+
56661
+ let hookInput;
56662
+ try { hookInput = JSON.parse(input); } catch {
56663
+ console.log(JSON.stringify({ decision: 'allow' }));
56664
+ return;
56665
+ }
56666
+
56667
+ const toolName = hookInput.tool_name || '';
56668
+ const toolInput = hookInput.tool_input || {};
56669
+
56670
+ const settings = await fetchSettings();
56671
+ if (!settings || !settings.enabled || settings.mode === 'off') {
56672
+ console.log(JSON.stringify({ decision: 'allow' }));
56673
+ return;
56674
+ }
56675
+
56676
+ if (ALWAYS_ALLOWED.includes(toolName)) {
56677
+ console.log(JSON.stringify({ decision: 'allow' }));
56678
+ return;
56679
+ }
56680
+
56681
+ const customBlocked = settings.blockedTools || BLOCKED_TOOLS;
56682
+ const isBlockedTool = customBlocked.includes(toolName);
56683
+ let isBlockedBash = false;
56684
+ if (toolName === 'Bash') {
56685
+ const cmd = String(toolInput.command || '');
56686
+ isBlockedBash = BLOCKED_BASH_PATTERNS.some((p) => cmd.includes(p));
56687
+ }
56688
+
56689
+ if (!isBlockedTool && !isBlockedBash) {
56690
+ console.log(JSON.stringify({ decision: 'allow' }));
56691
+ return;
56692
+ }
56693
+
56694
+ const msg = (settings.blockMessage || ${JSON.stringify(DEFAULT_BLOCK_MESSAGE)})
56695
+ .replace(/\\{tool\\}/g, toolName)
56696
+ .replace(/\\{org\\}/g, ORG_NAME);
56697
+
56698
+ if (settings.mode === 'warn') {
56699
+ process.stderr.write(\`\\n\\x1b[33m[GAL] WARNING: \${msg}\\x1b[0m\\n\\n\`);
56700
+ console.log(JSON.stringify({ decision: 'allow' }));
56701
+ return;
56702
+ }
56703
+
56704
+ console.log(JSON.stringify({ decision: 'block', reason: msg }));
56705
+ });
56706
+ `;
56707
+ return {
56708
+ platform: "cursor",
56709
+ hookType: "pre_tool_use",
56710
+ filename: "gal-workflow-enforcement.js",
56711
+ content: hookScript,
56712
+ settingsSnippet: {
56713
+ hooks: {
56714
+ pre_tool_use: {
56715
+ command: "node .cursor/hooks/gal-workflow-enforcement.js",
56716
+ timeout: 1e4
56717
+ }
56718
+ }
56719
+ }
56720
+ };
56721
+ }
56722
+ var init_workflow_enforcement_hook = __esm({
56723
+ "src/enforcement/workflow-enforcement-hook.ts"() {
56724
+ "use strict";
56725
+ init_dist2();
56726
+ }
56727
+ });
56728
+
55611
56729
  // src/enforcement/index.ts
55612
56730
  var init_enforcement = __esm({
55613
56731
  "src/enforcement/index.ts"() {
@@ -55618,6 +56736,7 @@ var init_enforcement = __esm({
55618
56736
  init_config_validator();
55619
56737
  init_hook_generator();
55620
56738
  init_system_enforcer();
56739
+ init_workflow_enforcement_hook();
55621
56740
  }
55622
56741
  });
55623
56742
 
@@ -58146,6 +59265,183 @@ Native SDLC enforcement updated to ${level} for ${orgName}.
58146
59265
  }
58147
59266
  });
58148
59267
  enforce.addCommand(sdlcCommand);
59268
+ const workflowCommand = new Command("workflow").description("Manage workflow enforcement mode (background agents only)");
59269
+ workflowCommand.command("status").description("Show the current workflow enforcement mode").option("--json", "Output as JSON").action(async (options) => {
59270
+ const { authToken, orgName, apiUrl } = getAuthAndOrg2();
59271
+ const spinner = options.json ? null : ora("Fetching workflow enforcement settings...").start();
59272
+ try {
59273
+ const res = await fetchApi3(
59274
+ `${apiUrl}/organizations/${encodeURIComponent(orgName)}/workflow-enforcement`,
59275
+ authToken
59276
+ );
59277
+ if (!res.ok) {
59278
+ const data2 = await res.json().catch(() => ({}));
59279
+ throw new Error(data2.error || `HTTP ${res.status}`);
59280
+ }
59281
+ const data = await res.json();
59282
+ spinner?.stop();
59283
+ if (options.json) {
59284
+ console.log(JSON.stringify(data, null, 2));
59285
+ return;
59286
+ }
59287
+ const mode = data.mode || "off";
59288
+ const enabled = data.enabled === true;
59289
+ const modeColor = mode === "background-only" ? source_default.red : mode === "warn" ? source_default.yellow : source_default.dim;
59290
+ console.log(source_default.bold("\nWorkflow Enforcement Settings\n"));
59291
+ console.log(` Organization: ${source_default.cyan(orgName)}`);
59292
+ console.log(` Enabled: ${enabled ? source_default.green("yes") : source_default.dim("no")}`);
59293
+ console.log(` Mode: ${modeColor(mode)}`);
59294
+ if (mode === "background-only") {
59295
+ console.log();
59296
+ console.log(source_default.yellow(" Local sessions are restricted to orchestration only."));
59297
+ console.log(source_default.yellow(" All implementation must go through the background agent queue."));
59298
+ } else if (mode === "warn") {
59299
+ console.log();
59300
+ console.log(source_default.dim(" Warnings are shown for local implementation but not blocked."));
59301
+ }
59302
+ if (data.exemptUsers?.length) {
59303
+ console.log(` Exempt Users: ${data.exemptUsers.join(", ")}`);
59304
+ }
59305
+ if (data.updatedBy) {
59306
+ console.log(` Updated By: ${data.updatedBy}`);
59307
+ }
59308
+ if (data.updatedAt) {
59309
+ console.log(` Updated At: ${data.updatedAt}`);
59310
+ }
59311
+ console.log();
59312
+ } catch (error3) {
59313
+ spinner?.stop();
59314
+ const msg = error3 instanceof Error ? error3.message : String(error3);
59315
+ if (options.json) {
59316
+ console.log(JSON.stringify({ error: msg }));
59317
+ } else {
59318
+ console.error(source_default.red("Error fetching workflow enforcement settings:"), msg);
59319
+ }
59320
+ process.exit(1);
59321
+ }
59322
+ });
59323
+ workflowCommand.command("check").description("Check if a tool call would be allowed under current enforcement policy").requiredOption("--tool <toolName>", "Tool name to check (e.g., Edit, Write, Bash)").option("--command <command>", "Bash command to check (when tool is Bash)").option("--json", "Output as JSON").action(async (options) => {
59324
+ const { authToken, orgName, apiUrl } = getAuthAndOrg2();
59325
+ try {
59326
+ const res = await fetchApi3(
59327
+ `${apiUrl}/organizations/${encodeURIComponent(orgName)}/workflow-enforcement`,
59328
+ authToken
59329
+ );
59330
+ if (!res.ok) {
59331
+ if (options.json) {
59332
+ console.log(JSON.stringify({ allowed: true, mode: "off", reason: "API unavailable \u2014 fail open" }));
59333
+ } else {
59334
+ console.log(source_default.green("\u2713 Allowed (API unavailable \u2014 fail open)"));
59335
+ }
59336
+ return;
59337
+ }
59338
+ const settings = await res.json();
59339
+ const { checkEnforcement: checkEnforcement2 } = await Promise.resolve().then(() => (init_dist2(), dist_exports));
59340
+ const toolInput = {};
59341
+ if (options.command) {
59342
+ toolInput.command = options.command;
59343
+ }
59344
+ const result = checkEnforcement2(
59345
+ options.tool,
59346
+ toolInput,
59347
+ settings,
59348
+ "orchestrator"
59349
+ // CLI is always an orchestrator
59350
+ );
59351
+ if (options.json) {
59352
+ console.log(JSON.stringify(result, null, 2));
59353
+ return;
59354
+ }
59355
+ if (result.allowed && !result.isWarning) {
59356
+ console.log(source_default.green(`\u2713 ${options.tool} is allowed`));
59357
+ } else if (result.isWarning) {
59358
+ console.log(source_default.yellow(`\u26A0 ${options.tool} triggers a warning`));
59359
+ if (result.reason) {
59360
+ console.log(source_default.dim(result.reason));
59361
+ }
59362
+ } else {
59363
+ console.log(source_default.red(`\u2717 ${options.tool} is blocked`));
59364
+ if (result.reason) {
59365
+ console.log(result.reason);
59366
+ }
59367
+ }
59368
+ } catch (error3) {
59369
+ const msg = error3 instanceof Error ? error3.message : String(error3);
59370
+ if (options.json) {
59371
+ console.log(JSON.stringify({ error: msg }));
59372
+ } else {
59373
+ console.error(source_default.red("Error checking enforcement:"), msg);
59374
+ }
59375
+ process.exit(1);
59376
+ }
59377
+ });
59378
+ workflowCommand.command("install").description("Install workflow enforcement hook for Claude Code").option("--path <path>", "Base path (default: current directory)").option("--force", "Overwrite existing hook").option("--dry-run", "Show what would be installed without making changes").action(async (options) => {
59379
+ const spinner = ora();
59380
+ const { orgName, apiUrl } = getAuthAndOrg2();
59381
+ const basePath = options.path || process.cwd();
59382
+ try {
59383
+ spinner.start("Generating workflow enforcement hook...");
59384
+ const { generateWorkflowEnforcementHook: generateWorkflowEnforcementHook2 } = await Promise.resolve().then(() => (init_workflow_enforcement_hook(), workflow_enforcement_hook_exports));
59385
+ const hook = generateWorkflowEnforcementHook2({
59386
+ apiUrl,
59387
+ orgName
59388
+ });
59389
+ const claudeHooksDir = (0, import_node_path3.join)(basePath, ".claude", "hooks");
59390
+ const hookPath = (0, import_node_path3.join)(claudeHooksDir, hook.filename);
59391
+ if (options.dryRun) {
59392
+ spinner.info("[DRY RUN] Would install workflow enforcement hook");
59393
+ console.log(source_default.dim(` Path: ${hookPath}`));
59394
+ console.log(source_default.dim(` API: ${apiUrl}`));
59395
+ console.log(source_default.dim(` Org: ${orgName}`));
59396
+ return;
59397
+ }
59398
+ if ((0, import_node_fs2.existsSync)(hookPath) && !options.force) {
59399
+ spinner.fail("Workflow enforcement hook already exists. Use --force to overwrite.");
59400
+ return;
59401
+ }
59402
+ await (0, import_promises4.mkdir)(claudeHooksDir, { recursive: true });
59403
+ await (0, import_promises4.writeFile)(hookPath, hook.content, "utf-8");
59404
+ await (0, import_promises4.chmod)(hookPath, 493);
59405
+ const settingsPath = (0, import_node_path3.join)(basePath, ".claude", "settings.json");
59406
+ let settings = {};
59407
+ try {
59408
+ const existing2 = await (0, import_promises4.readFile)(settingsPath, "utf-8");
59409
+ settings = JSON.parse(existing2);
59410
+ } catch {
59411
+ settings = {};
59412
+ }
59413
+ const hooks = settings.hooks && typeof settings.hooks === "object" ? { ...settings.hooks } : {};
59414
+ const existing = Array.isArray(hooks.PreToolUse) ? [...hooks.PreToolUse] : [];
59415
+ const filtered = existing.filter((entry) => {
59416
+ if (!entry || typeof entry !== "object") return true;
59417
+ return entry.command !== "python3 .claude/hooks/gal-workflow-enforcement.py";
59418
+ });
59419
+ filtered.push({
59420
+ type: "command",
59421
+ command: "python3 .claude/hooks/gal-workflow-enforcement.py",
59422
+ timeout: 1e4
59423
+ });
59424
+ hooks.PreToolUse = filtered;
59425
+ settings.hooks = hooks;
59426
+ await (0, import_promises4.writeFile)(settingsPath, JSON.stringify(settings, null, 2), "utf-8");
59427
+ spinner.succeed("Installed workflow enforcement hook");
59428
+ console.log(source_default.dim(` Hook: ${hookPath}`));
59429
+ console.log(source_default.dim(` Settings: ${settingsPath}`));
59430
+ console.log();
59431
+ console.log(source_default.blue("The hook will check your org's enforcement mode before each tool call."));
59432
+ if (orgName) {
59433
+ console.log(source_default.dim(` Organization: ${orgName}`));
59434
+ console.log(source_default.dim(` API: ${apiUrl}`));
59435
+ }
59436
+ } catch (error3) {
59437
+ spinner.fail("Failed to install workflow enforcement hook");
59438
+ if (error3 instanceof Error) {
59439
+ console.error(source_default.red(error3.message));
59440
+ }
59441
+ process.exit(1);
59442
+ }
59443
+ });
59444
+ enforce.addCommand(workflowCommand);
58149
59445
  enforce.command("domains").description("Manage domain allowlist and exceptions").option("--json", "Output as JSON").action(async (options) => {
58150
59446
  const { authToken, orgName, apiUrl } = getAuthAndOrg2();
58151
59447
  const spinner = options.json ? null : ora("Fetching domain exceptions...").start();
@@ -66817,6 +68113,23 @@ async function reportDriftStatus(result, projectId, apiUrl, authToken) {
66817
68113
  } catch {
66818
68114
  }
66819
68115
  }
68116
+ function displayQualityScores(qualityScore) {
68117
+ console.log(source_default.bold("Config Quality:"));
68118
+ console.log();
68119
+ const overallPct = (qualityScore.overall * 100).toFixed(0);
68120
+ const overallColor = qualityScore.overall >= 0.7 ? source_default.green : qualityScore.overall >= 0.4 ? source_default.yellow : source_default.red;
68121
+ console.log(` Overall: ${overallColor(overallPct + "%")} ${source_default.dim(`(${qualityScore.totalEvaluations} evaluations)`)}`);
68122
+ if (qualityScore.byPhase) {
68123
+ const phases = Object.entries(qualityScore.byPhase).sort(([a], [b]) => a.localeCompare(b));
68124
+ for (const [phase, data] of phases) {
68125
+ if (!data) continue;
68126
+ const pct = (data.score * 100).toFixed(0);
68127
+ const color = data.score >= 0.7 ? source_default.green : data.score >= 0.4 ? source_default.yellow : source_default.red;
68128
+ console.log(` ${phase.padEnd(15)} ${color(pct + "%")} ${source_default.dim(`(${data.evaluationCount} evals)`)}`);
68129
+ }
68130
+ }
68131
+ console.log();
68132
+ }
66820
68133
  function createStatusCommand3() {
66821
68134
  const command = new Command("status").description("Check sync status, drift, and pending proposals").option("--json", "Output as JSON").option("-d, --directory <path>", "Project directory to check", process.cwd()).option("--check-update", "Check for updates from organization (requires network)").option("--no-report", "Skip reporting drift status to API").action(async (options) => {
66822
68135
  try {
@@ -66876,6 +68189,27 @@ function createStatusCommand3() {
66876
68189
  console.log(source_default.bold(`Config Sync (${source_default.dim(directory)}):`));
66877
68190
  displayStatus(result, proposals);
66878
68191
  }
68192
+ if (health.allHealthy && config2.defaultOrg && config2.apiUrl && authToken) {
68193
+ try {
68194
+ const qualitySpinner = options.json ? null : ora("Checking config quality...").start();
68195
+ const response = await fetch(
68196
+ `${config2.apiUrl}/organizations/${encodeURIComponent(config2.defaultOrg)}/approved-config?platform=claude`,
68197
+ {
68198
+ headers: { Authorization: `Bearer ${authToken}` },
68199
+ signal: AbortSignal.timeout(5e3)
68200
+ }
68201
+ );
68202
+ qualitySpinner?.stop();
68203
+ if (response.ok) {
68204
+ const configData = await response.json();
68205
+ const qualityScore = configData?.qualityScore;
68206
+ if (qualityScore && !options.json) {
68207
+ displayQualityScores(qualityScore);
68208
+ }
68209
+ }
68210
+ } catch {
68211
+ }
68212
+ }
66879
68213
  process.exit(0);
66880
68214
  } catch (error3) {
66881
68215
  const err = error3 instanceof Error ? error3 : new Error(String(error3));
@@ -71815,7 +73149,7 @@ var init_index = __esm({
71815
73149
  });
71816
73150
 
71817
73151
  // src/bootstrap.ts
71818
- var cliVersion10 = true ? "0.0.408" : "0.0.0-dev";
73152
+ var cliVersion10 = true ? "0.0.411" : "0.0.0-dev";
71819
73153
  var args = process.argv.slice(2);
71820
73154
  var requestedGlobalHelp = args.length === 1 && (args[0] === "--help" || args[0] === "-h");
71821
73155
  var requestedVersion = args.length === 1 && (args[0] === "--version" || args[0] === "-V");