@packmind/cli 0.4.0 → 0.5.0

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/main.cjs +968 -136
  2. package/package.json +1 -1
package/main.cjs CHANGED
@@ -38,7 +38,7 @@ var require_package = __commonJS({
38
38
  "apps/cli/package.json"(exports2, module2) {
39
39
  module2.exports = {
40
40
  name: "@packmind/cli",
41
- version: "0.4.0",
41
+ version: "0.5.0",
42
42
  description: "A command-line interface for Packmind linting and code quality checks",
43
43
  private: false,
44
44
  bin: {
@@ -259,38 +259,38 @@ var createRuleId = brandedIdFactory();
259
259
  var createRuleExampleId = brandedIdFactory();
260
260
 
261
261
  // packages/types/src/languages/ProgrammingLanguage.ts
262
- var ProgrammingLanguage = /* @__PURE__ */ ((ProgrammingLanguage3) => {
263
- ProgrammingLanguage3["JAVASCRIPT"] = "JAVASCRIPT";
264
- ProgrammingLanguage3["JAVASCRIPT_JSX"] = "JAVASCRIPT_JSX";
265
- ProgrammingLanguage3["TYPESCRIPT"] = "TYPESCRIPT";
266
- ProgrammingLanguage3["TYPESCRIPT_TSX"] = "TYPESCRIPT_TSX";
267
- ProgrammingLanguage3["PYTHON"] = "PYTHON";
268
- ProgrammingLanguage3["PHP"] = "PHP";
269
- ProgrammingLanguage3["JAVA"] = "JAVA";
270
- ProgrammingLanguage3["SCSS"] = "SCSS";
271
- ProgrammingLanguage3["HTML"] = "HTML";
272
- ProgrammingLanguage3["CSHARP"] = "CSHARP";
273
- ProgrammingLanguage3["GENERIC"] = "GENERIC";
274
- ProgrammingLanguage3["GO"] = "GO";
275
- ProgrammingLanguage3["C"] = "C";
276
- ProgrammingLanguage3["CPP"] = "CPP";
277
- ProgrammingLanguage3["SQL"] = "SQL";
278
- ProgrammingLanguage3["KOTLIN"] = "KOTLIN";
279
- ProgrammingLanguage3["VUE"] = "VUE";
280
- ProgrammingLanguage3["CSS"] = "CSS";
281
- ProgrammingLanguage3["YAML"] = "YAML";
282
- ProgrammingLanguage3["JSON"] = "JSON";
283
- ProgrammingLanguage3["XML"] = "XML";
284
- ProgrammingLanguage3["BASH"] = "BASH";
285
- ProgrammingLanguage3["MARKDOWN"] = "MARKDOWN";
286
- ProgrammingLanguage3["RUBY"] = "RUBY";
287
- ProgrammingLanguage3["RUST"] = "RUST";
288
- ProgrammingLanguage3["SAP_ABAP"] = "SAP_ABAP";
289
- ProgrammingLanguage3["SAP_CDS"] = "SAP_CDS";
290
- ProgrammingLanguage3["SAP_HANA_SQL"] = "SAP_HANA_SQL";
291
- ProgrammingLanguage3["SWIFT"] = "SWIFT";
292
- ProgrammingLanguage3["PROPERTIES"] = "PROPERTIES";
293
- return ProgrammingLanguage3;
262
+ var ProgrammingLanguage = /* @__PURE__ */ ((ProgrammingLanguage4) => {
263
+ ProgrammingLanguage4["JAVASCRIPT"] = "JAVASCRIPT";
264
+ ProgrammingLanguage4["JAVASCRIPT_JSX"] = "JAVASCRIPT_JSX";
265
+ ProgrammingLanguage4["TYPESCRIPT"] = "TYPESCRIPT";
266
+ ProgrammingLanguage4["TYPESCRIPT_TSX"] = "TYPESCRIPT_TSX";
267
+ ProgrammingLanguage4["PYTHON"] = "PYTHON";
268
+ ProgrammingLanguage4["PHP"] = "PHP";
269
+ ProgrammingLanguage4["JAVA"] = "JAVA";
270
+ ProgrammingLanguage4["SCSS"] = "SCSS";
271
+ ProgrammingLanguage4["HTML"] = "HTML";
272
+ ProgrammingLanguage4["CSHARP"] = "CSHARP";
273
+ ProgrammingLanguage4["GENERIC"] = "GENERIC";
274
+ ProgrammingLanguage4["GO"] = "GO";
275
+ ProgrammingLanguage4["C"] = "C";
276
+ ProgrammingLanguage4["CPP"] = "CPP";
277
+ ProgrammingLanguage4["SQL"] = "SQL";
278
+ ProgrammingLanguage4["KOTLIN"] = "KOTLIN";
279
+ ProgrammingLanguage4["VUE"] = "VUE";
280
+ ProgrammingLanguage4["CSS"] = "CSS";
281
+ ProgrammingLanguage4["YAML"] = "YAML";
282
+ ProgrammingLanguage4["JSON"] = "JSON";
283
+ ProgrammingLanguage4["XML"] = "XML";
284
+ ProgrammingLanguage4["BASH"] = "BASH";
285
+ ProgrammingLanguage4["MARKDOWN"] = "MARKDOWN";
286
+ ProgrammingLanguage4["RUBY"] = "RUBY";
287
+ ProgrammingLanguage4["RUST"] = "RUST";
288
+ ProgrammingLanguage4["SAP_ABAP"] = "SAP_ABAP";
289
+ ProgrammingLanguage4["SAP_CDS"] = "SAP_CDS";
290
+ ProgrammingLanguage4["SAP_HANA_SQL"] = "SAP_HANA_SQL";
291
+ ProgrammingLanguage4["SWIFT"] = "SWIFT";
292
+ ProgrammingLanguage4["PROPERTIES"] = "PROPERTIES";
293
+ return ProgrammingLanguage4;
294
294
  })(ProgrammingLanguage || {});
295
295
  var ProgrammingLanguageDetails = {
296
296
  ["GENERIC" /* GENERIC */]: {
@@ -458,6 +458,262 @@ var createDetectionProgramId = brandedIdFactory();
458
458
  // packages/types/src/linter/RuleDetectionAssessment.ts
459
459
  var createRuleDetectionAssessmentId = brandedIdFactory();
460
460
 
461
+ // packages/types/src/llm/LLMProviderMetadata.ts
462
+ var DEFAULT_OPENAI_MODELS = {
463
+ model: "gpt-5.1",
464
+ fastestModel: "gpt-4.1-mini"
465
+ };
466
+ var DEFAULT_ANTHROPIC_MODELS = {
467
+ model: "claude-sonnet-4-5-20250929",
468
+ fastestModel: "claude-haiku-4-5-20251001"
469
+ };
470
+ var DEFAULT_GEMINI_MODELS = {
471
+ model: "gemini-3-pro-preview",
472
+ fastestModel: "gemini-2.5-flash"
473
+ };
474
+ var DEFAULT_AZURE_OPENAI_API_VERSION = "2024-12-01-preview";
475
+ var LLM_PROVIDER_METADATA = {
476
+ ["openai" /* OPENAI */]: {
477
+ id: "openai" /* OPENAI */,
478
+ displayName: "OpenAI",
479
+ description: "OpenAI GPT models including GPT-4 and GPT-5. Requires an API key from OpenAI.",
480
+ defaultModel: DEFAULT_OPENAI_MODELS.model,
481
+ defaultFastModel: DEFAULT_OPENAI_MODELS.fastestModel,
482
+ documentationUrl: "https://platform.openai.com/docs",
483
+ fields: [
484
+ {
485
+ name: "apiKey",
486
+ label: "API Key",
487
+ type: "password",
488
+ defaultValue: "",
489
+ helpMessage: "Your OpenAI API key from platform.openai.com.",
490
+ optional: false,
491
+ placeholder: "sk-...",
492
+ secret: true
493
+ },
494
+ {
495
+ name: "model",
496
+ label: "Model",
497
+ type: "text",
498
+ defaultValue: DEFAULT_OPENAI_MODELS.model,
499
+ helpMessage: "The primary model to use for standard operations. Defaults to the latest recommended model.",
500
+ optional: true,
501
+ placeholder: "gpt-5.1",
502
+ secret: false
503
+ },
504
+ {
505
+ name: "fastestModel",
506
+ label: "Fast Model",
507
+ type: "text",
508
+ defaultValue: DEFAULT_OPENAI_MODELS.fastestModel,
509
+ helpMessage: "A faster, more economical model for less complex operations.",
510
+ optional: true,
511
+ placeholder: "gpt-4.1-mini",
512
+ secret: false
513
+ }
514
+ ]
515
+ },
516
+ ["anthropic" /* ANTHROPIC */]: {
517
+ id: "anthropic" /* ANTHROPIC */,
518
+ displayName: "Anthropic Claude",
519
+ description: "Anthropic Claude models known for safety and helpfulness. Requires an API key from Anthropic.",
520
+ defaultModel: DEFAULT_ANTHROPIC_MODELS.model,
521
+ defaultFastModel: DEFAULT_ANTHROPIC_MODELS.fastestModel,
522
+ documentationUrl: "https://docs.anthropic.com",
523
+ fields: [
524
+ {
525
+ name: "apiKey",
526
+ label: "API Key",
527
+ type: "password",
528
+ defaultValue: "",
529
+ helpMessage: "Your Anthropic API key from console.anthropic.com.",
530
+ optional: false,
531
+ placeholder: "sk-ant-...",
532
+ secret: true
533
+ },
534
+ {
535
+ name: "model",
536
+ label: "Model",
537
+ type: "text",
538
+ defaultValue: DEFAULT_ANTHROPIC_MODELS.model,
539
+ helpMessage: "The primary Claude model to use. Defaults to the latest Sonnet model.",
540
+ optional: true,
541
+ placeholder: "claude-sonnet-4-5-20250929",
542
+ secret: false
543
+ },
544
+ {
545
+ name: "fastestModel",
546
+ label: "Fast Model",
547
+ type: "text",
548
+ defaultValue: DEFAULT_ANTHROPIC_MODELS.fastestModel,
549
+ helpMessage: "A faster Claude model for less complex operations. Defaults to Haiku.",
550
+ optional: true,
551
+ placeholder: "claude-haiku-4-5-20251001",
552
+ secret: false
553
+ }
554
+ ]
555
+ },
556
+ ["gemini" /* GEMINI */]: {
557
+ id: "gemini" /* GEMINI */,
558
+ displayName: "Google Gemini",
559
+ description: "Google's Gemini models with multimodal capabilities. Requires an API key from Google AI Studio.",
560
+ defaultModel: DEFAULT_GEMINI_MODELS.model,
561
+ defaultFastModel: DEFAULT_GEMINI_MODELS.fastestModel,
562
+ documentationUrl: "https://ai.google.dev/docs",
563
+ fields: [
564
+ {
565
+ name: "apiKey",
566
+ label: "API Key",
567
+ type: "password",
568
+ defaultValue: "",
569
+ helpMessage: "Your Google AI API key from aistudio.google.com.",
570
+ optional: false,
571
+ placeholder: "AIza...",
572
+ secret: true
573
+ },
574
+ {
575
+ name: "model",
576
+ label: "Model",
577
+ type: "text",
578
+ defaultValue: DEFAULT_GEMINI_MODELS.model,
579
+ helpMessage: "The primary Gemini model to use. Defaults to the latest Pro model.",
580
+ optional: true,
581
+ placeholder: "gemini-3-pro-preview",
582
+ secret: false
583
+ },
584
+ {
585
+ name: "fastestModel",
586
+ label: "Fast Model",
587
+ type: "text",
588
+ defaultValue: DEFAULT_GEMINI_MODELS.fastestModel,
589
+ helpMessage: "A faster Gemini model for less complex operations. Defaults to Flash.",
590
+ optional: true,
591
+ placeholder: "gemini-2.5-flash",
592
+ secret: false
593
+ }
594
+ ]
595
+ },
596
+ ["azure-openai" /* AZURE_OPENAI */]: {
597
+ id: "azure-openai" /* AZURE_OPENAI */,
598
+ displayName: "Azure OpenAI",
599
+ description: "Microsoft Azure-hosted OpenAI models. Requires Azure deployment names and credentials.",
600
+ defaultModel: "",
601
+ defaultFastModel: "",
602
+ documentationUrl: "https://learn.microsoft.com/en-us/azure/ai-services/openai/",
603
+ fields: [
604
+ {
605
+ name: "model",
606
+ label: "Model Deployment Name",
607
+ type: "text",
608
+ defaultValue: "",
609
+ helpMessage: "The Azure deployment name for the primary model. This is the name you gave your deployment in Azure Portal.",
610
+ optional: false,
611
+ placeholder: "my-gpt-4-deployment",
612
+ secret: false
613
+ },
614
+ {
615
+ name: "fastestModel",
616
+ label: "Fast Model Deployment Name",
617
+ type: "text",
618
+ defaultValue: "",
619
+ helpMessage: "The Azure deployment name for the fast/economical model.",
620
+ optional: false,
621
+ placeholder: "my-gpt-35-turbo-deployment",
622
+ secret: false
623
+ },
624
+ {
625
+ name: "endpoint",
626
+ label: "Endpoint URL",
627
+ type: "url",
628
+ defaultValue: "",
629
+ helpMessage: "Your Azure OpenAI endpoint URL.",
630
+ optional: false,
631
+ placeholder: "https://your-resource.openai.azure.com",
632
+ secret: false
633
+ },
634
+ {
635
+ name: "apiKey",
636
+ label: "API Key",
637
+ type: "password",
638
+ defaultValue: "",
639
+ helpMessage: "Your Azure OpenAI API key.",
640
+ optional: false,
641
+ placeholder: "",
642
+ secret: true
643
+ },
644
+ {
645
+ name: "apiVersion",
646
+ label: "API Version",
647
+ type: "text",
648
+ defaultValue: DEFAULT_AZURE_OPENAI_API_VERSION,
649
+ helpMessage: "The Azure OpenAI API version to use. Defaults to the latest stable version.",
650
+ optional: true,
651
+ placeholder: "2024-12-01-preview",
652
+ secret: false
653
+ }
654
+ ]
655
+ },
656
+ ["openai-compatible" /* OPENAI_COMPATIBLE */]: {
657
+ id: "openai-compatible" /* OPENAI_COMPATIBLE */,
658
+ displayName: "OpenAI-Compatible",
659
+ description: "Any OpenAI-compatible API endpoint. Use this for local models (Ollama, LM Studio) or other compatible providers.",
660
+ defaultModel: "",
661
+ defaultFastModel: "",
662
+ documentationUrl: void 0,
663
+ fields: [
664
+ {
665
+ name: "llmEndpoint",
666
+ label: "Endpoint URL",
667
+ type: "url",
668
+ defaultValue: "",
669
+ helpMessage: "The base URL of the OpenAI-compatible API endpoint (e.g., http://localhost:11434/v1 for Ollama).",
670
+ optional: false,
671
+ placeholder: "http://localhost:11434/v1",
672
+ secret: false
673
+ },
674
+ {
675
+ name: "llmApiKey",
676
+ label: "API Key",
677
+ type: "password",
678
+ defaultValue: "",
679
+ helpMessage: "API key for authentication. Some local providers may not require this.",
680
+ optional: false,
681
+ placeholder: "",
682
+ secret: true
683
+ },
684
+ {
685
+ name: "model",
686
+ label: "Model",
687
+ type: "text",
688
+ defaultValue: "",
689
+ helpMessage: "The model identifier to use for standard operations (e.g., llama3, mistral).",
690
+ optional: false,
691
+ placeholder: "llama3",
692
+ secret: false
693
+ },
694
+ {
695
+ name: "fastestModel",
696
+ label: "Fast Model",
697
+ type: "text",
698
+ defaultValue: "",
699
+ helpMessage: "A faster model for less complex operations. Can be the same as the primary model.",
700
+ optional: false,
701
+ placeholder: "llama3",
702
+ secret: false
703
+ }
704
+ ]
705
+ },
706
+ ["packmind" /* PACKMIND */]: {
707
+ id: "packmind" /* PACKMIND */,
708
+ displayName: "Packmind (SaaS)",
709
+ description: "Packmind managed LLM service. Uses the platform default provider configuration.",
710
+ defaultModel: DEFAULT_OPENAI_MODELS.model,
711
+ defaultFastModel: DEFAULT_OPENAI_MODELS.fastestModel,
712
+ documentationUrl: void 0,
713
+ fields: []
714
+ }
715
+ };
716
+
461
717
  // packages/types/src/sse/SSEEvent.ts
462
718
  function createProgramStatusChangeEvent(ruleId, language) {
463
719
  return {
@@ -543,27 +799,34 @@ var GitService = class {
543
799
  constructor(logger2 = new PackmindLogger(origin)) {
544
800
  this.logger = logger2;
545
801
  }
546
- async getGitRepositoryRoot(path5) {
802
+ async getGitRepositoryRoot(path7) {
547
803
  try {
548
804
  const { stdout } = await execAsync("git rev-parse --show-toplevel", {
549
- cwd: path5
805
+ cwd: path7
550
806
  });
551
807
  const gitRoot = stdout.trim();
552
808
  this.logger.debug("Resolved git repository root", {
553
- inputPath: path5,
809
+ inputPath: path7,
554
810
  gitRoot
555
811
  });
556
812
  return gitRoot;
557
813
  } catch (error) {
558
814
  if (error instanceof Error) {
559
815
  throw new Error(
560
- `Failed to get Git repository root. The path '${path5}' does not appear to be inside a Git repository.
816
+ `Failed to get Git repository root. The path '${path7}' does not appear to be inside a Git repository.
561
817
  ${error.message}`
562
818
  );
563
819
  }
564
820
  throw new Error("Failed to get Git repository root: Unknown error");
565
821
  }
566
822
  }
823
+ async tryGetGitRepositoryRoot(path7) {
824
+ try {
825
+ return await this.getGitRepositoryRoot(path7);
826
+ } catch {
827
+ return null;
828
+ }
829
+ }
567
830
  async getCurrentBranches(repoPath) {
568
831
  try {
569
832
  const { stdout } = await execAsync("git branch -a --contains HEAD", {
@@ -581,7 +844,7 @@ ${error.message}`
581
844
  throw new Error("Failed to get Git branches: Unknown error");
582
845
  }
583
846
  }
584
- async getGitRemoteUrl(repoPath, origin10) {
847
+ async getGitRemoteUrl(repoPath, origin11) {
585
848
  try {
586
849
  const { stdout } = await execAsync("git remote -v", {
587
850
  cwd: repoPath
@@ -591,10 +854,10 @@ ${error.message}`
591
854
  throw new Error("No Git remotes found in the repository");
592
855
  }
593
856
  let selectedRemote;
594
- if (origin10) {
595
- const foundRemote = remotes.find((remote) => remote.name === origin10);
857
+ if (origin11) {
858
+ const foundRemote = remotes.find((remote) => remote.name === origin11);
596
859
  if (!foundRemote) {
597
- throw new Error(`Remote '${origin10}' not found in repository`);
860
+ throw new Error(`Remote '${origin11}' not found in repository`);
598
861
  }
599
862
  selectedRemote = foundRemote.url;
600
863
  } else if (remotes.length === 1) {
@@ -664,14 +927,14 @@ ${error.message}`
664
927
  normalizeGitUrl(url) {
665
928
  const sshMatch = url.match(/^git@([^:]+):(.+)$/);
666
929
  if (sshMatch) {
667
- const [, host, path5] = sshMatch;
668
- const cleanPath = path5.replace(/\.git$/, "");
930
+ const [, host, path7] = sshMatch;
931
+ const cleanPath = path7.replace(/\.git$/, "");
669
932
  return `${host}/${cleanPath}`;
670
933
  }
671
934
  const httpsMatch = url.match(/^https?:\/\/([^/]+)\/(.+)$/);
672
935
  if (httpsMatch) {
673
- const [, host, path5] = httpsMatch;
674
- const cleanPath = path5.replace(/\.git$/, "");
936
+ const [, host, path7] = httpsMatch;
937
+ const cleanPath = path7.replace(/\.git$/, "");
675
938
  return `${host}/${cleanPath}`;
676
939
  }
677
940
  return url;
@@ -684,8 +947,8 @@ var GetGitRemoteUrlUseCase = class {
684
947
  this.gitRemoteUrlService = gitRemoteUrlService;
685
948
  }
686
949
  async execute(command3) {
687
- const { path: repoPath, origin: origin10 } = command3;
688
- return this.gitRemoteUrlService.getGitRemoteUrl(repoPath, origin10);
950
+ const { path: repoPath, origin: origin11 } = command3;
951
+ return this.gitRemoteUrlService.getGitRemoteUrl(repoPath, origin11);
689
952
  }
690
953
  };
691
954
 
@@ -1150,6 +1413,224 @@ var LintFilesInDirectoryUseCase = class {
1150
1413
  }
1151
1414
  };
1152
1415
 
1416
+ // apps/cli/src/application/useCases/LintFilesLocallyUseCase.ts
1417
+ var import_minimatch2 = require("minimatch");
1418
+ var path3 = __toESM(require("path"));
1419
+ var fs3 = __toESM(require("fs/promises"));
1420
+ var origin3 = "LintFilesLocallyUseCase";
1421
+ var LintFilesLocallyUseCase = class {
1422
+ constructor(services, repositories, logger2 = new PackmindLogger(origin3)) {
1423
+ this.services = services;
1424
+ this.repositories = repositories;
1425
+ this.logger = logger2;
1426
+ }
1427
+ fileMatchesTargetAndScope(filePath, targetPath, scopePatterns) {
1428
+ if (!scopePatterns || scopePatterns.length === 0) {
1429
+ const effectivePattern = this.buildEffectivePattern(targetPath, null);
1430
+ return (0, import_minimatch2.minimatch)(filePath, effectivePattern, { matchBase: false });
1431
+ }
1432
+ return scopePatterns.some((scopePattern) => {
1433
+ const effectivePattern = this.buildEffectivePattern(
1434
+ targetPath,
1435
+ scopePattern
1436
+ );
1437
+ return (0, import_minimatch2.minimatch)(filePath, effectivePattern, { matchBase: false });
1438
+ });
1439
+ }
1440
+ buildEffectivePattern(targetPath, scope) {
1441
+ const normalizedTarget = targetPath === "/" ? "/" : targetPath.replace(/\/$/, "");
1442
+ if (!scope) {
1443
+ return normalizedTarget === "/" ? "/**" : normalizedTarget + "/**";
1444
+ }
1445
+ if (scope.startsWith(normalizedTarget + "/") || scope === normalizedTarget) {
1446
+ return scope.endsWith("/") ? scope + "**" : scope;
1447
+ }
1448
+ const cleanScope = scope.startsWith("/") ? scope.substring(1) : scope;
1449
+ let pattern;
1450
+ if (normalizedTarget === "/") {
1451
+ pattern = "/" + cleanScope;
1452
+ } else {
1453
+ pattern = normalizedTarget + "/" + cleanScope;
1454
+ }
1455
+ if (pattern.endsWith("/")) {
1456
+ pattern = pattern + "**";
1457
+ }
1458
+ return pattern;
1459
+ }
1460
+ async execute(command3) {
1461
+ const { path: userPath } = command3;
1462
+ this.logger.debug(`Starting local linting: path="${userPath}"`);
1463
+ const absoluteUserPath = path3.isAbsolute(userPath) ? userPath : path3.resolve(process.cwd(), userPath);
1464
+ let pathStats;
1465
+ try {
1466
+ pathStats = await fs3.stat(absoluteUserPath);
1467
+ } catch {
1468
+ throw new Error(
1469
+ `The path "${absoluteUserPath}" does not exist or cannot be accessed`
1470
+ );
1471
+ }
1472
+ const isFile = pathStats.isFile();
1473
+ const directoryForConfig = isFile ? path3.dirname(absoluteUserPath) : absoluteUserPath;
1474
+ const gitRepoRoot = await this.services.gitRemoteUrlService.tryGetGitRepositoryRoot(
1475
+ directoryForConfig
1476
+ );
1477
+ const hierarchicalConfig = await this.repositories.configFileRepository.readHierarchicalConfig(
1478
+ directoryForConfig,
1479
+ gitRepoRoot
1480
+ );
1481
+ if (!hierarchicalConfig.hasConfigs) {
1482
+ const boundary = gitRepoRoot ?? "filesystem root";
1483
+ throw new Error(
1484
+ `No packmind.json found between ${directoryForConfig} and ${boundary}. Cannot use local linting.`
1485
+ );
1486
+ }
1487
+ const basePath = gitRepoRoot ?? directoryForConfig;
1488
+ this.logger.debug(
1489
+ `Found ${hierarchicalConfig.configPaths.length} packmind.json file(s)`
1490
+ );
1491
+ for (const configPath of hierarchicalConfig.configPaths) {
1492
+ this.logger.debug(`Using config: ${configPath}`);
1493
+ }
1494
+ const packageSlugs = Object.keys(hierarchicalConfig.packages);
1495
+ this.logger.debug(
1496
+ `Merged ${packageSlugs.length} packages from configuration files`
1497
+ );
1498
+ const detectionPrograms = await this.repositories.packmindGateway.getDetectionProgramsForPackages({
1499
+ packagesSlugs: packageSlugs
1500
+ });
1501
+ this.logger.debug(
1502
+ `Retrieved detection programs: targetsCount=${detectionPrograms.targets.length}`
1503
+ );
1504
+ const files = isFile ? [{ path: absoluteUserPath }] : await this.services.listFiles.listFilesInDirectory(
1505
+ absoluteUserPath,
1506
+ [],
1507
+ ["node_modules", "dist", ".min.", ".map.", ".git"]
1508
+ );
1509
+ this.logger.debug(`Found ${files.length} files to lint`);
1510
+ const violations = [];
1511
+ for (const file of files) {
1512
+ const fileViolations = [];
1513
+ const relativeFilePath = file.path.startsWith(basePath) ? file.path.substring(basePath.length) : file.path;
1514
+ const normalizedFilePath = relativeFilePath.startsWith("/") ? relativeFilePath : "/" + relativeFilePath;
1515
+ this.logger.debug(
1516
+ `Processing file: absolute="${file.path}", relative="${normalizedFilePath}"`
1517
+ );
1518
+ const fileExtension = this.extractExtensionFromFile(file.path);
1519
+ const fileLanguage = this.resolveProgrammingLanguage(fileExtension);
1520
+ if (!fileLanguage) {
1521
+ continue;
1522
+ }
1523
+ const programsByLanguage = /* @__PURE__ */ new Map();
1524
+ for (const target of detectionPrograms.targets) {
1525
+ for (const standard of target.standards) {
1526
+ if (!this.fileMatchesTargetAndScope(
1527
+ normalizedFilePath,
1528
+ target.path,
1529
+ standard.scope
1530
+ )) {
1531
+ continue;
1532
+ }
1533
+ for (const rule of standard.rules) {
1534
+ for (const activeProgram of rule.activeDetectionPrograms) {
1535
+ try {
1536
+ const programLanguage = this.resolveProgrammingLanguage(
1537
+ activeProgram.language
1538
+ );
1539
+ if (!programLanguage || programLanguage !== fileLanguage) {
1540
+ continue;
1541
+ }
1542
+ const programsForLanguage = programsByLanguage.get(programLanguage) ?? [];
1543
+ programsForLanguage.push({
1544
+ code: activeProgram.detectionProgram.code,
1545
+ ruleContent: rule.content,
1546
+ standardSlug: standard.slug,
1547
+ sourceCodeState: activeProgram.detectionProgram.sourceCodeState,
1548
+ language: fileLanguage
1549
+ });
1550
+ programsByLanguage.set(programLanguage, programsForLanguage);
1551
+ } catch (error) {
1552
+ console.error(
1553
+ `Error preparing program for file ${file.path}: ${error}`
1554
+ );
1555
+ }
1556
+ }
1557
+ }
1558
+ }
1559
+ }
1560
+ if (programsByLanguage.size > 0) {
1561
+ try {
1562
+ const fileContent = await this.services.listFiles.readFileContent(
1563
+ file.path
1564
+ );
1565
+ for (const [language, programs] of programsByLanguage.entries()) {
1566
+ try {
1567
+ const result = await this.executeProgramsForFile({
1568
+ filePath: file.path,
1569
+ fileContent,
1570
+ language,
1571
+ programs
1572
+ });
1573
+ fileViolations.push(...result);
1574
+ } catch (error) {
1575
+ console.error(
1576
+ `Error executing programs for file ${file.path} (${language}): ${error}`
1577
+ );
1578
+ }
1579
+ }
1580
+ } catch (error) {
1581
+ console.error(
1582
+ `Error reading file content for ${file.path}: ${error}`
1583
+ );
1584
+ }
1585
+ }
1586
+ if (fileViolations.length > 0) {
1587
+ violations.push({
1588
+ file: file.path,
1589
+ violations: fileViolations
1590
+ });
1591
+ }
1592
+ }
1593
+ const totalViolations = violations.reduce(
1594
+ (sum, violation) => sum + violation.violations.length,
1595
+ 0
1596
+ );
1597
+ const standardsChecked = Array.from(
1598
+ new Set(
1599
+ detectionPrograms.targets.flatMap(
1600
+ (target) => target.standards.map((standard) => standard.slug)
1601
+ )
1602
+ )
1603
+ );
1604
+ return {
1605
+ violations,
1606
+ summary: {
1607
+ totalFiles: files.length,
1608
+ violatedFiles: violations.length,
1609
+ totalViolations,
1610
+ standardsChecked
1611
+ }
1612
+ };
1613
+ }
1614
+ resolveProgrammingLanguage(language) {
1615
+ try {
1616
+ return stringToProgrammingLanguage(language);
1617
+ } catch {
1618
+ return null;
1619
+ }
1620
+ }
1621
+ async executeProgramsForFile(command3) {
1622
+ const result = await this.services.linterExecutionUseCase.execute(command3);
1623
+ return result.violations;
1624
+ }
1625
+ extractExtensionFromFile(filePath) {
1626
+ const lastDotIndex = filePath.lastIndexOf(".");
1627
+ if (lastDotIndex === -1 || lastDotIndex === filePath.length - 1) {
1628
+ return "";
1629
+ }
1630
+ return filePath.substring(lastDotIndex + 1);
1631
+ }
1632
+ };
1633
+
1153
1634
  // apps/cli/src/infra/repositories/PackmindGateway.ts
1154
1635
  function decodeJwt(jwt) {
1155
1636
  try {
@@ -1256,7 +1737,7 @@ var PackmindGateway = class {
1256
1737
  );
1257
1738
  }
1258
1739
  throw new Error(
1259
- `Failed to pull content: Error: ${err?.message || JSON.stringify(error)}`
1740
+ `Failed to fetch content: Error: ${err?.message || JSON.stringify(error)}`
1260
1741
  );
1261
1742
  }
1262
1743
  };
@@ -1536,6 +2017,51 @@ var PackmindGateway = class {
1536
2017
  );
1537
2018
  }
1538
2019
  };
2020
+ this.getDetectionProgramsForPackages = async (params) => {
2021
+ const decodedApiKey = decodeApiKey(this.apiKey);
2022
+ if (!decodedApiKey.isValid) {
2023
+ throw new Error(`Invalid API key: ${decodedApiKey.error}`);
2024
+ }
2025
+ const { host } = decodedApiKey.payload;
2026
+ const url = `${host}/api/v0/detection-programs-for-packages`;
2027
+ const payload = {
2028
+ packagesSlugs: params.packagesSlugs
2029
+ };
2030
+ try {
2031
+ const response = await fetch(url, {
2032
+ method: "POST",
2033
+ headers: {
2034
+ "Content-Type": "application/json",
2035
+ Authorization: `Bearer ${this.apiKey}`
2036
+ },
2037
+ body: JSON.stringify(payload)
2038
+ });
2039
+ if (!response.ok) {
2040
+ let errorMsg = `API request failed: ${response.status} ${response.statusText}`;
2041
+ try {
2042
+ const errorBody = await response.json();
2043
+ if (errorBody && errorBody.message) {
2044
+ errorMsg = `${errorBody.message}`;
2045
+ }
2046
+ } catch {
2047
+ }
2048
+ throw new Error(errorMsg);
2049
+ }
2050
+ const result = await response.json();
2051
+ return result;
2052
+ } catch (error) {
2053
+ const err = error;
2054
+ const code = err?.code || err?.cause?.code;
2055
+ if (code === "ECONNREFUSED" || code === "ENOTFOUND" || err?.name === "FetchError" || typeof err?.message === "string" && (err.message.includes("Failed to fetch") || err.message.includes("network") || err.message.includes("NetworkError"))) {
2056
+ throw new Error(
2057
+ `Packmind server is not accessible at ${host}. Please check your network connection or the server URL.`
2058
+ );
2059
+ }
2060
+ throw new Error(
2061
+ `Failed to fetch detection programs for packages: Error: ${err?.message || JSON.stringify(error)}`
2062
+ );
2063
+ }
2064
+ };
1539
2065
  }
1540
2066
  };
1541
2067
 
@@ -3208,9 +3734,9 @@ var LinterAstAdapter = class {
3208
3734
  var TreeSitter17 = __toESM(require("web-tree-sitter"));
3209
3735
 
3210
3736
  // packages/linter-execution/src/application/useCases/ExecuteLinterProgramsUseCase.ts
3211
- var origin3 = "ExecuteLinterProgramsUseCase";
3737
+ var origin4 = "ExecuteLinterProgramsUseCase";
3212
3738
  var ExecuteLinterProgramsUseCase = class {
3213
- constructor(linterAstAdapter = new LinterAstAdapter(), logger2 = new PackmindLogger(origin3)) {
3739
+ constructor(linterAstAdapter = new LinterAstAdapter(), logger2 = new PackmindLogger(origin4)) {
3214
3740
  this.linterAstAdapter = linterAstAdapter;
3215
3741
  this.logger = logger2;
3216
3742
  }
@@ -3405,14 +3931,30 @@ var ExecuteLinterProgramsUseCase = class {
3405
3931
  }
3406
3932
  };
3407
3933
 
3408
- // packages/node-utils/src/ai/prompts/OpenAIService.ts
3409
- var import_openai = __toESM(require("openai"));
3934
+ // packages/node-utils/src/dataSources/local.ts
3935
+ var import_typeorm = require("typeorm");
3936
+ var dataSource = makeDatasource();
3937
+ function makeDatasource() {
3938
+ try {
3939
+ return new import_typeorm.DataSource({
3940
+ type: "postgres",
3941
+ url: process.env["DATABASE_URL"],
3942
+ entities: [],
3943
+ migrations: []
3944
+ });
3945
+ } catch {
3946
+ return {};
3947
+ }
3948
+ }
3949
+
3950
+ // packages/node-utils/src/cache/Cache.ts
3951
+ var import_ioredis = __toESM(require("ioredis"));
3410
3952
 
3411
3953
  // packages/node-utils/src/config/infra/Infisical/InfisicalConfig.ts
3412
3954
  var import_sdk = require("@infisical/sdk");
3413
- var origin4 = "InfisicalConfig";
3955
+ var origin5 = "InfisicalConfig";
3414
3956
  var InfisicalConfig = class {
3415
- constructor(clientId, clientSecret, env, projectId, logger2 = new PackmindLogger(origin4)) {
3957
+ constructor(clientId, clientSecret, env, projectId, logger2 = new PackmindLogger(origin5)) {
3416
3958
  this.clientId = clientId;
3417
3959
  this.clientSecret = clientSecret;
3418
3960
  this.env = env;
@@ -3487,7 +4029,7 @@ var InfisicalConfig = class {
3487
4029
  };
3488
4030
 
3489
4031
  // packages/node-utils/src/config/config/Configuration.ts
3490
- var origin5 = "Configuration";
4032
+ var origin6 = "Configuration";
3491
4033
  var Configuration = class _Configuration {
3492
4034
  constructor() {
3493
4035
  this.initialized = false;
@@ -3495,7 +4037,7 @@ var Configuration = class _Configuration {
3495
4037
  }
3496
4038
  static {
3497
4039
  this.logger = new PackmindLogger(
3498
- origin5,
4040
+ origin6,
3499
4041
  "info" /* INFO */
3500
4042
  );
3501
4043
  }
@@ -3642,8 +4184,7 @@ var Configuration = class _Configuration {
3642
4184
  };
3643
4185
 
3644
4186
  // packages/node-utils/src/cache/Cache.ts
3645
- var import_ioredis = __toESM(require("ioredis"));
3646
- var origin6 = "Cache";
4187
+ var origin7 = "Cache";
3647
4188
  var Cache = class _Cache {
3648
4189
  constructor() {
3649
4190
  this.initialized = false;
@@ -3655,7 +4196,7 @@ var Cache = class _Cache {
3655
4196
  }
3656
4197
  static {
3657
4198
  this.logger = new PackmindLogger(
3658
- origin6,
4199
+ origin7,
3659
4200
  "info" /* INFO */
3660
4201
  );
3661
4202
  }
@@ -3837,14 +4378,14 @@ var import_common2 = require("@nestjs/common");
3837
4378
 
3838
4379
  // packages/node-utils/src/sse/RedisSSEClient.ts
3839
4380
  var import_ioredis2 = __toESM(require("ioredis"));
3840
- var origin7 = "RedisSSEClient";
4381
+ var origin8 = "RedisSSEClient";
3841
4382
  var RedisSSEClient = class _RedisSSEClient {
3842
4383
  // eslint-disable-next-line @typescript-eslint/no-empty-function
3843
4384
  constructor() {
3844
4385
  this.initialized = false;
3845
4386
  }
3846
4387
  static {
3847
- this.logger = new PackmindLogger(origin7);
4388
+ this.logger = new PackmindLogger(origin8);
3848
4389
  }
3849
4390
  static getInstance() {
3850
4391
  _RedisSSEClient.logger.debug("Getting RedisSSEClient instance");
@@ -4028,10 +4569,10 @@ function serializeSSERedisMessage(message) {
4028
4569
  }
4029
4570
 
4030
4571
  // packages/node-utils/src/sse/SSEEventPublisher.ts
4031
- var origin8 = "SSEEventPublisher";
4572
+ var origin9 = "SSEEventPublisher";
4032
4573
  var SSEEventPublisher = class _SSEEventPublisher {
4033
4574
  static {
4034
- this.logger = new PackmindLogger(origin8);
4575
+ this.logger = new PackmindLogger(origin9);
4035
4576
  }
4036
4577
  /**
4037
4578
  * Get the singleton instance
@@ -4270,25 +4811,9 @@ ${sectionBlock}
4270
4811
  return result;
4271
4812
  }
4272
4813
 
4273
- // packages/node-utils/src/dataSources/local.ts
4274
- var import_typeorm = require("typeorm");
4275
- var dataSource = makeDatasource();
4276
- function makeDatasource() {
4277
- try {
4278
- return new import_typeorm.DataSource({
4279
- type: "postgres",
4280
- url: process.env["DATABASE_URL"],
4281
- entities: [],
4282
- migrations: []
4283
- });
4284
- } catch {
4285
- return {};
4286
- }
4287
- }
4288
-
4289
4814
  // apps/cli/src/application/useCases/PullDataUseCase.ts
4290
- var fs3 = __toESM(require("fs/promises"));
4291
- var path3 = __toESM(require("path"));
4815
+ var fs4 = __toESM(require("fs/promises"));
4816
+ var path4 = __toESM(require("path"));
4292
4817
  var PullDataUseCase = class {
4293
4818
  constructor(packmindGateway) {
4294
4819
  this.packmindGateway = packmindGateway;
@@ -4299,13 +4824,27 @@ var PullDataUseCase = class {
4299
4824
  filesCreated: 0,
4300
4825
  filesUpdated: 0,
4301
4826
  filesDeleted: 0,
4302
- errors: []
4827
+ errors: [],
4828
+ recipesCount: 0,
4829
+ standardsCount: 0
4303
4830
  };
4304
4831
  const response = await this.packmindGateway.getPullData({
4305
4832
  packagesSlugs: command3.packagesSlugs
4306
4833
  });
4834
+ const uniqueFilesMap = /* @__PURE__ */ new Map();
4835
+ for (const file of response.fileUpdates.createOrUpdate) {
4836
+ uniqueFilesMap.set(file.path, file);
4837
+ }
4838
+ const uniqueFiles = Array.from(uniqueFilesMap.values());
4839
+ for (const file of uniqueFiles) {
4840
+ if (file.path.includes(".packmind/recipes/") && file.path.endsWith(".md")) {
4841
+ result.recipesCount++;
4842
+ } else if (file.path.includes(".packmind/standards/") && file.path.endsWith(".md")) {
4843
+ result.standardsCount++;
4844
+ }
4845
+ }
4307
4846
  try {
4308
- for (const file of response.fileUpdates.createOrUpdate) {
4847
+ for (const file of uniqueFiles) {
4309
4848
  try {
4310
4849
  await this.createOrUpdateFile(baseDirectory, file, result);
4311
4850
  } catch (error) {
@@ -4330,9 +4869,9 @@ var PullDataUseCase = class {
4330
4869
  return result;
4331
4870
  }
4332
4871
  async createOrUpdateFile(baseDirectory, file, result) {
4333
- const fullPath = path3.join(baseDirectory, file.path);
4334
- const directory = path3.dirname(fullPath);
4335
- await fs3.mkdir(directory, { recursive: true });
4872
+ const fullPath = path4.join(baseDirectory, file.path);
4873
+ const directory = path4.dirname(fullPath);
4874
+ await fs4.mkdir(directory, { recursive: true });
4336
4875
  const fileExists = await this.fileExists(fullPath);
4337
4876
  if (file.content !== void 0) {
4338
4877
  await this.handleFullContentUpdate(
@@ -4352,7 +4891,7 @@ var PullDataUseCase = class {
4352
4891
  }
4353
4892
  async handleFullContentUpdate(fullPath, content, fileExists, result) {
4354
4893
  if (fileExists) {
4355
- const existingContent = await fs3.readFile(fullPath, "utf-8");
4894
+ const existingContent = await fs4.readFile(fullPath, "utf-8");
4356
4895
  const commentMarker = this.extractCommentMarker(content);
4357
4896
  let finalContent;
4358
4897
  if (!commentMarker) {
@@ -4364,23 +4903,23 @@ var PullDataUseCase = class {
4364
4903
  commentMarker
4365
4904
  );
4366
4905
  }
4367
- await fs3.writeFile(fullPath, finalContent, "utf-8");
4906
+ await fs4.writeFile(fullPath, finalContent, "utf-8");
4368
4907
  result.filesUpdated++;
4369
4908
  } else {
4370
- await fs3.writeFile(fullPath, content, "utf-8");
4909
+ await fs4.writeFile(fullPath, content, "utf-8");
4371
4910
  result.filesCreated++;
4372
4911
  }
4373
4912
  }
4374
4913
  async handleSectionsUpdate(fullPath, sections, fileExists, result) {
4375
4914
  let currentContent = "";
4376
4915
  if (fileExists) {
4377
- currentContent = await fs3.readFile(fullPath, "utf-8");
4916
+ currentContent = await fs4.readFile(fullPath, "utf-8");
4378
4917
  }
4379
4918
  const mergedContent = mergeSectionsIntoFileContent(
4380
4919
  currentContent,
4381
4920
  sections
4382
4921
  );
4383
- await fs3.writeFile(fullPath, mergedContent, "utf-8");
4922
+ await fs4.writeFile(fullPath, mergedContent, "utf-8");
4384
4923
  if (fileExists) {
4385
4924
  result.filesUpdated++;
4386
4925
  } else {
@@ -4388,16 +4927,16 @@ var PullDataUseCase = class {
4388
4927
  }
4389
4928
  }
4390
4929
  async deleteFile(baseDirectory, filePath, result) {
4391
- const fullPath = path3.join(baseDirectory, filePath);
4930
+ const fullPath = path4.join(baseDirectory, filePath);
4392
4931
  const fileExists = await this.fileExists(fullPath);
4393
4932
  if (fileExists) {
4394
- await fs3.unlink(fullPath);
4933
+ await fs4.unlink(fullPath);
4395
4934
  result.filesDeleted++;
4396
4935
  }
4397
4936
  }
4398
4937
  async fileExists(filePath) {
4399
4938
  try {
4400
- await fs3.access(filePath);
4939
+ await fs4.access(filePath);
4401
4940
  return true;
4402
4941
  } catch {
4403
4942
  return false;
@@ -4470,6 +5009,126 @@ var GetPackageSummaryUseCase = class {
4470
5009
  }
4471
5010
  };
4472
5011
 
5012
+ // apps/cli/src/infra/repositories/ConfigFileRepository.ts
5013
+ var fs5 = __toESM(require("fs/promises"));
5014
+ var path5 = __toESM(require("path"));
5015
+ var ConfigFileRepository = class {
5016
+ constructor() {
5017
+ this.CONFIG_FILENAME = "packmind.json";
5018
+ this.EXCLUDED_DIRECTORIES = [
5019
+ "node_modules",
5020
+ ".git",
5021
+ "dist",
5022
+ "build",
5023
+ "coverage",
5024
+ ".nx"
5025
+ ];
5026
+ }
5027
+ async writeConfig(baseDirectory, config) {
5028
+ const configPath = path5.join(baseDirectory, this.CONFIG_FILENAME);
5029
+ const configContent = JSON.stringify(config, null, 2) + "\n";
5030
+ await fs5.writeFile(configPath, configContent, "utf-8");
5031
+ }
5032
+ async readConfig(baseDirectory) {
5033
+ const configPath = path5.join(baseDirectory, this.CONFIG_FILENAME);
5034
+ try {
5035
+ const configContent = await fs5.readFile(configPath, "utf-8");
5036
+ const config = JSON.parse(configContent);
5037
+ if (!config.packages || typeof config.packages !== "object") {
5038
+ throw new Error(
5039
+ "Invalid packmind.json structure. Expected { packages: { ... } }"
5040
+ );
5041
+ }
5042
+ return config;
5043
+ } catch (error) {
5044
+ if (error.code === "ENOENT") {
5045
+ return null;
5046
+ }
5047
+ throw new Error(
5048
+ `Failed to read packmind.json: ${error.message}`
5049
+ );
5050
+ }
5051
+ }
5052
+ /**
5053
+ * Recursively finds all directories containing packmind.json in descendant folders.
5054
+ * Excludes common build/dependency directories (node_modules, .git, dist, etc.)
5055
+ *
5056
+ * @param directory - The root directory to search from
5057
+ * @returns Array of directory paths that contain a packmind.json file
5058
+ */
5059
+ async findDescendantConfigs(directory) {
5060
+ const normalizedDir = path5.resolve(directory);
5061
+ const results = [];
5062
+ const searchRecursively = async (currentDir) => {
5063
+ let entries;
5064
+ try {
5065
+ entries = await fs5.readdir(currentDir, { withFileTypes: true });
5066
+ } catch {
5067
+ return;
5068
+ }
5069
+ for (const entry of entries) {
5070
+ if (!entry.isDirectory()) {
5071
+ continue;
5072
+ }
5073
+ if (this.EXCLUDED_DIRECTORIES.includes(entry.name)) {
5074
+ continue;
5075
+ }
5076
+ const entryPath = path5.join(currentDir, entry.name);
5077
+ const config = await this.readConfig(entryPath);
5078
+ if (config) {
5079
+ results.push(entryPath);
5080
+ }
5081
+ await searchRecursively(entryPath);
5082
+ }
5083
+ };
5084
+ await searchRecursively(normalizedDir);
5085
+ return results;
5086
+ }
5087
+ /**
5088
+ * Reads all packmind.json files from startDirectory up to stopDirectory (inclusive)
5089
+ * and merges their package configurations.
5090
+ *
5091
+ * @param startDirectory - Directory to start searching from (typically the lint target)
5092
+ * @param stopDirectory - Directory to stop searching at (typically git repo root), or null to walk to filesystem root
5093
+ * @returns Merged configuration from all found packmind.json files
5094
+ */
5095
+ async readHierarchicalConfig(startDirectory, stopDirectory) {
5096
+ const configs = [];
5097
+ const configPaths = [];
5098
+ const normalizedStart = path5.resolve(startDirectory);
5099
+ const normalizedStop = stopDirectory ? path5.resolve(stopDirectory) : null;
5100
+ let currentDir = normalizedStart;
5101
+ while (true) {
5102
+ const config = await this.readConfig(currentDir);
5103
+ if (config) {
5104
+ configs.push(config);
5105
+ configPaths.push(path5.join(currentDir, this.CONFIG_FILENAME));
5106
+ }
5107
+ if (normalizedStop !== null && currentDir === normalizedStop) {
5108
+ break;
5109
+ }
5110
+ const parentDir = path5.dirname(currentDir);
5111
+ if (parentDir === currentDir) {
5112
+ break;
5113
+ }
5114
+ currentDir = parentDir;
5115
+ }
5116
+ const mergedPackages = {};
5117
+ for (const config of configs) {
5118
+ for (const [slug, version] of Object.entries(config.packages)) {
5119
+ if (!(slug in mergedPackages)) {
5120
+ mergedPackages[slug] = version;
5121
+ }
5122
+ }
5123
+ }
5124
+ return {
5125
+ packages: mergedPackages,
5126
+ configPaths,
5127
+ hasConfigs: configs.length > 0
5128
+ };
5129
+ }
5130
+ };
5131
+
4473
5132
  // apps/cli/src/PackmindCliHexaFactory.ts
4474
5133
  var PackmindCliHexaFactory = class {
4475
5134
  constructor(logger2) {
@@ -4477,7 +5136,8 @@ var PackmindCliHexaFactory = class {
4477
5136
  this.repositories = {
4478
5137
  packmindGateway: new PackmindGateway(
4479
5138
  process.env.PACKMIND_API_KEY_V3 || ""
4480
- )
5139
+ ),
5140
+ configFileRepository: new ConfigFileRepository()
4481
5141
  };
4482
5142
  this.services = {
4483
5143
  listFiles: new ListFiles(),
@@ -4495,6 +5155,11 @@ var PackmindCliHexaFactory = class {
4495
5155
  this.repositories,
4496
5156
  this.logger
4497
5157
  ),
5158
+ lintFilesLocally: new LintFilesLocallyUseCase(
5159
+ this.services,
5160
+ this.repositories,
5161
+ this.logger
5162
+ ),
4498
5163
  pullData: new PullDataUseCase(this.repositories.packmindGateway),
4499
5164
  listPackages: new ListPackagesUseCase(this.repositories.packmindGateway),
4500
5165
  getPackageBySlug: new GetPackageSummaryUseCase(
@@ -4505,9 +5170,9 @@ var PackmindCliHexaFactory = class {
4505
5170
  };
4506
5171
 
4507
5172
  // apps/cli/src/PackmindCliHexa.ts
4508
- var origin9 = "PackmindCliHexa";
5173
+ var origin10 = "PackmindCliHexa";
4509
5174
  var PackmindCliHexa = class {
4510
- constructor(logger2 = new PackmindLogger(origin9)) {
5175
+ constructor(logger2 = new PackmindLogger(origin10)) {
4511
5176
  this.logger = logger2;
4512
5177
  try {
4513
5178
  this.hexa = new PackmindCliHexaFactory(this.logger);
@@ -4537,6 +5202,9 @@ var PackmindCliHexa = class {
4537
5202
  async lintFilesInDirectory(command3) {
4538
5203
  return this.hexa.useCases.lintFilesInDirectory.execute(command3);
4539
5204
  }
5205
+ async lintFilesLocally(command3) {
5206
+ return this.hexa.useCases.lintFilesLocally.execute(command3);
5207
+ }
4540
5208
  async pullData(command3) {
4541
5209
  return this.hexa.useCases.pullData.execute(command3);
4542
5210
  }
@@ -4546,6 +5214,57 @@ var PackmindCliHexa = class {
4546
5214
  async getPackageBySlug(command3) {
4547
5215
  return this.hexa.useCases.getPackageBySlug.execute(command3);
4548
5216
  }
5217
+ async writeConfig(baseDirectory, packagesSlugs) {
5218
+ const config = {
5219
+ packages: packagesSlugs.reduce(
5220
+ (acc, slug) => {
5221
+ acc[slug] = "*";
5222
+ return acc;
5223
+ },
5224
+ {}
5225
+ )
5226
+ };
5227
+ await this.hexa.repositories.configFileRepository.writeConfig(
5228
+ baseDirectory,
5229
+ config
5230
+ );
5231
+ }
5232
+ async readConfig(baseDirectory) {
5233
+ const config = await this.hexa.repositories.configFileRepository.readConfig(
5234
+ baseDirectory
5235
+ );
5236
+ if (!config) return [];
5237
+ const hasNonWildcardVersions = Object.values(config.packages).some(
5238
+ (version) => version !== "*"
5239
+ );
5240
+ if (hasNonWildcardVersions) {
5241
+ console.log(
5242
+ "WARN: Package versions are not supported yet, getting the latest version"
5243
+ );
5244
+ }
5245
+ return Object.keys(config.packages);
5246
+ }
5247
+ async readHierarchicalConfig(startDirectory, stopDirectory) {
5248
+ return this.hexa.repositories.configFileRepository.readHierarchicalConfig(
5249
+ startDirectory,
5250
+ stopDirectory
5251
+ );
5252
+ }
5253
+ async findDescendantConfigs(directory) {
5254
+ return this.hexa.repositories.configFileRepository.findDescendantConfigs(
5255
+ directory
5256
+ );
5257
+ }
5258
+ async getGitRepositoryRoot(directory) {
5259
+ return this.hexa.services.gitRemoteUrlService.getGitRepositoryRoot(
5260
+ directory
5261
+ );
5262
+ }
5263
+ async tryGetGitRepositoryRoot(directory) {
5264
+ return this.hexa.services.gitRemoteUrlService.tryGetGitRepositoryRoot(
5265
+ directory
5266
+ );
5267
+ }
4549
5268
  };
4550
5269
 
4551
5270
  // apps/cli/src/infra/repositories/IDELintLogger.ts
@@ -4600,6 +5319,7 @@ var HumanReadableLogger = class {
4600
5319
  };
4601
5320
 
4602
5321
  // apps/cli/src/infra/commands/LinterCommand.ts
5322
+ var pathModule = __toESM(require("path"));
4603
5323
  var Logger = {
4604
5324
  from: async (input) => {
4605
5325
  switch (input) {
@@ -4662,7 +5382,7 @@ var lintCommand = (0, import_cmd_ts.command)({
4662
5382
  description: "Enable debug logging"
4663
5383
  })
4664
5384
  },
4665
- handler: async ({ path: path5, draft, rule, debug, language, logger: logger2 }) => {
5385
+ handler: async ({ path: path7, draft, rule, debug, language, logger: logger2 }) => {
4666
5386
  if (draft && !rule) {
4667
5387
  throw new Error("option --rule is required to use --draft mode");
4668
5388
  }
@@ -4672,13 +5392,52 @@ var lintCommand = (0, import_cmd_ts.command)({
4672
5392
  debug ? "debug" /* DEBUG */ : "info" /* INFO */
4673
5393
  );
4674
5394
  const packmindCliHexa = new PackmindCliHexa(packmindLogger);
4675
- const { violations } = await packmindCliHexa.lintFilesInDirectory({
4676
- path: path5 ?? ".",
4677
- draftMode: draft,
4678
- standardSlug: rule?.standardSlug,
4679
- ruleId: rule?.ruleId,
4680
- language
4681
- });
5395
+ const targetPath = path7 ?? ".";
5396
+ const hasArguments = !!(draft || rule || language);
5397
+ const absolutePath = pathModule.isAbsolute(targetPath) ? targetPath : pathModule.resolve(process.cwd(), targetPath);
5398
+ let useLocalLinting = false;
5399
+ let lintTargets = [];
5400
+ if (!hasArguments) {
5401
+ const stopDirectory = await packmindCliHexa.tryGetGitRepositoryRoot(absolutePath);
5402
+ const hierarchicalConfig = await packmindCliHexa.readHierarchicalConfig(
5403
+ absolutePath,
5404
+ stopDirectory
5405
+ );
5406
+ if (hierarchicalConfig.hasConfigs) {
5407
+ useLocalLinting = true;
5408
+ const rootConfig = await packmindCliHexa.readHierarchicalConfig(
5409
+ absolutePath,
5410
+ absolutePath
5411
+ );
5412
+ if (rootConfig.hasConfigs) {
5413
+ lintTargets.push(absolutePath);
5414
+ }
5415
+ const descendantTargets = await packmindCliHexa.findDescendantConfigs(absolutePath);
5416
+ lintTargets = [...lintTargets, ...descendantTargets];
5417
+ if (lintTargets.length === 0) {
5418
+ lintTargets.push(absolutePath);
5419
+ }
5420
+ }
5421
+ }
5422
+ let violations = [];
5423
+ if (useLocalLinting && lintTargets.length > 0) {
5424
+ for (const target of lintTargets) {
5425
+ packmindLogger.debug(`Linting target: ${target}`);
5426
+ const result = await packmindCliHexa.lintFilesLocally({
5427
+ path: target
5428
+ });
5429
+ violations = [...violations, ...result.violations];
5430
+ }
5431
+ } else {
5432
+ const result = await packmindCliHexa.lintFilesInDirectory({
5433
+ path: targetPath,
5434
+ draftMode: draft,
5435
+ standardSlug: rule?.standardSlug,
5436
+ ruleId: rule?.ruleId,
5437
+ language
5438
+ });
5439
+ violations = result.violations;
5440
+ }
4682
5441
  (logger2 === "ide" /* ide */ ? new IDELintLogger() : new HumanReadableLogger()).logViolations(violations);
4683
5442
  const durationSeconds = (Date.now() - startedAt) / 1e3;
4684
5443
  console.log(`Lint completed in ${durationSeconds.toFixed(2)}s`);
@@ -4746,8 +5505,8 @@ function extractWasmFiles() {
4746
5505
 
4747
5506
  // apps/cli/src/main.ts
4748
5507
  var import_dotenv = require("dotenv");
4749
- var fs4 = __toESM(require("fs"));
4750
- var path4 = __toESM(require("path"));
5508
+ var fs6 = __toESM(require("fs"));
5509
+ var path6 = __toESM(require("path"));
4751
5510
 
4752
5511
  // apps/cli/src/infra/commands/PullCommand.ts
4753
5512
  var import_cmd_ts2 = require("cmd-ts");
@@ -4784,7 +5543,11 @@ var pullCommand = (0, import_cmd_ts2.command)({
4784
5543
  }
4785
5544
  console.log("Available packages:");
4786
5545
  packages.forEach((pkg) => {
4787
- console.log(` - ${pkg.slug}: ${pkg.description || pkg.name}`);
5546
+ console.log(`- ${pkg.name} (${pkg.slug})`);
5547
+ if (pkg.description) {
5548
+ console.log(` ${pkg.description}`);
5549
+ console.log("");
5550
+ }
4788
5551
  });
4789
5552
  process.exit(0);
4790
5553
  } catch (error) {
@@ -4841,30 +5604,59 @@ var pullCommand = (0, import_cmd_ts2.command)({
4841
5604
  process.exit(1);
4842
5605
  }
4843
5606
  }
4844
- if (packagesSlugs.length === 0) {
4845
- console.log("Usage: packmind-cli pull <package-slug> [package-slug...]");
4846
- console.log(" packmind-cli pull --list");
5607
+ let configPackages;
5608
+ let configExists = false;
5609
+ try {
5610
+ configPackages = await packmindCliHexa.readConfig(process.cwd());
5611
+ configExists = configPackages.length > 0;
5612
+ } catch (error) {
5613
+ console.error("ERROR Failed to parse packmind.json");
5614
+ if (error instanceof Error) {
5615
+ console.error(`ERROR ${error.message}`);
5616
+ } else {
5617
+ console.error(`ERROR ${String(error)}`);
5618
+ }
5619
+ console.error(
5620
+ "\n\u{1F4A1} Please fix the packmind.json file or delete it to continue."
5621
+ );
5622
+ process.exit(1);
5623
+ }
5624
+ const allPackages = [.../* @__PURE__ */ new Set([...configPackages, ...packagesSlugs])];
5625
+ if (allPackages.length === 0) {
5626
+ console.log("WARN config packmind.json not found");
5627
+ console.log(
5628
+ "Usage: packmind-cli install <package-slug> [package-slug...]"
5629
+ );
5630
+ console.log(" packmind-cli install --list");
4847
5631
  console.log("");
4848
5632
  console.log("Examples:");
4849
- console.log(" packmind-cli pull backend");
4850
- console.log(" packmind-cli pull backend frontend");
4851
- console.log(" packmind-cli pull --list # Show available packages");
5633
+ console.log(" packmind-cli install backend");
5634
+ console.log(" packmind-cli install backend frontend");
5635
+ console.log(" packmind-cli install --list # Show available packages");
4852
5636
  console.log("");
4853
- console.log("Pull recipes and standards from the specified packages.");
5637
+ console.log("Install recipes and standards from the specified packages.");
4854
5638
  process.exit(0);
4855
5639
  }
4856
- console.log(
4857
- `Pulling content from packages: ${packagesSlugs.join(", ")}...`
4858
- );
5640
+ if (!configExists && packagesSlugs.length > 0) {
5641
+ console.log("INFO initializing packmind.json");
5642
+ }
4859
5643
  try {
5644
+ const packageCount = allPackages.length;
5645
+ const packageWord = packageCount === 1 ? "package" : "packages";
5646
+ console.log(
5647
+ `Fetching ${packageCount} ${packageWord}: ${allPackages.join(", ")}...`
5648
+ );
4860
5649
  const result = await packmindCliHexa.pullData({
4861
5650
  baseDirectory: process.cwd(),
4862
- packagesSlugs
5651
+ packagesSlugs: allPackages
4863
5652
  });
4864
- console.log("\n\u2705 Pull completed successfully!");
4865
- console.log(` Files created: ${result.filesCreated}`);
4866
- console.log(` Files updated: ${result.filesUpdated}`);
4867
- console.log(` Files deleted: ${result.filesDeleted}`);
5653
+ console.log(
5654
+ `Installing ${result.recipesCount} recipes and ${result.standardsCount} standards...`
5655
+ );
5656
+ console.log(
5657
+ `
5658
+ added ${result.filesCreated} files, changed ${result.filesUpdated} files, removed ${result.filesDeleted} files`
5659
+ );
4868
5660
  if (result.errors.length > 0) {
4869
5661
  console.log("\n\u26A0\uFE0F Errors encountered:");
4870
5662
  result.errors.forEach((error) => {
@@ -4872,15 +5664,54 @@ var pullCommand = (0, import_cmd_ts2.command)({
4872
5664
  });
4873
5665
  process.exit(1);
4874
5666
  }
5667
+ await packmindCliHexa.writeConfig(process.cwd(), allPackages);
4875
5668
  } catch (error) {
4876
- console.error("\n\u274C Failed to pull content:");
5669
+ console.error("\n\u274C Failed to install content:");
4877
5670
  if (error instanceof Error) {
4878
5671
  const errorObj = error;
4879
5672
  if (errorObj.statusCode === 404) {
4880
5673
  console.error(` ${errorObj.message}`);
4881
- console.error(
4882
- "\n\u{1F4A1} Use `packmind-cli pull --list` to show available packages"
4883
- );
5674
+ if (configExists && configPackages.length > 0) {
5675
+ const missingPackages = allPackages.filter(
5676
+ (pkg) => configPackages.includes(pkg)
5677
+ );
5678
+ if (missingPackages.length > 0) {
5679
+ console.error(
5680
+ "\n\u{1F4A1} Either remove the following package(s) from packmind.json:"
5681
+ );
5682
+ missingPackages.forEach((pkg) => {
5683
+ console.error(` "${pkg}"`);
5684
+ });
5685
+ console.error(" Or ensure that:");
5686
+ console.error(
5687
+ " - The package slug exists and is correctly spelled"
5688
+ );
5689
+ console.error(" - The package exists in your organization");
5690
+ console.error(" - You have the correct API key configured");
5691
+ } else {
5692
+ console.error("\n\u{1F4A1} Troubleshooting tips:");
5693
+ console.error(
5694
+ " - Check if the package slug exists and is correctly spelled"
5695
+ );
5696
+ console.error(
5697
+ " - Check that the package exists in your organization"
5698
+ );
5699
+ console.error(
5700
+ " - Ensure you have the correct API key configured"
5701
+ );
5702
+ }
5703
+ } else {
5704
+ console.error("\n\u{1F4A1} Troubleshooting tips:");
5705
+ console.error(
5706
+ " - Check if the package slug exists and is correctly spelled"
5707
+ );
5708
+ console.error(
5709
+ " - Check that the package exists in your organization"
5710
+ );
5711
+ console.error(
5712
+ " - Ensure you have the correct API key configured"
5713
+ );
5714
+ }
4884
5715
  } else {
4885
5716
  console.error(` ${errorObj.message}`);
4886
5717
  const apiErrorObj = error;
@@ -4910,25 +5741,25 @@ function findEnvFile() {
4910
5741
  const startDir = currentDir;
4911
5742
  let gitRootFound = false;
4912
5743
  let searchDir = currentDir;
4913
- while (searchDir !== path4.parse(searchDir).root) {
4914
- if (fs4.existsSync(path4.join(searchDir, ".git"))) {
5744
+ while (searchDir !== path6.parse(searchDir).root) {
5745
+ if (fs6.existsSync(path6.join(searchDir, ".git"))) {
4915
5746
  gitRootFound = true;
4916
5747
  break;
4917
5748
  }
4918
- searchDir = path4.dirname(searchDir);
5749
+ searchDir = path6.dirname(searchDir);
4919
5750
  }
4920
- while (currentDir !== path4.parse(currentDir).root) {
4921
- const envPath2 = path4.join(currentDir, ".env");
4922
- if (fs4.existsSync(envPath2)) {
5751
+ while (currentDir !== path6.parse(currentDir).root) {
5752
+ const envPath2 = path6.join(currentDir, ".env");
5753
+ if (fs6.existsSync(envPath2)) {
4923
5754
  return envPath2;
4924
5755
  }
4925
- if (gitRootFound && fs4.existsSync(path4.join(currentDir, ".git"))) {
5756
+ if (gitRootFound && fs6.existsSync(path6.join(currentDir, ".git"))) {
4926
5757
  break;
4927
5758
  }
4928
5759
  if (!gitRootFound && currentDir !== startDir) {
4929
5760
  break;
4930
5761
  }
4931
- currentDir = path4.dirname(currentDir);
5762
+ currentDir = path6.dirname(currentDir);
4932
5763
  }
4933
5764
  return null;
4934
5765
  }
@@ -4953,7 +5784,8 @@ var app = (0, import_cmd_ts3.subcommands)({
4953
5784
  description: "Packmind CLI tool",
4954
5785
  cmds: {
4955
5786
  lint: lintCommand,
4956
- pull: pullCommand
5787
+ pull: pullCommand,
5788
+ install: pullCommand
4957
5789
  }
4958
5790
  });
4959
5791
  (0, import_cmd_ts3.run)(app, args).catch((error) => {