nexus-agents 2.82.0 → 2.83.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.
- package/dist/{child-mcp-config-BMSYR7VV.js → child-mcp-config-XVPIO5HV.js} +2 -2
- package/dist/{chunk-OYDJ3C4N.js → chunk-2W4QJ27D.js} +3 -3
- package/dist/{chunk-BQ4YXGGQ.js → chunk-3ACDP4E6.js} +124 -2
- package/dist/chunk-3ACDP4E6.js.map +1 -0
- package/dist/{chunk-C7P2HLJX.js → chunk-3ASED5PR.js} +4 -4
- package/dist/{chunk-TXIUCEFT.js → chunk-3GJAAENS.js} +3 -3
- package/dist/{chunk-GNG7URCR.js → chunk-3WUVWZUC.js} +2 -2
- package/dist/{chunk-FTT2IYYX.js → chunk-4BXQ2OZY.js} +34 -16
- package/dist/chunk-4BXQ2OZY.js.map +1 -0
- package/dist/{chunk-SBZVRH4S.js → chunk-54UXPJVM.js} +2 -2
- package/dist/{chunk-QHL4KGNB.js → chunk-5T6SYCH2.js} +2 -2
- package/dist/{chunk-BL5IBHEY.js → chunk-6WDJ3IE4.js} +3 -3
- package/dist/{chunk-2R5UUBGA.js → chunk-AFPHR72D.js} +2 -2
- package/dist/{chunk-S36LIUV2.js → chunk-AINOJRZX.js} +7 -7
- package/dist/{chunk-HJUHDPXJ.js → chunk-BZUEUD4G.js} +3 -3
- package/dist/{chunk-2SQXJQTA.js → chunk-FHGDJKCZ.js} +16 -13
- package/dist/{chunk-2SQXJQTA.js.map → chunk-FHGDJKCZ.js.map} +1 -1
- package/dist/{chunk-XGUDCUMB.js → chunk-FI77TGBY.js} +2 -2
- package/dist/{chunk-7J7PNOJQ.js → chunk-HFOQKCD2.js} +22 -5
- package/dist/chunk-HFOQKCD2.js.map +1 -0
- package/dist/{chunk-PZESEBD3.js → chunk-L362KRSU.js} +2 -2
- package/dist/{chunk-HMXQKDUV.js → chunk-NLPMYBIV.js} +23 -7
- package/dist/chunk-NLPMYBIV.js.map +1 -0
- package/dist/{chunk-4XNVJS5A.js → chunk-PMLVZXAE.js} +763 -728
- package/dist/chunk-PMLVZXAE.js.map +1 -0
- package/dist/{chunk-JM3R267Z.js → chunk-PPBZQKRP.js} +5 -5
- package/dist/{chunk-WUUEKFKG.js → chunk-Q6PZMGLU.js} +2 -2
- package/dist/{chunk-W2AIGD35.js → chunk-RVRXKNND.js} +3 -3
- package/dist/{chunk-7VNVDFD5.js → chunk-RXVH52UI.js} +44 -18
- package/dist/{chunk-7VNVDFD5.js.map → chunk-RXVH52UI.js.map} +1 -1
- package/dist/{chunk-HVZ52LOL.js → chunk-U6LDVJS7.js} +2 -2
- package/dist/{chunk-X3JR3GMT.js → chunk-WGSRH5EQ.js} +3 -3
- package/dist/{chunk-SXL744NF.js → chunk-WJFUTRHX.js} +2 -2
- package/dist/{chunk-U7JXQSEM.js → chunk-YO27YAEX.js} +3 -3
- package/dist/{chunk-G2DZBEMU.js → chunk-Z64MSLEK.js} +2 -2
- package/dist/{chunk-NTLJ3INA.js → chunk-ZIWEEVMI.js} +2 -2
- package/dist/{chunk-SHWGK7X6.js → chunk-ZM5JKJHI.js} +2 -2
- package/dist/{cli-circuit-breaker-BIJUQRQI.js → cli-circuit-breaker-Y26NPPNO.js} +4 -4
- package/dist/cli.js +366 -344
- package/dist/cli.js.map +1 -1
- package/dist/codebase-search-ZFJUVMVR.js +8 -0
- package/dist/{composite-router-FB7P22L5.js → composite-router-X2ZYIEHH.js} +2 -2
- package/dist/{consensus-vote-KKAIFULI.js → consensus-vote-KZ6UURUI.js} +11 -11
- package/dist/{context-retriever-4JCGMWH7.js → context-retriever-QY4FNTDZ.js} +5 -5
- package/dist/{doctor-deep-7YK4BZIJ.js → doctor-deep-2E2GBMYR.js} +3 -3
- package/dist/expert-bridge-FHPWDFJX.js +13 -0
- package/dist/{factory-PK4EZL7K.js → factory-STNVY3Y3.js} +4 -4
- package/dist/{factory-DOWBGVAL.js → factory-W6KROBFN.js} +5 -5
- package/dist/index.d.ts +104 -60
- package/dist/index.js +100 -105
- package/dist/index.js.map +1 -1
- package/dist/{init-opencode-QP5CAMWN.js → init-opencode-6LVZ4CAQ.js} +5 -5
- package/dist/{issue-triage-T3SKNBH5.js → issue-triage-Y77JI7WF.js} +4 -4
- package/dist/{registry-command-TH7U6UMC.js → registry-command-P5VIAEOL.js} +2 -2
- package/dist/{repo-analyze-D2OY2QSR.js → repo-analyze-JZEMBE6R.js} +2 -2
- package/dist/{repo-security-plan-V257RYTW.js → repo-security-plan-RHSLO7H6.js} +4 -4
- package/dist/{research-helpers-synthesize-Y3O76PY4.js → research-helpers-synthesize-PVP6JRZV.js} +3 -3
- package/dist/{routing-memory-AAH7NIHD.js → routing-memory-QRIJPRVD.js} +2 -2
- package/dist/{session-memory-MY6YS2VX.js → session-memory-WARRGYY7.js} +3 -3
- package/dist/{setup-command-B6EC3OJA.js → setup-command-GMP5FI7F.js} +10 -10
- package/dist/{setup-config-HVO6ZSLW.js → setup-config-G3KKZM7O.js} +3 -3
- package/dist/{setup-custom-api-UOIKUQL4.js → setup-custom-api-B63X7ISD.js} +4 -4
- package/dist/{tool-memory-T7ZYIUJ2.js → tool-memory-E7JW4YLT.js} +4 -4
- package/dist/{weather-report-O3Z3BBAX.js → weather-report-RACZWJQL.js} +2 -2
- package/package.json +1 -1
- package/dist/chunk-4XNVJS5A.js.map +0 -1
- package/dist/chunk-7J7PNOJQ.js.map +0 -1
- package/dist/chunk-AP2FD37C.js +0 -127
- package/dist/chunk-AP2FD37C.js.map +0 -1
- package/dist/chunk-BQ4YXGGQ.js.map +0 -1
- package/dist/chunk-ED6VQWNG.js +0 -63
- package/dist/chunk-ED6VQWNG.js.map +0 -1
- package/dist/chunk-FTT2IYYX.js.map +0 -1
- package/dist/chunk-HMXQKDUV.js.map +0 -1
- package/dist/codebase-search-PIBRTGBE.js +0 -9
- package/dist/expert-bridge-DJM5GAWZ.js +0 -11
- package/dist/shared-memory-CM6T2MYE.js +0 -8
- package/dist/symbol-extractor-WYXPJH65.js +0 -10
- package/dist/tool-memory-T7ZYIUJ2.js.map +0 -1
- package/dist/weather-report-O3Z3BBAX.js.map +0 -1
- /package/dist/{child-mcp-config-BMSYR7VV.js.map → child-mcp-config-XVPIO5HV.js.map} +0 -0
- /package/dist/{chunk-OYDJ3C4N.js.map → chunk-2W4QJ27D.js.map} +0 -0
- /package/dist/{chunk-C7P2HLJX.js.map → chunk-3ASED5PR.js.map} +0 -0
- /package/dist/{chunk-TXIUCEFT.js.map → chunk-3GJAAENS.js.map} +0 -0
- /package/dist/{chunk-GNG7URCR.js.map → chunk-3WUVWZUC.js.map} +0 -0
- /package/dist/{chunk-SBZVRH4S.js.map → chunk-54UXPJVM.js.map} +0 -0
- /package/dist/{chunk-QHL4KGNB.js.map → chunk-5T6SYCH2.js.map} +0 -0
- /package/dist/{chunk-BL5IBHEY.js.map → chunk-6WDJ3IE4.js.map} +0 -0
- /package/dist/{chunk-2R5UUBGA.js.map → chunk-AFPHR72D.js.map} +0 -0
- /package/dist/{chunk-S36LIUV2.js.map → chunk-AINOJRZX.js.map} +0 -0
- /package/dist/{chunk-HJUHDPXJ.js.map → chunk-BZUEUD4G.js.map} +0 -0
- /package/dist/{chunk-XGUDCUMB.js.map → chunk-FI77TGBY.js.map} +0 -0
- /package/dist/{chunk-PZESEBD3.js.map → chunk-L362KRSU.js.map} +0 -0
- /package/dist/{chunk-JM3R267Z.js.map → chunk-PPBZQKRP.js.map} +0 -0
- /package/dist/{chunk-WUUEKFKG.js.map → chunk-Q6PZMGLU.js.map} +0 -0
- /package/dist/{chunk-W2AIGD35.js.map → chunk-RVRXKNND.js.map} +0 -0
- /package/dist/{chunk-HVZ52LOL.js.map → chunk-U6LDVJS7.js.map} +0 -0
- /package/dist/{chunk-X3JR3GMT.js.map → chunk-WGSRH5EQ.js.map} +0 -0
- /package/dist/{chunk-SXL744NF.js.map → chunk-WJFUTRHX.js.map} +0 -0
- /package/dist/{chunk-U7JXQSEM.js.map → chunk-YO27YAEX.js.map} +0 -0
- /package/dist/{chunk-G2DZBEMU.js.map → chunk-Z64MSLEK.js.map} +0 -0
- /package/dist/{chunk-NTLJ3INA.js.map → chunk-ZIWEEVMI.js.map} +0 -0
- /package/dist/{chunk-SHWGK7X6.js.map → chunk-ZM5JKJHI.js.map} +0 -0
- /package/dist/{cli-circuit-breaker-BIJUQRQI.js.map → cli-circuit-breaker-Y26NPPNO.js.map} +0 -0
- /package/dist/{codebase-search-PIBRTGBE.js.map → codebase-search-ZFJUVMVR.js.map} +0 -0
- /package/dist/{composite-router-FB7P22L5.js.map → composite-router-X2ZYIEHH.js.map} +0 -0
- /package/dist/{consensus-vote-KKAIFULI.js.map → consensus-vote-KZ6UURUI.js.map} +0 -0
- /package/dist/{context-retriever-4JCGMWH7.js.map → context-retriever-QY4FNTDZ.js.map} +0 -0
- /package/dist/{doctor-deep-7YK4BZIJ.js.map → doctor-deep-2E2GBMYR.js.map} +0 -0
- /package/dist/{expert-bridge-DJM5GAWZ.js.map → expert-bridge-FHPWDFJX.js.map} +0 -0
- /package/dist/{factory-DOWBGVAL.js.map → factory-STNVY3Y3.js.map} +0 -0
- /package/dist/{factory-PK4EZL7K.js.map → factory-W6KROBFN.js.map} +0 -0
- /package/dist/{init-opencode-QP5CAMWN.js.map → init-opencode-6LVZ4CAQ.js.map} +0 -0
- /package/dist/{issue-triage-T3SKNBH5.js.map → issue-triage-Y77JI7WF.js.map} +0 -0
- /package/dist/{registry-command-TH7U6UMC.js.map → registry-command-P5VIAEOL.js.map} +0 -0
- /package/dist/{repo-analyze-D2OY2QSR.js.map → repo-analyze-JZEMBE6R.js.map} +0 -0
- /package/dist/{repo-security-plan-V257RYTW.js.map → repo-security-plan-RHSLO7H6.js.map} +0 -0
- /package/dist/{research-helpers-synthesize-Y3O76PY4.js.map → research-helpers-synthesize-PVP6JRZV.js.map} +0 -0
- /package/dist/{routing-memory-AAH7NIHD.js.map → routing-memory-QRIJPRVD.js.map} +0 -0
- /package/dist/{session-memory-MY6YS2VX.js.map → session-memory-WARRGYY7.js.map} +0 -0
- /package/dist/{setup-command-B6EC3OJA.js.map → setup-command-GMP5FI7F.js.map} +0 -0
- /package/dist/{setup-config-HVO6ZSLW.js.map → setup-config-G3KKZM7O.js.map} +0 -0
- /package/dist/{setup-custom-api-UOIKUQL4.js.map → setup-custom-api-B63X7ISD.js.map} +0 -0
- /package/dist/{shared-memory-CM6T2MYE.js.map → tool-memory-E7JW4YLT.js.map} +0 -0
- /package/dist/{symbol-extractor-WYXPJH65.js.map → weather-report-RACZWJQL.js.map} +0 -0
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/mcp/tools/repo-analyze.ts"],"sourcesContent":["/* eslint-disable max-lines -- cohesive module, governance allows 400-600 */\n/**\n * nexus-agents/mcp - Repository Analyze Logic\n *\n * Inspects a GitHub repository and returns structured analysis\n * including language, tooling, CI, security, and gap identification.\n *\n * @module mcp/tools/repo-analyze\n * (Source: Issue #1074)\n */\n\nimport type { RepoAnalyzeInput, RepoAnalysis } from './repo-analyze-types.js';\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\n/** Normalize \"owner/repo\" from either \"owner/repo\" or full GitHub URL. */\nexport function normalizeRepoId(input: string): string {\n const urlMatch = /github\\.com\\/([^/]+\\/[^/]+?)(?:\\.git)?(?:\\/|$)/.exec(input);\n const matched = urlMatch?.[1] ?? '';\n if (matched.length > 0) return matched;\n if (/^[^/]+\\/[^/]+$/.test(input)) return input;\n throw new Error(`Invalid repo format: \"${input}\". Use \"owner/name\" or a GitHub URL.`);\n}\n\n/** Package manager detection rules: [files, manager]. First match wins. */\nconst PACKAGE_MANAGER_RULES: ReadonlyArray<readonly [readonly string[], string]> = [\n [['pnpm-lock.yaml'], 'pnpm'],\n [['yarn.lock'], 'yarn'],\n [['package-lock.json', 'package.json'], 'npm'],\n [['Cargo.toml'], 'cargo'],\n [['go.mod'], 'go'],\n [['requirements.txt', 'pyproject.toml'], 'pip'],\n [['Gemfile'], 'bundler'],\n [['pom.xml'], 'maven'],\n [['build.gradle', 'build.gradle.kts'], 'gradle'],\n];\n\n/** Detect package manager from top-level files. */\nexport function detectPackageManager(entries: readonly string[]): string | null {\n for (const [files, manager] of PACKAGE_MANAGER_RULES) {\n if (files.some((f) => entries.includes(f))) return manager;\n }\n return null;\n}\n\n/** Detect CI provider from directory structure. */\nexport function detectCiProvider(entries: readonly string[]): string | null {\n if (entries.includes('.github')) return 'github-actions';\n if (entries.includes('.gitlab-ci.yml')) return 'gitlab-ci';\n if (entries.includes('Jenkinsfile')) return 'jenkins';\n if (entries.includes('.circleci')) return 'circleci';\n if (entries.includes('.travis.yml')) return 'travis';\n if (entries.includes('azure-pipelines.yml')) return 'azure-devops';\n if (entries.includes('concourse')) return 'concourse';\n return null;\n}\n\n/** Tool names detectable from CI workflow filenames (#1674). */\nconst WORKFLOW_SECURITY_PATTERNS: ReadonlyArray<readonly [string, string]> = [\n ['semgrep', 'semgrep'],\n ['codeql', 'codeql'],\n ['grype', 'grype'],\n ['snyk', 'snyk'],\n];\n\n/** Detect security tools from .github/workflows/ filenames. */\nfunction detectWorkflowSecurity(\n workflowEntries: readonly string[],\n existing: readonly string[]\n): readonly string[] {\n const wfLower = workflowEntries.map((w) => w.toLowerCase());\n const found: string[] = [];\n for (const [pattern, tool] of WORKFLOW_SECURITY_PATTERNS) {\n if (!existing.includes(tool) && wfLower.some((w) => w.includes(pattern))) {\n found.push(tool);\n }\n }\n return found;\n}\n\n/** Root-level config files that imply a security tool is in use. */\nconst ROOT_SECURITY_FILES: ReadonlyArray<readonly [readonly string[], string]> = [\n [['.semgrep.yml', '.semgrep'], 'semgrep'],\n [['.snyk'], 'snyk'],\n [['SECURITY.md'], 'security-policy'],\n [['.grype.yaml'], 'grype'],\n [['CODEOWNERS'], 'codeowners'],\n // gitleaks (#2732). Catches `.gitleaks.toml` (canonical) plus legacy\n // `gitleaks.toml` / `.gitleaksignore` variants. Without this, a repo\n // carrying `.gitleaks.toml` reported `existingTooling` without gitleaks,\n // and `repo_security_plan` then showed `secrets: covered: true,\n // scanners: []` (covered by existing-but-undetected tooling).\n [['.gitleaks.toml', 'gitleaks.toml', '.gitleaksignore'], 'gitleaks'],\n];\n\n/** Detect security tooling from root files and CI workflow filenames (#1674). */\nexport function detectSecurityTooling(\n entries: readonly string[],\n workflowEntries?: readonly string[]\n): readonly string[] {\n const tools: string[] = [];\n for (const [files, tool] of ROOT_SECURITY_FILES) {\n if (files.some((f) => entries.includes(f))) tools.push(tool);\n }\n if (workflowEntries !== undefined) {\n tools.push(...detectWorkflowSecurity(workflowEntries, tools));\n }\n return tools;\n}\n\n/** Detect framework from package manager config. */\nexport function detectFramework(entries: readonly string[]): string | null {\n if (entries.includes('helmfile.yaml') || entries.includes('helmfile.yaml.gotmpl'))\n return 'helmfile';\n if (entries.includes('next.config.js') || entries.includes('next.config.ts')) return 'nextjs';\n if (entries.includes('angular.json')) return 'angular';\n if (entries.includes('vite.config.ts') || entries.includes('vite.config.js')) return 'vite';\n if (entries.includes('tsconfig.json') && entries.includes('package.json')) return 'typescript';\n return null;\n}\n\n/** Gap detection rules: [files-any-present, gap message]. */\nconst GAP_RULES: ReadonlyArray<readonly [readonly string[], string]> = [\n [['SECURITY.md'], 'No SECURITY.md policy'],\n [['CODEOWNERS'], 'No CODEOWNERS file'],\n [['LICENSE', 'LICENSE.md'], 'No LICENSE file'],\n [\n ['.semgrep.yml', '.semgrep', '.grype.yaml', '.snyk'],\n 'No SAST/SCA security scanning configured',\n ],\n // Test detection handled separately via detectTestInfra (supports monorepo + co-located patterns)\n [['.gitignore'], 'No .gitignore file'],\n];\n\n/** Scanner recommendation per language. Canonical source: secure-language-stacks. */\ninterface LanguageScanners {\n readonly sast: readonly string[];\n readonly sca: readonly string[];\n}\n\nconst LANGUAGE_SCANNER_MATRIX: Readonly<Record<string, LanguageScanners>> = {\n TypeScript: {\n sast: ['semgrep (p/typescript, p/nodejs)', 'eslint-plugin-security'],\n sca: ['osv-scanner', 'npm audit'],\n },\n JavaScript: {\n sast: ['semgrep (p/javascript, p/nodejs)', 'eslint-plugin-security'],\n sca: ['osv-scanner', 'npm audit'],\n },\n Python: {\n sast: ['semgrep (p/python)', 'bandit'],\n sca: ['osv-scanner', 'pip-audit'],\n },\n Java: {\n sast: ['semgrep (p/java)', 'spotbugs + find-sec-bugs'],\n sca: ['osv-scanner', 'OWASP dependency-check'],\n },\n Go: {\n sast: ['semgrep (p/golang)', 'gosec'],\n sca: ['osv-scanner', 'govulncheck'],\n },\n Rust: {\n sast: ['semgrep (p/rust)'],\n sca: ['osv-scanner', 'cargo-audit'],\n },\n 'C++': {\n sast: ['semgrep (p/c)', 'cppcheck'],\n sca: ['osv-scanner'],\n },\n C: {\n sast: ['semgrep (p/c)', 'cppcheck'],\n sca: ['osv-scanner'],\n },\n Kotlin: {\n sast: ['semgrep (p/kotlin)', 'detekt'],\n sca: ['osv-scanner', 'OWASP dependency-check'],\n },\n Swift: {\n sast: ['semgrep (p/swift)'],\n sca: ['osv-scanner'],\n },\n Ruby: {\n sast: ['semgrep (p/ruby)', 'brakeman'],\n sca: ['osv-scanner', 'bundler-audit'],\n },\n PHP: {\n sast: ['semgrep (p/php)', 'phpstan'],\n sca: ['osv-scanner', 'composer audit'],\n },\n Shell: {\n sast: ['semgrep (p/bash)', 'shellcheck'],\n sca: [],\n },\n HCL: {\n sast: ['semgrep (p/terraform)', 'tfsec'],\n sca: ['osv-scanner'],\n },\n};\n\n/** Generate language-specific scanner recommendations when SAST/SCA is missing. */\nexport function getLanguageRecommendations(\n language: string | null,\n securityTooling: readonly string[]\n): readonly string[] {\n if (language === null) return [];\n const scanners = LANGUAGE_SCANNER_MATRIX[language];\n if (scanners === undefined) return [];\n\n const hasSast = securityTooling.includes('semgrep') || securityTooling.includes('snyk');\n const hasSca =\n securityTooling.includes('osv-scanner') ||\n securityTooling.includes('grype') ||\n securityTooling.includes('snyk');\n\n const recs: string[] = [];\n if (!hasSast && scanners.sast.length > 0) {\n const tools = scanners.sast.join(', ');\n recs.push(`${language} project missing SAST: ${tools}`);\n }\n if (!hasSca && scanners.sca.length > 0) {\n const tools = scanners.sca.join(', ');\n recs.push(`${language} project missing SCA: ${tools}`);\n }\n return recs;\n}\n\n/** SAST tool names that suppress the generic gap message. */\nconst SAST_TOOLS = new Set(['semgrep', 'codeql', 'snyk']);\nconst SAST_GAP_MSG = 'No SAST/SCA security scanning configured';\n\n/** Remove the SAST/SCA gap if any SAST tool was detected (#1674). */\nfunction removeSastGapIfToolDetected(gaps: string[], secTools: readonly string[]): void {\n if (secTools.some((t) => SAST_TOOLS.has(t))) {\n const idx = gaps.indexOf(SAST_GAP_MSG);\n if (idx !== -1) gaps.splice(idx, 1);\n }\n}\n\n/** Identify gaps in repository best practices. */\nexport function identifyGaps(\n entries: readonly string[],\n ciProvider: string | null,\n language?: string | null,\n securityTooling?: readonly string[]\n): readonly string[] {\n const gaps: string[] = [];\n if (ciProvider === null) gaps.push('No CI/CD configuration detected');\n for (const [files, message] of GAP_RULES) {\n if (!files.some((f) => entries.includes(f))) gaps.push(message);\n }\n // Test detection: uses detectTestInfra for monorepo + co-located pattern support (#1130)\n if (!detectTestInfra(entries)) gaps.push('No test directory detected');\n\n // Remove SAST/SCA gap if workflow-level security was detected (#1674)\n removeSastGapIfToolDetected(gaps, securityTooling ?? []);\n\n // Language-specific recommendations when generic SAST/SCA gap detected\n const hasGenericSecGap = gaps.includes('No SAST/SCA security scanning configured');\n if (\n hasGenericSecGap &&\n language !== null &&\n language !== undefined &&\n securityTooling !== undefined\n ) {\n const langRecs = getLanguageRecommendations(language, securityTooling);\n gaps.push(...langRecs);\n }\n\n return gaps;\n}\n\n// ============================================================================\n// Core\n// ============================================================================\n\n/** GitHub repo metadata from the API. */\nexport interface GhRepoMetadata {\n readonly name: string;\n readonly full_name: string;\n readonly description: string | null;\n readonly language: string | null;\n readonly default_branch: string;\n readonly stargazers_count: number;\n readonly license: { readonly spdx_id: string } | null;\n}\n\n/** Analyze a GitHub repository given its metadata and file tree. */\nexport function analyzeRepo(\n metadata: GhRepoMetadata,\n topLevelEntries: readonly string[],\n workflowEntries?: readonly string[]\n): RepoAnalysis {\n const ciProvider = detectCiProvider(topLevelEntries);\n const secTooling = detectSecurityTooling(topLevelEntries, workflowEntries);\n const hasTests = detectTestInfra(topLevelEntries);\n\n return {\n name: metadata.full_name,\n language: metadata.language,\n framework: detectFramework(topLevelEntries),\n packageManager: detectPackageManager(topLevelEntries),\n ciProvider,\n securityTooling: secTooling,\n // Match `Dockerfile`, `Dockerfile.<purpose>` (e.g. `Dockerfile.sandbox`),\n // and docker-compose variants. Pre-#2730 the check was exact-match only,\n // so a repo with three legitimate `Dockerfile.*` files reported false.\n hasDockerfile:\n topLevelEntries.some((e) => e === 'Dockerfile' || e.startsWith('Dockerfile.')) ||\n topLevelEntries.includes('docker-compose.yml') ||\n topLevelEntries.includes('docker-compose.yaml'),\n hasHelmCharts:\n topLevelEntries.includes('Chart.yaml') ||\n topLevelEntries.includes('charts') ||\n topLevelEntries.includes('helm'),\n hasMakefile: topLevelEntries.includes('Makefile'),\n hasTests,\n license: metadata.license?.spdx_id ?? null,\n description: metadata.description,\n defaultBranch: metadata.default_branch,\n stars: metadata.stargazers_count,\n topLevelEntries: [...topLevelEntries],\n gaps: identifyGaps(topLevelEntries, ciProvider, metadata.language, secTooling),\n };\n}\n\n/** Non-code languages to exclude when detecting primary language. */\nconst MARKUP_LANGUAGES = new Set([\n 'HTML',\n 'CSS',\n 'SCSS',\n 'Less',\n 'Markdown',\n 'Roff',\n 'SVG',\n 'XML',\n 'XSLT',\n 'Mustache',\n 'Handlebars',\n 'EJS',\n]);\n\n/** Detect primary language from GitHub languages API (byte counts). */\nfunction detectPrimaryLanguage(\n languages: Record<string, number>,\n fallback: string | null\n): string | null {\n const sorted = Object.entries(languages)\n .filter(([lang]) => !MARKUP_LANGUAGES.has(lang))\n .sort((a, b) => b[1] - a[1]);\n const top = sorted[0];\n return top !== undefined ? top[0] : fallback;\n}\n\n/** Check for test infrastructure beyond top-level directories. */\nfunction detectTestInfra(entries: readonly string[]): boolean {\n const testDirs = ['tests', 'test', '__tests__', 'spec'];\n if (testDirs.some((d) => entries.includes(d))) return true;\n // Check for test config files (co-located test pattern, monorepos)\n const testConfigs = [\n 'vitest.config.ts',\n 'vitest.config.js',\n 'vitest.config.mts',\n 'vitest.workspace.ts',\n 'vitest.workspace.js',\n 'jest.config.ts',\n 'jest.config.js',\n 'jest.config.mjs',\n 'cypress.config.ts',\n 'cypress.config.js',\n 'playwright.config.ts',\n '.mocharc.yml',\n '.mocharc.json',\n ];\n if (testConfigs.some((c) => entries.includes(c))) return true;\n // Monorepo: packages/ dir + package.json implies co-located tests\n return entries.includes('packages') && entries.includes('package.json');\n}\n\n/** Infer code language from project files when GitHub reports markup. */\nfunction inferLanguageFromEntries(\n entries: readonly string[],\n fallback: string | null\n): string | null {\n if (entries.includes('tsconfig.json')) return 'TypeScript';\n if (entries.includes('Cargo.toml')) return 'Rust';\n if (entries.includes('go.mod')) return 'Go';\n if (entries.includes('pyproject.toml') || entries.includes('setup.py')) return 'Python';\n if (entries.includes('pom.xml') || entries.includes('build.gradle')) return 'Java';\n if (entries.includes('Gemfile')) return 'Ruby';\n if (entries.includes('package.json')) return 'JavaScript';\n return fallback;\n}\n\ntype ExecFileFn = (\n cmd: string,\n args: string[],\n options?: { timeout?: number }\n) => Promise<{ stdout: string }>;\n\n/** Lazy-load promisified execFile. */\nasync function getExecFile(): Promise<ExecFileFn> {\n const { execFile } = await import('node:child_process');\n const { promisify } = await import('node:util');\n return promisify(execFile);\n}\n\n/** Fetch repo metadata and languages via GitHub API. */\nasync function fetchRepoData(\n repoId: string,\n exec: ExecFileFn\n): Promise<{ metadata: GhRepoMetadata; entries: string[] }> {\n const { stdout: metaJson } = await exec(\n 'gh',\n [\n 'api',\n `repos/${repoId}`,\n '--jq',\n '{name: .name, full_name: .full_name, description: .description, language: .language, default_branch: .default_branch, stargazers_count: .stargazers_count, license: .license}',\n ],\n { timeout: 30_000 }\n );\n let metadata: GhRepoMetadata;\n try {\n metadata = JSON.parse(metaJson.trim()) as GhRepoMetadata;\n } catch {\n throw new Error(`Failed to parse repo metadata for ${repoId}: ${metaJson.slice(0, 200)}`);\n }\n const { stdout: contentsJson } = await exec(\n 'gh',\n ['api', `repos/${repoId}/contents`, '--jq', '[.[].name]'],\n { timeout: 30_000 }\n );\n let entries: string[];\n try {\n const parsed: unknown = JSON.parse(contentsJson.trim());\n entries = Array.isArray(parsed) ? parsed.filter((e): e is string => typeof e === 'string') : [];\n } catch {\n throw new Error(`Failed to parse repo contents for ${repoId}: ${contentsJson.slice(0, 200)}`);\n }\n return { metadata, entries };\n}\n\n/** Resolve NOASSERTION license via the GitHub license API. */\nasync function resolveLicense(repoId: string, exec: ExecFileFn): Promise<string | null> {\n try {\n const { stdout } = await exec(\n 'gh',\n ['api', `repos/${repoId}/license`, '--jq', '.license.spdx_id'],\n { timeout: 15_000 }\n );\n const spdxId = stdout.trim();\n if (spdxId !== '' && spdxId !== 'null' && spdxId !== 'NOASSERTION') {\n return spdxId;\n }\n } catch {\n // Keep NOASSERTION if license API also fails\n }\n return null;\n}\n\n/** Resolve primary language, falling back to entry inference for markup repos. */\nasync function resolveLanguage(\n repoId: string,\n entries: readonly string[],\n metadata: GhRepoMetadata,\n exec: ExecFileFn\n): Promise<string | null> {\n let languages: Record<string, number> = {};\n try {\n const { stdout } = await exec('gh', ['api', `repos/${repoId}/languages`], { timeout: 15_000 });\n languages = JSON.parse(stdout.trim()) as Record<string, number>;\n } catch {\n /* fall back to metadata.language */\n }\n const primary = detectPrimaryLanguage(languages, metadata.language);\n if (primary === null || MARKUP_LANGUAGES.has(primary)) {\n return inferLanguageFromEntries(entries, primary);\n }\n return primary;\n}\n\n/** Fetch workflow filenames from .github/workflows/ (#1674). Best-effort. */\nasync function fetchWorkflowEntries(repoId: string, exec: ExecFileFn): Promise<readonly string[]> {\n try {\n const { stdout } = await exec(\n 'gh',\n ['api', `repos/${repoId}/contents/.github/workflows`, '--jq', '[.[].name]'],\n { timeout: 15_000 }\n );\n const parsed: unknown = JSON.parse(stdout.trim());\n return Array.isArray(parsed) ? parsed.filter((e): e is string => typeof e === 'string') : [];\n } catch {\n return []; // No workflows directory or API error — graceful fallback\n }\n}\n\n/** Fetch repo data from GitHub and produce analysis. */\nexport async function analyzeGitHubRepo(input: RepoAnalyzeInput): Promise<RepoAnalysis> {\n const repoId = normalizeRepoId(input.repo);\n const exec = await getExecFile();\n const { metadata, entries } = await fetchRepoData(repoId, exec);\n\n const primaryLang = await resolveLanguage(repoId, entries, metadata, exec);\n const enhanced = { ...metadata, language: primaryLang };\n\n // Resolve null or NOASSERTION license when LICENSE file exists\n const hasLicenseFile = entries.includes('LICENSE') || entries.includes('LICENSE.md');\n const licenseUnresolved = enhanced.license === null || enhanced.license.spdx_id === 'NOASSERTION';\n if (licenseUnresolved && hasLicenseFile) {\n const resolved = await resolveLicense(repoId, exec);\n if (resolved !== null) enhanced.license = { spdx_id: resolved };\n }\n\n // Fetch workflow filenames for CI-level security detection (#1674)\n const workflowEntries = entries.includes('.github')\n ? await fetchWorkflowEntries(repoId, exec)\n : [];\n\n return analyzeRepo(enhanced, entries, workflowEntries);\n}\n"],"mappings":"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","names":[]}
|
package/dist/chunk-AP2FD37C.js
DELETED
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
extractSymbols
|
|
3
|
-
} from "./chunk-BQ4YXGGQ.js";
|
|
4
|
-
|
|
5
|
-
// src/indexer/codebase-search.ts
|
|
6
|
-
import { readdir } from "fs/promises";
|
|
7
|
-
import { resolve, extname, relative } from "path";
|
|
8
|
-
var SCORE_EXACT = 20;
|
|
9
|
-
var SCORE_PREFIX = 10;
|
|
10
|
-
var SCORE_WORD = 5;
|
|
11
|
-
var SCORE_SUBSTRING = 2;
|
|
12
|
-
var SCORE_EXPORTED_BONUS = 3;
|
|
13
|
-
function isSourceFile(name) {
|
|
14
|
-
const ext = extname(name).toLowerCase();
|
|
15
|
-
return [".ts", ".tsx", ".js", ".jsx"].includes(ext) && !name.endsWith(".test.ts") && !name.endsWith(".test.tsx") && !name.endsWith(".d.ts");
|
|
16
|
-
}
|
|
17
|
-
async function findSourceFiles(dir, maxDepth) {
|
|
18
|
-
if (maxDepth <= 0) return [];
|
|
19
|
-
const files = [];
|
|
20
|
-
const entries = await readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
21
|
-
for (const entry of entries) {
|
|
22
|
-
const fullPath = resolve(dir, entry.name);
|
|
23
|
-
if (entry.isDirectory() && entry.name !== "node_modules" && entry.name !== "dist") {
|
|
24
|
-
files.push(...await findSourceFiles(fullPath, maxDepth - 1));
|
|
25
|
-
}
|
|
26
|
-
if (entry.isFile() && isSourceFile(entry.name)) {
|
|
27
|
-
files.push(fullPath);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
return files;
|
|
31
|
-
}
|
|
32
|
-
function scoreMatch(symbolName, query) {
|
|
33
|
-
const nameLower = symbolName.toLowerCase();
|
|
34
|
-
const queryLower = query.toLowerCase();
|
|
35
|
-
if (nameLower === queryLower) return SCORE_EXACT;
|
|
36
|
-
if (nameLower.startsWith(queryLower)) return SCORE_PREFIX;
|
|
37
|
-
const words = symbolName.replace(/([a-z])([A-Z])/g, "$1 $2").toLowerCase().split(/[\s_-]+/);
|
|
38
|
-
if (words.some((w) => w === queryLower)) return SCORE_WORD;
|
|
39
|
-
if (nameLower.includes(queryLower)) return SCORE_SUBSTRING;
|
|
40
|
-
return null;
|
|
41
|
-
}
|
|
42
|
-
function getMatchType(score) {
|
|
43
|
-
if (score >= SCORE_EXACT) return "exact";
|
|
44
|
-
if (score >= SCORE_PREFIX) return "prefix";
|
|
45
|
-
if (score >= SCORE_WORD) return "word";
|
|
46
|
-
return "substring";
|
|
47
|
-
}
|
|
48
|
-
var CodebaseIndex = class {
|
|
49
|
-
symbols = [];
|
|
50
|
-
fileResults = /* @__PURE__ */ new Map();
|
|
51
|
-
rootDir;
|
|
52
|
-
constructor(rootDir) {
|
|
53
|
-
this.rootDir = rootDir;
|
|
54
|
-
}
|
|
55
|
-
/** Index all TS/JS source files in the directory. */
|
|
56
|
-
async index(maxDepth = 4) {
|
|
57
|
-
const files = await findSourceFiles(this.rootDir, maxDepth);
|
|
58
|
-
for (const file of files) {
|
|
59
|
-
const result = await extractSymbols(file);
|
|
60
|
-
const relPath = relative(this.rootDir, file);
|
|
61
|
-
this.fileResults.set(relPath, result);
|
|
62
|
-
for (const symbol of result.symbols) {
|
|
63
|
-
this.symbols.push({ ...symbol, filePath: relPath });
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
return {
|
|
67
|
-
totalFiles: files.length,
|
|
68
|
-
totalSymbols: this.symbols.length,
|
|
69
|
-
indexedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
/** Search symbols by keyword. Returns top N results sorted by relevance. */
|
|
73
|
-
search(query, limit = 20) {
|
|
74
|
-
const results = [];
|
|
75
|
-
for (const symbol of this.symbols) {
|
|
76
|
-
const baseScore = scoreMatch(symbol.name, query);
|
|
77
|
-
if (baseScore === null) continue;
|
|
78
|
-
const bonus = symbol.exported ? SCORE_EXPORTED_BONUS : 0;
|
|
79
|
-
results.push({
|
|
80
|
-
symbol,
|
|
81
|
-
score: baseScore + bonus,
|
|
82
|
-
matchType: getMatchType(baseScore)
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
|
-
return results.sort((a, b) => b.score - a.score).slice(0, limit);
|
|
86
|
-
}
|
|
87
|
-
/** Get a compact summary of a file's symbols. */
|
|
88
|
-
getFileSummary(filePath) {
|
|
89
|
-
const result = this.fileResults.get(filePath);
|
|
90
|
-
if (result === void 0) return void 0;
|
|
91
|
-
const kinds = {};
|
|
92
|
-
let exported = 0;
|
|
93
|
-
let priv = 0;
|
|
94
|
-
for (const s of result.symbols) {
|
|
95
|
-
kinds[s.kind] = (kinds[s.kind] ?? 0) + 1;
|
|
96
|
-
if (s.exported) exported++;
|
|
97
|
-
else priv++;
|
|
98
|
-
}
|
|
99
|
-
return {
|
|
100
|
-
filePath,
|
|
101
|
-
totalLines: result.totalLines,
|
|
102
|
-
exportedSymbols: exported,
|
|
103
|
-
privateSymbols: priv,
|
|
104
|
-
kinds
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
/** List all indexed files with symbol counts. */
|
|
108
|
-
listFiles() {
|
|
109
|
-
return [...this.fileResults.entries()].map(([path, result]) => ({
|
|
110
|
-
path,
|
|
111
|
-
symbols: result.symbols.length,
|
|
112
|
-
lines: result.totalLines
|
|
113
|
-
}));
|
|
114
|
-
}
|
|
115
|
-
/** Get index statistics. */
|
|
116
|
-
get stats() {
|
|
117
|
-
return {
|
|
118
|
-
files: this.fileResults.size,
|
|
119
|
-
symbols: this.symbols.length
|
|
120
|
-
};
|
|
121
|
-
}
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
export {
|
|
125
|
-
CodebaseIndex
|
|
126
|
-
};
|
|
127
|
-
//# sourceMappingURL=chunk-AP2FD37C.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/indexer/codebase-search.ts"],"sourcesContent":["/**\n * Codebase search — keyword search across symbol indices.\n *\n * Builds an in-memory symbol index for a directory of TS/JS files,\n * then supports keyword search, file summaries, and symbol lookup.\n *\n * Inspired by Augment Code's Context Engine. Uses the existing\n * extractSymbols() function for AST parsing.\n *\n * @module indexer/codebase-search\n */\n\nimport { readdir } from 'node:fs/promises';\nimport { resolve, extname, relative } from 'node:path';\nimport {\n extractSymbols,\n type CodeSymbol,\n type SymbolExtractionResult,\n} from './symbol-extractor.js';\n\n/** A symbol with its source file path. */\nexport interface IndexedSymbol extends CodeSymbol {\n /** Relative file path from the indexed root. */\n filePath: string;\n}\n\n/** Search result with relevance scoring. */\nexport interface SearchResult {\n symbol: IndexedSymbol;\n /** Relevance score (higher = better match). */\n score: number;\n /** How the query matched (exact, prefix, substring, word). */\n matchType: 'exact' | 'prefix' | 'substring' | 'word';\n}\n\n/** File summary — compact overview of a source file. */\nexport interface FileSummary {\n filePath: string;\n totalLines: number;\n exportedSymbols: number;\n privateSymbols: number;\n kinds: Record<string, number>;\n}\n\n/** Index statistics. */\nexport interface IndexStats {\n totalFiles: number;\n totalSymbols: number;\n indexedAt: string;\n}\n\n// Score weights for different match types\nconst SCORE_EXACT = 20;\nconst SCORE_PREFIX = 10;\nconst SCORE_WORD = 5;\nconst SCORE_SUBSTRING = 2;\nconst SCORE_EXPORTED_BONUS = 3;\n\nfunction isSourceFile(name: string): boolean {\n const ext = extname(name).toLowerCase();\n return (\n ['.ts', '.tsx', '.js', '.jsx'].includes(ext) &&\n !name.endsWith('.test.ts') &&\n !name.endsWith('.test.tsx') &&\n !name.endsWith('.d.ts')\n );\n}\n\nasync function findSourceFiles(dir: string, maxDepth: number): Promise<string[]> {\n if (maxDepth <= 0) return [];\n const files: string[] = [];\n const entries = await readdir(dir, { withFileTypes: true }).catch(() => []);\n for (const entry of entries) {\n const fullPath = resolve(dir, entry.name);\n if (entry.isDirectory() && entry.name !== 'node_modules' && entry.name !== 'dist') {\n files.push(...(await findSourceFiles(fullPath, maxDepth - 1)));\n }\n if (entry.isFile() && isSourceFile(entry.name)) {\n files.push(fullPath);\n }\n }\n return files;\n}\n\nfunction scoreMatch(symbolName: string, query: string): SearchResult['score'] | null {\n const nameLower = symbolName.toLowerCase();\n const queryLower = query.toLowerCase();\n\n if (nameLower === queryLower) return SCORE_EXACT;\n if (nameLower.startsWith(queryLower)) return SCORE_PREFIX;\n\n // Word boundary match (camelCase splitting)\n const words = symbolName\n .replace(/([a-z])([A-Z])/g, '$1 $2')\n .toLowerCase()\n .split(/[\\s_-]+/);\n if (words.some((w) => w === queryLower)) return SCORE_WORD;\n\n if (nameLower.includes(queryLower)) return SCORE_SUBSTRING;\n\n return null;\n}\n\nfunction getMatchType(score: number): SearchResult['matchType'] {\n if (score >= SCORE_EXACT) return 'exact';\n if (score >= SCORE_PREFIX) return 'prefix';\n if (score >= SCORE_WORD) return 'word';\n return 'substring';\n}\n\n/** In-memory codebase symbol index. */\nexport class CodebaseIndex {\n private readonly symbols: IndexedSymbol[] = [];\n private readonly fileResults = new Map<string, SymbolExtractionResult>();\n private readonly rootDir: string;\n\n constructor(rootDir: string) {\n this.rootDir = rootDir;\n }\n\n /** Index all TS/JS source files in the directory. */\n async index(maxDepth = 4): Promise<IndexStats> {\n const files = await findSourceFiles(this.rootDir, maxDepth);\n\n for (const file of files) {\n const result = await extractSymbols(file);\n const relPath = relative(this.rootDir, file);\n this.fileResults.set(relPath, result);\n\n for (const symbol of result.symbols) {\n this.symbols.push({ ...symbol, filePath: relPath });\n }\n }\n\n return {\n totalFiles: files.length,\n totalSymbols: this.symbols.length,\n indexedAt: new Date().toISOString(),\n };\n }\n\n /** Search symbols by keyword. Returns top N results sorted by relevance. */\n search(query: string, limit = 20): SearchResult[] {\n const results: SearchResult[] = [];\n\n for (const symbol of this.symbols) {\n const baseScore = scoreMatch(symbol.name, query);\n if (baseScore === null) continue;\n\n const bonus = symbol.exported ? SCORE_EXPORTED_BONUS : 0;\n results.push({\n symbol,\n score: baseScore + bonus,\n matchType: getMatchType(baseScore),\n });\n }\n\n return results.sort((a, b) => b.score - a.score).slice(0, limit);\n }\n\n /** Get a compact summary of a file's symbols. */\n getFileSummary(filePath: string): FileSummary | undefined {\n const result = this.fileResults.get(filePath);\n if (result === undefined) return undefined;\n\n const kinds: Record<string, number> = {};\n let exported = 0;\n let priv = 0;\n\n for (const s of result.symbols) {\n kinds[s.kind] = (kinds[s.kind] ?? 0) + 1;\n if (s.exported) exported++;\n else priv++;\n }\n\n return {\n filePath,\n totalLines: result.totalLines,\n exportedSymbols: exported,\n privateSymbols: priv,\n kinds,\n };\n }\n\n /** List all indexed files with symbol counts. */\n listFiles(): Array<{ path: string; symbols: number; lines: number }> {\n return [...this.fileResults.entries()].map(([path, result]) => ({\n path,\n symbols: result.symbols.length,\n lines: result.totalLines,\n }));\n }\n\n /** Get index statistics. */\n get stats(): { files: number; symbols: number } {\n return {\n files: this.fileResults.size,\n symbols: this.symbols.length,\n };\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/indexer/symbol-extractor.ts"],"sourcesContent":["/**\n * AST symbol extraction for token-efficient code retrieval.\n *\n * Uses TypeScript's compiler API to extract function, class, method,\n * interface, and type definitions from source files.\n *\n * Token savings: ~80-99% vs reading full files.\n * No additional dependencies — uses TypeScript (already a project dep).\n *\n * @module indexer/symbol-extractor\n */\n\nimport { readFile } from 'node:fs/promises';\nimport { extname } from 'node:path';\nimport ts from 'typescript';\n\n/** A symbol extracted from source code. */\nexport interface CodeSymbol {\n /** Symbol name */\n name: string;\n /** Symbol kind */\n kind: 'function' | 'class' | 'method' | 'interface' | 'type' | 'variable' | 'enum';\n /** Start line (1-based) */\n startLine: number;\n /** End line (1-based) */\n endLine: number;\n /** Full source text of the symbol */\n text: string;\n /** Whether the symbol is exported */\n exported: boolean;\n}\n\n/** Result of extracting symbols from a file. */\nexport interface SymbolExtractionResult {\n filePath: string;\n symbols: CodeSymbol[];\n totalLines: number;\n totalChars: number;\n symbolChars: number;\n savingsPercent: number;\n}\n\nfunction getKind(node: ts.Node): CodeSymbol['kind'] | null {\n if (ts.isFunctionDeclaration(node)) return 'function';\n if (ts.isClassDeclaration(node)) return 'class';\n if (ts.isInterfaceDeclaration(node)) return 'interface';\n if (ts.isTypeAliasDeclaration(node)) return 'type';\n if (ts.isEnumDeclaration(node)) return 'enum';\n if (ts.isMethodDeclaration(node)) return 'method';\n if (ts.isVariableStatement(node)) return 'variable';\n return null;\n}\n\nfunction getName(node: ts.Node): string {\n if (\n ts.isFunctionDeclaration(node) ||\n ts.isClassDeclaration(node) ||\n ts.isInterfaceDeclaration(node) ||\n ts.isTypeAliasDeclaration(node) ||\n ts.isEnumDeclaration(node) ||\n ts.isMethodDeclaration(node)\n ) {\n const nameNode = (node as ts.NamedDeclaration).name;\n return nameNode ? nameNode.getText() : '<anonymous>';\n }\n if (ts.isVariableStatement(node)) {\n const decls = node.declarationList.declarations;\n const firstDecl = decls[0];\n if (firstDecl !== undefined) {\n return firstDecl.name.getText();\n }\n }\n return '<anonymous>';\n}\n\nfunction isExported(node: ts.Node): boolean {\n const modifiers = ts.canHaveModifiers(node) ? ts.getModifiers(node) : undefined;\n if (modifiers) {\n return modifiers.some((m) => m.kind === ts.SyntaxKind.ExportKeyword);\n }\n return false;\n}\n\nfunction visitNode(node: ts.Node, sourceFile: ts.SourceFile, symbols: CodeSymbol[]): void {\n const kind = getKind(node);\n if (kind !== null) {\n const name = getName(node);\n if (name !== '<anonymous>') {\n const start = sourceFile.getLineAndCharacterOfPosition(node.getStart());\n const end = sourceFile.getLineAndCharacterOfPosition(node.getEnd());\n symbols.push({\n name,\n kind,\n startLine: start.line + 1,\n endLine: end.line + 1,\n text: node.getText(sourceFile),\n exported: isExported(node),\n });\n }\n }\n if (ts.isClassDeclaration(node)) {\n visitClassMembers(node, sourceFile, symbols);\n return;\n }\n ts.forEachChild(node, (child) => {\n visitNode(child, sourceFile, symbols);\n });\n}\n\nfunction visitClassMembers(\n node: ts.ClassDeclaration,\n sourceFile: ts.SourceFile,\n symbols: CodeSymbol[]\n): void {\n for (const member of node.members) {\n if (ts.isMethodDeclaration(member) || ts.isPropertyDeclaration(member)) {\n const memberName = member.name.getText();\n if (memberName !== '<anonymous>') {\n const start = sourceFile.getLineAndCharacterOfPosition(member.getStart());\n const end = sourceFile.getLineAndCharacterOfPosition(member.getEnd());\n symbols.push({\n name: memberName,\n kind: 'method',\n startLine: start.line + 1,\n endLine: end.line + 1,\n text: member.getText(sourceFile),\n exported: false,\n });\n }\n }\n }\n}\n\nfunction computeSavings(totalChars: number, symbolChars: number): number {\n return totalChars > 0 ? Math.round(100 * (1 - symbolChars / totalChars) * 10) / 10 : 0;\n}\n\n/**\n * Extract symbols from a TypeScript/JavaScript file.\n */\nexport async function extractSymbols(filePath: string): Promise<SymbolExtractionResult> {\n const ext = extname(filePath).toLowerCase();\n if (!['.ts', '.tsx', '.js', '.jsx'].includes(ext)) {\n return {\n filePath,\n symbols: [],\n totalLines: 0,\n totalChars: 0,\n symbolChars: 0,\n savingsPercent: 0,\n };\n }\n\n const source = await readFile(filePath, 'utf-8');\n const sourceFile = ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true);\n const symbols: CodeSymbol[] = [];\n\n ts.forEachChild(sourceFile, (node) => {\n visitNode(node, sourceFile, symbols);\n });\n\n const totalChars = source.length;\n const symbolChars = symbols.reduce((sum, s) => sum + s.text.length, 0);\n\n return {\n filePath,\n symbols,\n totalLines: source.split('\\n').length,\n totalChars,\n symbolChars,\n savingsPercent: computeSavings(totalChars, symbolChars),\n };\n}\n\n/**\n * Extract a compact symbol index (names + locations only, no source text).\n * This is the minimal representation for LLM context — ~95%+ token savings.\n */\nexport async function extractSymbolIndex(filePath: string): Promise<string> {\n const result = await extractSymbols(filePath);\n if (result.symbols.length === 0) return '';\n\n const lines = result.symbols.map((s) => {\n const exp = s.exported ? 'export ' : '';\n return `${exp}${s.kind} ${s.name} (L${String(s.startLine)}-${String(s.endLine)})`;\n });\n\n return `// ${filePath} — ${String(result.symbols.length)} symbols\\n${lines.join('\\n')}`;\n}\n"],"mappings":"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","names":[]}
|
package/dist/chunk-ED6VQWNG.js
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
// src/pipeline/shared-memory.ts
|
|
2
|
-
var SharedMemoryStore = class {
|
|
3
|
-
entries = [];
|
|
4
|
-
maxEntries;
|
|
5
|
-
constructor(maxEntries = 100) {
|
|
6
|
-
this.maxEntries = maxEntries;
|
|
7
|
-
}
|
|
8
|
-
/** Write a memory entry. */
|
|
9
|
-
write(sourceStage, tag, content) {
|
|
10
|
-
if (this.entries.length >= this.maxEntries) {
|
|
11
|
-
this.entries.shift();
|
|
12
|
-
}
|
|
13
|
-
this.entries.push({
|
|
14
|
-
sourceStage,
|
|
15
|
-
tag,
|
|
16
|
-
content,
|
|
17
|
-
timestamp: Date.now()
|
|
18
|
-
});
|
|
19
|
-
}
|
|
20
|
-
/** Read all entries, optionally filtered by tag. */
|
|
21
|
-
read(tag) {
|
|
22
|
-
if (tag === void 0) return [...this.entries];
|
|
23
|
-
return this.entries.filter((e) => e.tag === tag);
|
|
24
|
-
}
|
|
25
|
-
/** Read entries from a specific stage. */
|
|
26
|
-
readFromStage(sourceStage) {
|
|
27
|
-
return this.entries.filter((e) => e.sourceStage === sourceStage);
|
|
28
|
-
}
|
|
29
|
-
/** Get summary of all entries as context string for LLM consumption. */
|
|
30
|
-
summarize(maxLength = 2e3) {
|
|
31
|
-
if (this.entries.length === 0) return "";
|
|
32
|
-
const lines = this.entries.map(
|
|
33
|
-
(e) => `[${e.tag}] (from ${e.sourceStage}): ${formatContent(e.content)}`
|
|
34
|
-
);
|
|
35
|
-
const joined = lines.join("\n");
|
|
36
|
-
if (joined.length <= maxLength) return joined;
|
|
37
|
-
return `${joined.slice(0, maxLength - 3)}...`;
|
|
38
|
-
}
|
|
39
|
-
/** Get entry count. */
|
|
40
|
-
get size() {
|
|
41
|
-
return this.entries.length;
|
|
42
|
-
}
|
|
43
|
-
/** Clear all entries. */
|
|
44
|
-
clear() {
|
|
45
|
-
this.entries.length = 0;
|
|
46
|
-
}
|
|
47
|
-
};
|
|
48
|
-
function formatContent(content) {
|
|
49
|
-
if (typeof content === "string") return content.slice(0, 200);
|
|
50
|
-
if (typeof content === "object" && content !== null) {
|
|
51
|
-
try {
|
|
52
|
-
return JSON.stringify(content).slice(0, 200);
|
|
53
|
-
} catch {
|
|
54
|
-
return "[object]";
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
return String(content);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export {
|
|
61
|
-
SharedMemoryStore
|
|
62
|
-
};
|
|
63
|
-
//# sourceMappingURL=chunk-ED6VQWNG.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/pipeline/shared-memory.ts"],"sourcesContent":["/**\n * Shared Memory — Cross-stage knowledge propagation (#1737, Phase 4)\n *\n * Early pipeline stages (planning, research) write structured discoveries\n * to a shared memory layer. Later stages read before acting.\n *\n * Scoped to a single pipeline run — not persisted cross-session.\n *\n * Pattern from: SWE-AF shared memory, AutoGen knowledge propagation.\n *\n * @module pipeline/shared-memory\n */\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/** A tagged memory entry written by a pipeline stage. */\nexport interface SharedMemoryEntry {\n /** Stage that wrote this entry. */\n readonly sourceStage: string;\n /** Category tag for filtering. */\n readonly tag: SharedMemoryTag;\n /** The content (structured or text). */\n readonly content: unknown;\n /** When this was written. */\n readonly timestamp: number;\n}\n\n/** Tags for categorizing shared memory entries. */\nexport type SharedMemoryTag =\n | 'convention'\n | 'discovery'\n | 'constraint'\n | 'decision'\n | 'risk'\n | 'dependency'\n | 'context';\n\n// ============================================================================\n// Shared Memory Store (per-pipeline-run)\n// ============================================================================\n\n/** In-memory store for cross-stage knowledge sharing. */\nexport class SharedMemoryStore {\n private readonly entries: SharedMemoryEntry[] = [];\n private readonly maxEntries: number;\n\n constructor(maxEntries = 100) {\n this.maxEntries = maxEntries;\n }\n\n /** Write a memory entry. */\n write(sourceStage: string, tag: SharedMemoryTag, content: unknown): void {\n if (this.entries.length >= this.maxEntries) {\n this.entries.shift(); // Evict oldest\n }\n this.entries.push({\n sourceStage,\n tag,\n content,\n timestamp: Date.now(),\n });\n }\n\n /** Read all entries, optionally filtered by tag. */\n read(tag?: SharedMemoryTag): readonly SharedMemoryEntry[] {\n if (tag === undefined) return [...this.entries];\n return this.entries.filter((e) => e.tag === tag);\n }\n\n /** Read entries from a specific stage. */\n readFromStage(sourceStage: string): readonly SharedMemoryEntry[] {\n return this.entries.filter((e) => e.sourceStage === sourceStage);\n }\n\n /** Get summary of all entries as context string for LLM consumption. */\n summarize(maxLength = 2000): string {\n if (this.entries.length === 0) return '';\n const lines = this.entries.map(\n (e) => `[${e.tag}] (from ${e.sourceStage}): ${formatContent(e.content)}`\n );\n const joined = lines.join('\\n');\n if (joined.length <= maxLength) return joined;\n return `${joined.slice(0, maxLength - 3)}...`;\n }\n\n /** Get entry count. */\n get size(): number {\n return this.entries.length;\n }\n\n /** Clear all entries. */\n clear(): void {\n this.entries.length = 0;\n }\n}\n\n/** Format content for summarization. */\nfunction formatContent(content: unknown): string {\n if (typeof content === 'string') return content.slice(0, 200);\n if (typeof content === 'object' && content !== null) {\n try {\n return JSON.stringify(content).slice(0, 200);\n } catch {\n return '[object]';\n }\n }\n return String(content);\n}\n"],"mappings":";AA4CO,IAAM,oBAAN,MAAwB;AAAA,EACZ,UAA+B,CAAC;AAAA,EAChC;AAAA,EAEjB,YAAY,aAAa,KAAK;AAC5B,SAAK,aAAa;AAAA,EACpB;AAAA;AAAA,EAGA,MAAM,aAAqB,KAAsB,SAAwB;AACvE,QAAI,KAAK,QAAQ,UAAU,KAAK,YAAY;AAC1C,WAAK,QAAQ,MAAM;AAAA,IACrB;AACA,SAAK,QAAQ,KAAK;AAAA,MAChB;AAAA,MACA;AAAA,MACA;AAAA,MACA,WAAW,KAAK,IAAI;AAAA,IACtB,CAAC;AAAA,EACH;AAAA;AAAA,EAGA,KAAK,KAAqD;AACxD,QAAI,QAAQ,OAAW,QAAO,CAAC,GAAG,KAAK,OAAO;AAC9C,WAAO,KAAK,QAAQ,OAAO,CAAC,MAAM,EAAE,QAAQ,GAAG;AAAA,EACjD;AAAA;AAAA,EAGA,cAAc,aAAmD;AAC/D,WAAO,KAAK,QAAQ,OAAO,CAAC,MAAM,EAAE,gBAAgB,WAAW;AAAA,EACjE;AAAA;AAAA,EAGA,UAAU,YAAY,KAAc;AAClC,QAAI,KAAK,QAAQ,WAAW,EAAG,QAAO;AACtC,UAAM,QAAQ,KAAK,QAAQ;AAAA,MACzB,CAAC,MAAM,IAAI,EAAE,GAAG,WAAW,EAAE,WAAW,MAAM,cAAc,EAAE,OAAO,CAAC;AAAA,IACxE;AACA,UAAM,SAAS,MAAM,KAAK,IAAI;AAC9B,QAAI,OAAO,UAAU,UAAW,QAAO;AACvC,WAAO,GAAG,OAAO,MAAM,GAAG,YAAY,CAAC,CAAC;AAAA,EAC1C;AAAA;AAAA,EAGA,IAAI,OAAe;AACjB,WAAO,KAAK,QAAQ;AAAA,EACtB;AAAA;AAAA,EAGA,QAAc;AACZ,SAAK,QAAQ,SAAS;AAAA,EACxB;AACF;AAGA,SAAS,cAAc,SAA0B;AAC/C,MAAI,OAAO,YAAY,SAAU,QAAO,QAAQ,MAAM,GAAG,GAAG;AAC5D,MAAI,OAAO,YAAY,YAAY,YAAY,MAAM;AACnD,QAAI;AACF,aAAO,KAAK,UAAU,OAAO,EAAE,MAAM,GAAG,GAAG;AAAA,IAC7C,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO,OAAO,OAAO;AACvB;","names":[]}
|