@scheduler-systems/gal-run 0.0.372 → 0.0.374

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 +208 -5
  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.372" : "0.0.0-dev";
3973
+ cliVersion = true ? "0.0.374" : "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.372" : void 0;
4883
+ const version2 = true ? "0.0.374" : 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.372" : "0.0.0-dev";
5252
+ return true ? "0.0.374" : "0.0.0-dev";
5253
5253
  } catch {
5254
5254
  return "0.0.0-dev";
5255
5255
  }
@@ -48299,6 +48299,188 @@ function displaySyncedConfigs(syncedItems, orgName, version2, policy) {
48299
48299
  const summaryText = summaryParts.length > 0 ? summaryParts.join(", ") : `${totalItems} file${totalItems > 1 ? "s" : ""}`;
48300
48300
  console.log(source_default.green(`[GAL] `) + source_default.white(`${summaryText} synced from ${orgName} v${version2}`) + policyText);
48301
48301
  }
48302
+ async function pushLearnings(directory, orgName, apiUrl, headers, options) {
48303
+ const spinner = ora("Scanning project for learnings...").start();
48304
+ try {
48305
+ let repoSlug;
48306
+ let sessionId = process.env.CLAUDE_SESSION_ID || `cli-${Date.now()}`;
48307
+ try {
48308
+ const { execSync: execSync14 } = await import("child_process");
48309
+ const remoteUrl = execSync14("git remote get-url origin", { cwd: directory, encoding: "utf-8" }).trim();
48310
+ const match = remoteUrl.match(/github\.com[:/]([^/]+\/[^/]+?)(\.git)?$/);
48311
+ if (match) {
48312
+ repoSlug = match[1];
48313
+ }
48314
+ } catch {
48315
+ }
48316
+ const learningFiles = [];
48317
+ const claudeMdPath = (0, import_path22.join)(directory, ".claude", "CLAUDE.md");
48318
+ if ((0, import_fs23.existsSync)(claudeMdPath)) {
48319
+ learningFiles.push({ path: claudeMdPath, type: "instruction" });
48320
+ }
48321
+ const rootClaudeMdPath = (0, import_path22.join)(directory, "CLAUDE.md");
48322
+ if ((0, import_fs23.existsSync)(rootClaudeMdPath)) {
48323
+ learningFiles.push({ path: rootClaudeMdPath, type: "memory" });
48324
+ }
48325
+ const rulesDir = (0, import_path22.join)(directory, ".claude", "rules");
48326
+ if ((0, import_fs23.existsSync)(rulesDir)) {
48327
+ const ruleFiles = (0, import_fs23.readdirSync)(rulesDir).filter((f) => f.endsWith(".md"));
48328
+ for (const file of ruleFiles) {
48329
+ learningFiles.push({ path: (0, import_path22.join)(rulesDir, file), type: "instruction" });
48330
+ }
48331
+ }
48332
+ const commandsDir = (0, import_path22.join)(directory, ".claude", "commands");
48333
+ if ((0, import_fs23.existsSync)(commandsDir)) {
48334
+ const cmdFiles = (0, import_fs23.readdirSync)(commandsDir).filter((f) => f.endsWith(".md"));
48335
+ for (const file of cmdFiles) {
48336
+ learningFiles.push({ path: (0, import_path22.join)(commandsDir, file), type: "command" });
48337
+ }
48338
+ }
48339
+ spinner.text = `Found ${learningFiles.length} source files, extracting learnings...`;
48340
+ const learnings = [];
48341
+ for (const { path: path8, type } of learningFiles) {
48342
+ const content = (0, import_fs23.readFileSync)(path8, "utf-8");
48343
+ const relPath = path8.replace(directory + "/", "");
48344
+ const learningPattern = /^##\s+(.*(?:Learning|Anti-pattern|Best Practice|CRITICAL).*?)$/i;
48345
+ const hasLearnings = content.match(new RegExp(learningPattern.source, "im"));
48346
+ if (!hasLearnings) continue;
48347
+ const lines = content.split("\n");
48348
+ for (let i = 0; i < lines.length; i++) {
48349
+ const line = lines[i];
48350
+ if (learningPattern.test(line)) {
48351
+ const title = line.replace(/^##\s+/, "").trim();
48352
+ let learningContent = "";
48353
+ let j = i + 1;
48354
+ while (j < lines.length && !lines[j].startsWith("##")) {
48355
+ learningContent += lines[j] + "\n";
48356
+ j++;
48357
+ }
48358
+ if (learningContent.trim().length > 0) {
48359
+ learningContent = sanitizeLearningContent(learningContent);
48360
+ const category = categorizeLearning(title, learningContent);
48361
+ learnings.push({
48362
+ title,
48363
+ content: learningContent.trim(),
48364
+ category,
48365
+ sourceFile: relPath,
48366
+ sourceKind: type === "memory" ? "memory_file" : "instruction_file"
48367
+ });
48368
+ }
48369
+ }
48370
+ }
48371
+ }
48372
+ spinner.succeed(`Extracted ${learnings.length} learnings`);
48373
+ if (learnings.length === 0) {
48374
+ console.log(source_default.yellow("\nNo learnings found to push."));
48375
+ console.log(source_default.dim("Learnings are extracted from sections with headers containing:"));
48376
+ console.log(source_default.dim(' - "Learning", "Anti-pattern", "Best Practice", or "CRITICAL"'));
48377
+ return;
48378
+ }
48379
+ if (options.dryRun) {
48380
+ console.log(source_default.cyan("\n=== DRY RUN - Would publish these learnings ===\n"));
48381
+ for (const learning of learnings) {
48382
+ console.log(source_default.bold(` ${learning.title}`));
48383
+ console.log(source_default.dim(` Category: ${learning.category}`));
48384
+ console.log(source_default.dim(` Source: ${learning.sourceFile}`));
48385
+ console.log(source_default.dim(` Size: ${learning.content.length} chars`));
48386
+ console.log();
48387
+ }
48388
+ return;
48389
+ }
48390
+ const pushSpinner = ora(`Publishing ${learnings.length} learnings to ${orgName}...`).start();
48391
+ let published = 0;
48392
+ let skipped = 0;
48393
+ let errors = 0;
48394
+ for (const learning of learnings) {
48395
+ try {
48396
+ const payload = {
48397
+ sessionId,
48398
+ provider: "cli",
48399
+ repo: repoSlug || "unknown",
48400
+ category: learning.category,
48401
+ title: learning.title,
48402
+ content: learning.content,
48403
+ sourceFile: learning.sourceFile,
48404
+ sourceKind: learning.sourceKind,
48405
+ scope: "organization"
48406
+ };
48407
+ const res = await fetch(
48408
+ `${apiUrl}/api/orgs/${encodeURIComponent(orgName)}/learnings`,
48409
+ {
48410
+ method: "POST",
48411
+ headers,
48412
+ body: JSON.stringify(payload)
48413
+ }
48414
+ );
48415
+ if (res.ok) {
48416
+ published++;
48417
+ } else if (res.status === 409) {
48418
+ skipped++;
48419
+ } else {
48420
+ errors++;
48421
+ const errData = await res.json().catch(() => ({ error: res.statusText }));
48422
+ console.error(source_default.dim(`
48423
+ Failed to publish "${learning.title}": ${errData.error || res.statusText}`));
48424
+ }
48425
+ } catch (err) {
48426
+ errors++;
48427
+ console.error(source_default.dim(`
48428
+ Failed to publish "${learning.title}": ${err instanceof Error ? err.message : String(err)}`));
48429
+ }
48430
+ }
48431
+ pushSpinner.stop();
48432
+ console.log();
48433
+ console.log(source_default.green(`\u2713 Published ${published} learnings to ${orgName}`));
48434
+ if (skipped > 0) {
48435
+ console.log(source_default.dim(` ${skipped} duplicates skipped`));
48436
+ }
48437
+ if (errors > 0) {
48438
+ console.log(source_default.yellow(` ${errors} failed to publish`));
48439
+ }
48440
+ console.log();
48441
+ } catch (error3) {
48442
+ spinner.fail(source_default.red("Failed to push learnings"));
48443
+ throw error3;
48444
+ }
48445
+ }
48446
+ function sanitizeLearningContent(content) {
48447
+ let sanitized = content;
48448
+ sanitized = sanitized.replace(/\/Users\/[^/\s]+/g, "<project>");
48449
+ sanitized = sanitized.replace(/\/home\/[^/\s]+/g, "<project>");
48450
+ sanitized = sanitized.replace(/C:\\Users\\[^\\s]+/g, "<project>");
48451
+ sanitized = sanitized.replace(/(\w+)=([^\s\n]+)/g, (match, name, value) => {
48452
+ if (name.toLowerCase().includes("token") || name.toLowerCase().includes("key") || name.toLowerCase().includes("secret")) {
48453
+ return `${name}=<redacted>`;
48454
+ }
48455
+ return match;
48456
+ });
48457
+ sanitized = sanitized.replace(/[a-zA-Z0-9_-]{20,}/g, (match) => {
48458
+ if (match.match(/^[A-Za-z0-9_-]+$/) && match.length > 30) {
48459
+ return "<redacted>";
48460
+ }
48461
+ return match;
48462
+ });
48463
+ return sanitized;
48464
+ }
48465
+ function categorizeLearning(title, content) {
48466
+ const text = (title + " " + content).toLowerCase();
48467
+ if (text.includes("workflow") || text.includes("ci") || text.includes("pipeline") || text.includes("deploy")) {
48468
+ return "workflow_pattern";
48469
+ }
48470
+ if (text.includes("debug") || text.includes("error") || text.includes("fix") || text.includes("fail")) {
48471
+ return "error_resolution";
48472
+ }
48473
+ if (text.includes("architecture") || text.includes("pattern") || text.includes("design")) {
48474
+ return "architectural_decision";
48475
+ }
48476
+ if (text.includes("tool") || text.includes("config") || text.includes("setup") || text.includes("install")) {
48477
+ return "tool_configuration";
48478
+ }
48479
+ if (text.includes("repo") || text.includes("project") || text.includes("codebase")) {
48480
+ return "repo_pattern";
48481
+ }
48482
+ return "repo_pattern";
48483
+ }
48302
48484
  function createSyncCommand() {
48303
48485
  const command = new Command("sync").description("Sync local configs with CISO-approved organization standard");
48304
48486
  const statusCmd = command.command("status").description("Check current sync status (for extension integration)").option("--json", "Output as JSON").option("-p, --platform <platform>", "Filter by platform (claude, cursor, etc.)");
@@ -48403,7 +48585,7 @@ function createSyncCommand() {
48403
48585
  }
48404
48586
  console.log();
48405
48587
  });
48406
- command.argument("[orgName]", "Organization name (uses default if not specified)").option("-d, --directory <path>", "Local config directory (auto-detects project root)", process.cwd()).option("-p, --platform <platform>", "Filter by platform (claude, cursor, etc.)").option("--pull", "Download CISO-approved config to local directory").option("--repo <name>", "Personal repo to sync from (for users without org)").option("--check", "Check if local files match approved config (for hooks)").option("--regenerate", "Regenerate native configs from canonical .gal/ directory (offline)").option("--auto", "Non-interactive mode for hooks/scripts (no prompts, exit cleanly)").option("--output-json", "Output as JSON").option("--target <target>", "Sync specific target: configs (default), domains (domain allowlist)").action(async (orgName, options) => {
48588
+ command.argument("[orgName]", "Organization name (uses default if not specified)").option("-d, --directory <path>", "Local config directory (auto-detects project root)", process.cwd()).option("-p, --platform <platform>", "Filter by platform (claude, cursor, etc.)").option("--pull", "Download CISO-approved config to local directory").option("--push", "Push session learnings to organization (capture learning pipeline)").option("--repo <name>", "Personal repo to sync from (for users without org)").option("--check", "Check if local files match approved config (for hooks)").option("--regenerate", "Regenerate native configs from canonical .gal/ directory (offline)").option("--auto", "Non-interactive mode for hooks/scripts (no prompts, exit cleanly)").option("--output-json", "Output as JSON").option("--dry-run", "Preview what would be pushed without making API calls").option("--target <target>", "Sync specific target: configs (default), domains (domain allowlist)").action(async (orgName, options) => {
48407
48589
  try {
48408
48590
  const directory = findProjectRoot(options.directory);
48409
48591
  if (options.regenerate) {
@@ -48607,6 +48789,27 @@ function createSyncCommand() {
48607
48789
  const provider = new CoreServiceProvider({ apiUrl, authToken: config2.authToken });
48608
48790
  const configRepo = provider.getConfigRepository();
48609
48791
  const authRepo = provider.getAuthRepository();
48792
+ if (options.push) {
48793
+ const pushOrg = orgName || config2.defaultOrg;
48794
+ if (!pushOrg) {
48795
+ console.error(source_default.red("Error: Organization not specified"));
48796
+ console.error(source_default.dim("Run: gal sync <orgName> --push"));
48797
+ console.error(source_default.dim("Or: gal config set defaultOrg <orgName>"));
48798
+ process.exit(1);
48799
+ }
48800
+ const headers = {
48801
+ "Accept": "application/json",
48802
+ "Content-Type": "application/json"
48803
+ };
48804
+ if (config2.authToken) {
48805
+ headers["Authorization"] = `Bearer ${config2.authToken}`;
48806
+ }
48807
+ await pushLearnings(directory, pushOrg, apiUrl, headers, {
48808
+ dryRun: options.dryRun,
48809
+ platform: options.platform
48810
+ });
48811
+ process.exit(0);
48812
+ }
48610
48813
  if (options.repo && options.pull) {
48611
48814
  await pullPersonalRepoConfig(authRepo, options.repo, options.directory);
48612
48815
  process.exit(0);
@@ -70559,7 +70762,7 @@ var init_index = __esm({
70559
70762
  });
70560
70763
 
70561
70764
  // src/bootstrap.ts
70562
- var cliVersion10 = true ? "0.0.372" : "0.0.0-dev";
70765
+ var cliVersion10 = true ? "0.0.374" : "0.0.0-dev";
70563
70766
  var args = process.argv.slice(2);
70564
70767
  var requestedGlobalHelp = args.length === 1 && (args[0] === "--help" || args[0] === "-h");
70565
70768
  var requestedVersion = args.length === 1 && (args[0] === "--version" || args[0] === "-V");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scheduler-systems/gal-run",
3
- "version": "0.0.372",
3
+ "version": "0.0.374",
4
4
  "description": "GAL CLI - Command-line tool for managing AI agent configurations across your organization",
5
5
  "license": "Elastic-2.0",
6
6
  "private": false,