coder-config 0.41.27 → 0.41.29

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.
package/lib/constants.js CHANGED
@@ -2,7 +2,7 @@
2
2
  * Constants and tool path configurations
3
3
  */
4
4
 
5
- const VERSION = '0.41.27';
5
+ const VERSION = '0.41.29';
6
6
 
7
7
  // Tool-specific path configurations
8
8
  const TOOL_PATHS = {
@@ -628,6 +628,208 @@ function workstreamCheckPath(installDir, targetPath, silent = false) {
628
628
  return isWithin;
629
629
  }
630
630
 
631
+ /**
632
+ * Generate rules/context from project repositories
633
+ * Reads README.md, package.json, CLAUDE.md, etc. to create a summary
634
+ */
635
+ function generateRulesFromRepos(projects) {
636
+ if (!projects || projects.length === 0) {
637
+ return '';
638
+ }
639
+
640
+ const lines = [];
641
+ lines.push('# Workstream Context');
642
+ lines.push('');
643
+ lines.push('## Repositories');
644
+ lines.push('');
645
+
646
+ for (const projectPath of projects) {
647
+ const absPath = path.resolve(projectPath.replace(/^~/, process.env.HOME || ''));
648
+ const name = path.basename(absPath);
649
+
650
+ lines.push(`### ${name}`);
651
+ lines.push('');
652
+
653
+ // Check for CLAUDE.md first (most relevant for Claude context)
654
+ const claudeMdPath = path.join(absPath, 'CLAUDE.md');
655
+ if (fs.existsSync(claudeMdPath)) {
656
+ try {
657
+ const content = fs.readFileSync(claudeMdPath, 'utf8');
658
+ // Extract first section or summary
659
+ const firstSection = extractFirstSection(content);
660
+ if (firstSection) {
661
+ lines.push(firstSection.trim());
662
+ lines.push('');
663
+ continue; // CLAUDE.md is comprehensive, skip other files
664
+ }
665
+ } catch (e) {
666
+ // Ignore read errors
667
+ }
668
+ }
669
+
670
+ // Check for package.json (JavaScript/TypeScript projects)
671
+ const packageJsonPath = path.join(absPath, 'package.json');
672
+ if (fs.existsSync(packageJsonPath)) {
673
+ try {
674
+ const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
675
+ if (pkg.description) {
676
+ lines.push(`**Description:** ${pkg.description}`);
677
+ lines.push('');
678
+ }
679
+ const deps = Object.keys(pkg.dependencies || {});
680
+ const devDeps = Object.keys(pkg.devDependencies || {});
681
+ if (deps.length > 0 || devDeps.length > 0) {
682
+ const keyDeps = [...deps, ...devDeps].filter(d =>
683
+ ['react', 'vue', 'angular', 'next', 'express', 'fastify', 'koa',
684
+ 'typescript', 'prisma', 'drizzle', 'mongoose', 'sequelize',
685
+ 'tailwindcss', 'vite', 'webpack', 'jest', 'vitest', 'mocha'].includes(d)
686
+ );
687
+ if (keyDeps.length > 0) {
688
+ lines.push(`**Stack:** ${keyDeps.join(', ')}`);
689
+ lines.push('');
690
+ }
691
+ }
692
+ } catch (e) {
693
+ // Ignore parse errors
694
+ }
695
+ }
696
+
697
+ // Check for pyproject.toml (Python projects)
698
+ const pyprojectPath = path.join(absPath, 'pyproject.toml');
699
+ if (fs.existsSync(pyprojectPath)) {
700
+ try {
701
+ const content = fs.readFileSync(pyprojectPath, 'utf8');
702
+ const descMatch = content.match(/description\s*=\s*"([^"]+)"/);
703
+ if (descMatch) {
704
+ lines.push(`**Description:** ${descMatch[1]}`);
705
+ lines.push('');
706
+ }
707
+ // Detect common Python frameworks
708
+ const frameworks = [];
709
+ if (content.includes('fastapi')) frameworks.push('FastAPI');
710
+ if (content.includes('django')) frameworks.push('Django');
711
+ if (content.includes('flask')) frameworks.push('Flask');
712
+ if (content.includes('sqlalchemy')) frameworks.push('SQLAlchemy');
713
+ if (content.includes('pytest')) frameworks.push('pytest');
714
+ if (frameworks.length > 0) {
715
+ lines.push(`**Stack:** ${frameworks.join(', ')}`);
716
+ lines.push('');
717
+ }
718
+ } catch (e) {
719
+ // Ignore read errors
720
+ }
721
+ }
722
+
723
+ // Check for README.md
724
+ const readmePath = path.join(absPath, 'README.md');
725
+ if (fs.existsSync(readmePath)) {
726
+ try {
727
+ const content = fs.readFileSync(readmePath, 'utf8');
728
+ // Extract first paragraph or description
729
+ const firstPara = extractFirstParagraph(content);
730
+ if (firstPara && firstPara.length > 20) {
731
+ lines.push(firstPara.trim());
732
+ lines.push('');
733
+ }
734
+ } catch (e) {
735
+ // Ignore read errors
736
+ }
737
+ }
738
+
739
+ // Check for Cargo.toml (Rust projects)
740
+ const cargoPath = path.join(absPath, 'Cargo.toml');
741
+ if (fs.existsSync(cargoPath)) {
742
+ try {
743
+ const content = fs.readFileSync(cargoPath, 'utf8');
744
+ const descMatch = content.match(/description\s*=\s*"([^"]+)"/);
745
+ if (descMatch) {
746
+ lines.push(`**Description:** ${descMatch[1]}`);
747
+ lines.push('');
748
+ }
749
+ lines.push('**Language:** Rust');
750
+ lines.push('');
751
+ } catch (e) {
752
+ // Ignore read errors
753
+ }
754
+ }
755
+
756
+ // Check for go.mod (Go projects)
757
+ const goModPath = path.join(absPath, 'go.mod');
758
+ if (fs.existsSync(goModPath)) {
759
+ lines.push('**Language:** Go');
760
+ lines.push('');
761
+ }
762
+
763
+ // If nothing was found, just note the path
764
+ if (lines[lines.length - 1] === '' && lines[lines.length - 2] === `### ${name}`) {
765
+ lines.push(`*Project at ${absPath.replace(process.env.HOME || '', '~')}*`);
766
+ lines.push('');
767
+ }
768
+ }
769
+
770
+ return lines.join('\n').trim();
771
+ }
772
+
773
+ /**
774
+ * Extract first meaningful section from markdown
775
+ */
776
+ function extractFirstSection(content) {
777
+ const lines = content.split('\n');
778
+ let result = [];
779
+ let inSection = false;
780
+ let lineCount = 0;
781
+
782
+ for (const line of lines) {
783
+ // Skip initial title
784
+ if (!inSection && line.startsWith('# ')) {
785
+ inSection = true;
786
+ continue;
787
+ }
788
+ if (inSection) {
789
+ // Stop at next heading or after reasonable amount of content
790
+ if (line.startsWith('## ') && lineCount > 3) break;
791
+ if (lineCount > 15) break;
792
+ result.push(line);
793
+ if (line.trim()) lineCount++;
794
+ }
795
+ }
796
+
797
+ return result.join('\n').trim();
798
+ }
799
+
800
+ /**
801
+ * Extract first paragraph from README
802
+ */
803
+ function extractFirstParagraph(content) {
804
+ const lines = content.split('\n');
805
+ let result = [];
806
+ let started = false;
807
+ let emptyLineCount = 0;
808
+
809
+ for (const line of lines) {
810
+ // Skip badges, titles, and empty lines at start
811
+ if (!started) {
812
+ if (line.startsWith('#') || line.startsWith('![') || line.startsWith('[!') || !line.trim()) {
813
+ continue;
814
+ }
815
+ started = true;
816
+ }
817
+
818
+ if (started) {
819
+ if (!line.trim()) {
820
+ emptyLineCount++;
821
+ if (emptyLineCount > 1) break;
822
+ } else {
823
+ emptyLineCount = 0;
824
+ result.push(line);
825
+ if (result.length > 5) break;
826
+ }
827
+ }
828
+ }
829
+
830
+ return result.join(' ').replace(/\s+/g, ' ').trim();
831
+ }
832
+
631
833
  module.exports = {
632
834
  getWorkstreamsPath,
633
835
  loadWorkstreams,
@@ -650,4 +852,5 @@ module.exports = {
650
852
  workstreamInstallHookCodex,
651
853
  workstreamDeactivate,
652
854
  workstreamCheckPath,
855
+ generateRulesFromRepos,
653
856
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coder-config",
3
- "version": "0.41.27",
3
+ "version": "0.41.29",
4
4
  "description": "Configuration manager for AI coding tools - Claude Code, Gemini CLI, Codex CLI, Antigravity. Manage MCPs, rules, permissions, memory, and workstreams.",
5
5
  "author": "regression.io",
6
6
  "main": "config-loader.js",