contextos-agents 1.6.1 → 2.0.0-beta.2
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/.agents/AGENTS.md +6 -1
- package/.agents/adapters/aider/export.js +117 -97
- package/.agents/adapters/claude/export.js +68 -26
- package/.agents/adapters/copilot/export.js +90 -51
- package/.agents/adapters/cursor/export.js +83 -68
- package/.agents/adapters/drift-detector.js +196 -0
- package/.agents/adapters/gemini/export.js +76 -45
- package/.agents/adapters/pure-compiler.js +443 -0
- package/.agents/adapters/zed/export.js +109 -62
- package/.agents/compiled/registry.v2.json +504 -0
- package/.agents/compiled/registry.v2.sha256 +1 -0
- package/.agents/compiler/manifest-compiler.js +963 -0
- package/.agents/compiler/vendor/yaml.LICENSE.txt +13 -0
- package/.agents/compiler/vendor/yaml.SBOM.json +6 -0
- package/.agents/compiler/vendor/yaml.js +139 -0
- package/.agents/core/profiles/init.yaml +25 -0
- package/.agents/core/skills/context-manager/references/context-rules.md +59 -0
- package/.agents/core/skills/context-manager/skill.yaml +10 -5
- package/.agents/core/skills/context-os/SKILL.md +3 -6
- package/.agents/core/skills/context-os/skill.yaml +14 -8
- package/.agents/core/skills/engineering-workflow/SKILL.md +1 -1
- package/.agents/core/skills/engineering-workflow/skill.yaml +7 -7
- package/.agents/core/skills/gemini-precision/SKILL.md +4 -0
- package/.agents/core/skills/gemini-precision/skill.yaml +5 -6
- package/.agents/core/skills/gstack-roles/SKILL.md +3 -1
- package/.agents/core/skills/gstack-roles/skill.yaml +9 -6
- package/.agents/core/skills/ponytail-mindset/skill.yaml +7 -7
- package/.agents/core/skills/security/skill.yaml +21 -2
- package/.agents/ctx.js +587 -111
- package/.agents/customization-dx.js +282 -0
- package/.agents/doctor.js +877 -33
- package/.agents/filesystem/index.js +71 -0
- package/.agents/filesystem/journaled-transaction.js +451 -0
- package/.agents/filesystem/lockfile-v2.js +275 -0
- package/.agents/filesystem/platform-hardening.js +222 -0
- package/.agents/filesystem/project-lock.js +218 -0
- package/.agents/filesystem/safe-path.js +256 -0
- package/.agents/generated/claude/skills/context-os/SKILL.md +1 -1
- package/.agents/generated/claude/skills/engineering-workflow/SKILL.md +1 -1
- package/.agents/generated/claude/skills/gemini-precision/SKILL.md +4 -0
- package/.agents/generated/claude/skills/gstack-roles/SKILL.md +3 -1
- package/.agents/generated/gemini/skills/context-os/SKILL.md +2 -2
- package/.agents/generated/gemini/skills/engineering-workflow/SKILL.md +1 -1
- package/.agents/generated/gemini/skills/gemini-precision/SKILL.md +4 -0
- package/.agents/generated/gemini/skills/gstack-roles/SKILL.md +3 -1
- package/.agents/plugins/contextos/hooks.json +25 -0
- package/.agents/plugins/contextos/plugin.json +19 -0
- package/.agents/plugins.js +432 -73
- package/.agents/profiles.js +507 -46
- package/.agents/resolver.js +50 -414
- package/.agents/schemas/attestation.review.v1.json +111 -0
- package/.agents/schemas/attestation.verification.v1.json +85 -0
- package/.agents/schemas/lockfile.v2.schema.json +134 -0
- package/.agents/schemas/profile.v2.schema.json +114 -0
- package/.agents/schemas/runtime.thread.v1.json +192 -0
- package/.agents/schemas/skill.manifest.v2.json +177 -0
- package/.agents/schemas/verification.spec.v1.json +39 -0
- package/.agents/schemas/workspace.graph.schema.json +106 -0
- package/.agents/stats.js +22 -9
- package/.agents/transaction-core/event-store.js +288 -0
- package/.agents/transaction-core/idempotency.js +129 -0
- package/.agents/transaction-core/ipc-lock.js +311 -0
- package/.agents/transaction-core/plugin-supply-chain-bundle.js +436 -0
- package/.agents/validate.js +143 -14
- package/.agents/watch.js +354 -102
- package/.agents/workspace/workspace-graph.js +778 -0
- package/README.md +59 -387
- package/benchmarks/v2/analysis/statistics.js +140 -0
- package/benchmarks/v2/analysis/stats.js +69 -0
- package/benchmarks/v2/arms/arm-definitions.js +79 -0
- package/benchmarks/v2/dataset.schema.json +34 -0
- package/benchmarks/v2/evaluators/index.js +25 -0
- package/benchmarks/v2/evaluators/verified-success.js +116 -0
- package/benchmarks/v2/harness/runner.js +88 -0
- package/benchmarks/v2/pilot-tasks.json +392 -0
- package/bin/commands/recover.js +88 -0
- package/bin/commands/uninstall.js +207 -0
- package/bin/commands/update.js +325 -0
- package/bin/commands.js +342 -0
- package/bin/index.js +326 -149
- package/bin/lib/detector.js +106 -0
- package/bin/lib/lockfile.js +253 -0
- package/bin/lib/safe-writer.js +290 -0
- package/package.json +85 -73
- package/registry.json +15 -7
- package/registry.schema.json +3 -1
- package/registry.v2.schema.json +86 -0
- package/.agents/core/profiles/backend.yaml +0 -47
- package/.agents/core/profiles/enterprise.yaml +0 -46
- package/.agents/core/profiles/frontend.yaml +0 -46
- package/.agents/core/profiles/hackathon.yaml +0 -45
- package/.agents/core/profiles/mvp.yaml +0 -44
- package/.agents/core/profiles/startup.yaml +0 -48
- package/.agents/core/skills/adapters/EXAMPLES.md +0 -19
- package/.agents/core/skills/adapters/SKILL.md +0 -105
- package/.agents/core/skills/adapters/TROUBLESHOOTING.md +0 -7
- package/.agents/core/skills/adapters/VALIDATION.json +0 -12
- package/.agents/core/skills/adapters/skill.yaml +0 -10
- package/.agents/core/skills/architecture-diagrams/SKILL.md +0 -108
- package/.agents/core/skills/architecture-diagrams/VALIDATION.json +0 -12
- package/.agents/core/skills/architecture-diagrams/skill.yaml +0 -8
- package/.agents/core/skills/brutalist-design/SKILL.md +0 -150
- package/.agents/core/skills/brutalist-design/VALIDATION.json +0 -12
- package/.agents/core/skills/brutalist-design/skill.yaml +0 -8
- package/.agents/core/skills/database/EXAMPLES.md +0 -74
- package/.agents/core/skills/database/SKILL.md +0 -101
- package/.agents/core/skills/database/TROUBLESHOOTING.md +0 -18
- package/.agents/core/skills/database/VALIDATION.json +0 -11
- package/.agents/core/skills/database/skill.yaml +0 -25
- package/.agents/core/skills/ddd/EXAMPLES.md +0 -42
- package/.agents/core/skills/ddd/SKILL.md +0 -247
- package/.agents/core/skills/ddd/TROUBLESHOOTING.md +0 -19
- package/.agents/core/skills/ddd/VALIDATION.json +0 -12
- package/.agents/core/skills/ddd/ddd.md +0 -178
- package/.agents/core/skills/ddd/skill.yaml +0 -10
- package/.agents/core/skills/decisions/EXAMPLES.md +0 -35
- package/.agents/core/skills/decisions/SKILL.md +0 -90
- package/.agents/core/skills/decisions/TROUBLESHOOTING.md +0 -13
- package/.agents/core/skills/decisions/VALIDATION.json +0 -12
- package/.agents/core/skills/decisions/skill.yaml +0 -10
- package/.agents/core/skills/docker/EXAMPLES.md +0 -56
- package/.agents/core/skills/docker/SKILL.md +0 -63
- package/.agents/core/skills/docker/TROUBLESHOOTING.md +0 -18
- package/.agents/core/skills/docker/VALIDATION.json +0 -11
- package/.agents/core/skills/docker/skill.yaml +0 -23
- package/.agents/core/skills/fastapi/EXAMPLES.md +0 -36
- package/.agents/core/skills/fastapi/SKILL.md +0 -148
- package/.agents/core/skills/fastapi/TROUBLESHOOTING.md +0 -19
- package/.agents/core/skills/fastapi/VALIDATION.json +0 -12
- package/.agents/core/skills/fastapi/fastapi.md +0 -112
- package/.agents/core/skills/fastapi/skill.yaml +0 -10
- package/.agents/core/skills/generators/EXAMPLES.md +0 -19
- package/.agents/core/skills/generators/SKILL.md +0 -112
- package/.agents/core/skills/generators/TROUBLESHOOTING.md +0 -7
- package/.agents/core/skills/generators/VALIDATION.json +0 -12
- package/.agents/core/skills/generators/skill.yaml +0 -10
- package/.agents/core/skills/generators/templates/API.md +0 -77
- package/.agents/core/skills/generators/templates/ARCHITECTURE.md +0 -70
- package/.agents/core/skills/generators/templates/DATABASE.md +0 -42
- package/.agents/core/skills/generators/templates/DECISION.md +0 -46
- package/.agents/core/skills/generators/templates/PRD.md +0 -67
- package/.agents/core/skills/generators/templates/PROJECT_GRAPH.md +0 -56
- package/.agents/core/skills/generators/templates/ROADMAP.md +0 -51
- package/.agents/core/skills/generators/templates/TASKS.md +0 -43
- package/.agents/core/skills/generators/templates/UI.md +0 -73
- package/.agents/core/skills/graphify/EXAMPLES.md +0 -73
- package/.agents/core/skills/graphify/SKILL.md +0 -130
- package/.agents/core/skills/graphify/VALIDATION.json +0 -12
- package/.agents/core/skills/graphify/skill.yaml +0 -13
- package/.agents/core/skills/impeccable-design/EXAMPLES.md +0 -26
- package/.agents/core/skills/impeccable-design/SKILL.md +0 -201
- package/.agents/core/skills/impeccable-design/TROUBLESHOOTING.md +0 -19
- package/.agents/core/skills/impeccable-design/VALIDATION.json +0 -12
- package/.agents/core/skills/impeccable-design/skill.yaml +0 -14
- package/.agents/core/skills/interview-me/SKILL.md +0 -97
- package/.agents/core/skills/interview-me/VALIDATION.json +0 -12
- package/.agents/core/skills/interview-me/skill.yaml +0 -8
- package/.agents/core/skills/microservices/EXAMPLES.md +0 -38
- package/.agents/core/skills/microservices/SKILL.md +0 -164
- package/.agents/core/skills/microservices/TROUBLESHOOTING.md +0 -19
- package/.agents/core/skills/microservices/VALIDATION.json +0 -12
- package/.agents/core/skills/microservices/microservices.md +0 -119
- package/.agents/core/skills/microservices/skill.yaml +0 -10
- package/.agents/core/skills/minimalist-design/SKILL.md +0 -113
- package/.agents/core/skills/minimalist-design/VALIDATION.json +0 -12
- package/.agents/core/skills/minimalist-design/skill.yaml +0 -8
- package/.agents/core/skills/nestjs/EXAMPLES.md +0 -40
- package/.agents/core/skills/nestjs/SKILL.md +0 -139
- package/.agents/core/skills/nestjs/TROUBLESHOOTING.md +0 -19
- package/.agents/core/skills/nestjs/VALIDATION.json +0 -12
- package/.agents/core/skills/nestjs/nestjs.md +0 -103
- package/.agents/core/skills/nestjs/skill.yaml +0 -10
- package/.agents/core/skills/nextjs/EXAMPLES.md +0 -40
- package/.agents/core/skills/nextjs/SKILL.md +0 -163
- package/.agents/core/skills/nextjs/TROUBLESHOOTING.md +0 -19
- package/.agents/core/skills/nextjs/VALIDATION.json +0 -12
- package/.agents/core/skills/nextjs/nextjs.md +0 -67
- package/.agents/core/skills/nextjs/skill.yaml +0 -10
- package/.agents/core/skills/node/EXAMPLES.md +0 -80
- package/.agents/core/skills/node/SKILL.md +0 -128
- package/.agents/core/skills/node/TROUBLESHOOTING.md +0 -19
- package/.agents/core/skills/node/VALIDATION.json +0 -12
- package/.agents/core/skills/node/node.md +0 -87
- package/.agents/core/skills/node/skill.yaml +0 -10
- package/.agents/core/skills/performance/EXAMPLES.md +0 -30
- package/.agents/core/skills/performance/SKILL.md +0 -75
- package/.agents/core/skills/performance/TROUBLESHOOTING.md +0 -19
- package/.agents/core/skills/performance/VALIDATION.json +0 -12
- package/.agents/core/skills/performance/performance.md +0 -52
- package/.agents/core/skills/performance/skill.yaml +0 -10
- package/.agents/core/skills/react/EXAMPLES.md +0 -79
- package/.agents/core/skills/react/SKILL.md +0 -132
- package/.agents/core/skills/react/TROUBLESHOOTING.md +0 -19
- package/.agents/core/skills/react/VALIDATION.json +0 -12
- package/.agents/core/skills/react/react.md +0 -93
- package/.agents/core/skills/react/skill.yaml +0 -10
- package/.agents/core/skills/react-best-practices/SKILL.md +0 -155
- package/.agents/core/skills/react-best-practices/VALIDATION.json +0 -12
- package/.agents/core/skills/react-best-practices/skill.yaml +0 -10
- package/.agents/core/skills/redesign-audit/SKILL.md +0 -117
- package/.agents/core/skills/redesign-audit/VALIDATION.json +0 -12
- package/.agents/core/skills/redesign-audit/skill.yaml +0 -8
- package/.agents/core/skills/soft-design/SKILL.md +0 -108
- package/.agents/core/skills/soft-design/VALIDATION.json +0 -12
- package/.agents/core/skills/soft-design/skill.yaml +0 -8
- package/.agents/core/skills/state-management/EXAMPLES.md +0 -56
- package/.agents/core/skills/state-management/SKILL.md +0 -48
- package/.agents/core/skills/state-management/TROUBLESHOOTING.md +0 -18
- package/.agents/core/skills/state-management/VALIDATION.json +0 -11
- package/.agents/core/skills/state-management/skill.yaml +0 -22
- package/.agents/core/skills/subagent-orchestrator/SKILL.md +0 -100
- package/.agents/core/skills/subagent-orchestrator/VALIDATION.json +0 -12
- package/.agents/core/skills/subagent-orchestrator/skill.yaml +0 -8
- package/.agents/core/skills/system-design/EXAMPLES.md +0 -75
- package/.agents/core/skills/system-design/SKILL.md +0 -419
- package/.agents/core/skills/system-design/TROUBLESHOOTING.md +0 -19
- package/.agents/core/skills/system-design/VALIDATION.json +0 -12
- package/.agents/core/skills/system-design/skill.yaml +0 -13
- package/.agents/core/skills/system-design/system-design.md +0 -112
- package/.agents/core/skills/testing/EXAMPLES.md +0 -71
- package/.agents/core/skills/testing/SKILL.md +0 -70
- package/.agents/core/skills/testing/TROUBLESHOOTING.md +0 -18
- package/.agents/core/skills/testing/VALIDATION.json +0 -11
- package/.agents/core/skills/testing/skill.yaml +0 -26
- package/.agents/core/skills/typescript/EXAMPLES.md +0 -64
- package/.agents/core/skills/typescript/SKILL.md +0 -112
- package/.agents/core/skills/typescript/TROUBLESHOOTING.md +0 -19
- package/.agents/core/skills/typescript/VALIDATION.json +0 -12
- package/.agents/core/skills/typescript/skill.yaml +0 -10
- package/.agents/core/skills/typescript/typescript.md +0 -71
- package/.agents/core/skills/ui-design/EXAMPLES.md +0 -21
- package/.agents/core/skills/ui-design/SKILL.md +0 -124
- package/.agents/core/skills/ui-design/TROUBLESHOOTING.md +0 -19
- package/.agents/core/skills/ui-design/VALIDATION.json +0 -12
- package/.agents/core/skills/ui-design/skill.yaml +0 -10
- package/.agents/core/skills/ui-design/ui.md +0 -88
- package/.agents/core/skills/ui-ux-pro/EXAMPLES.md +0 -62
- package/.agents/core/skills/ui-ux-pro/SKILL.md +0 -375
- package/.agents/core/skills/ui-ux-pro/TROUBLESHOOTING.md +0 -19
- package/.agents/core/skills/ui-ux-pro/VALIDATION.json +0 -12
- package/.agents/core/skills/ui-ux-pro/skill.yaml +0 -13
- package/.agents/core/skills/ux-design/EXAMPLES.md +0 -36
- package/.agents/core/skills/ux-design/SKILL.md +0 -116
- package/.agents/core/skills/ux-design/TROUBLESHOOTING.md +0 -19
- package/.agents/core/skills/ux-design/VALIDATION.json +0 -12
- package/.agents/core/skills/ux-design/skill.yaml +0 -10
- package/.agents/core/skills/ux-design/ux.md +0 -80
- package/.agents/core/skills/vercel-optimize/SKILL.md +0 -83
- package/.agents/core/skills/vercel-optimize/VALIDATION.json +0 -12
- package/.agents/core/skills/vercel-optimize/skill.yaml +0 -10
- package/.agents/core/skills/web-accessibility/EXAMPLES.md +0 -39
- package/.agents/core/skills/web-accessibility/SKILL.md +0 -170
- package/.agents/core/skills/web-accessibility/TROUBLESHOOTING.md +0 -19
- package/.agents/core/skills/web-accessibility/VALIDATION.json +0 -12
- package/.agents/core/skills/web-accessibility/accessibility.md +0 -63
- package/.agents/core/skills/web-accessibility/skill.yaml +0 -10
- package/.agents/generated/claude/skills/adapters/SKILL.md +0 -126
- package/.agents/generated/claude/skills/architecture-diagrams/SKILL.md +0 -101
- package/.agents/generated/claude/skills/brutalist-design/SKILL.md +0 -145
- package/.agents/generated/claude/skills/database/SKILL.md +0 -191
- package/.agents/generated/claude/skills/ddd/SKILL.md +0 -305
- package/.agents/generated/claude/skills/decisions/SKILL.md +0 -134
- package/.agents/generated/claude/skills/docker/SKILL.md +0 -135
- package/.agents/generated/claude/skills/fastapi/SKILL.md +0 -200
- package/.agents/generated/claude/skills/generators/SKILL.md +0 -133
- package/.agents/generated/claude/skills/graphify/SKILL.md +0 -198
- package/.agents/generated/claude/skills/impeccable-design/SKILL.md +0 -241
- package/.agents/generated/claude/skills/interview-me/SKILL.md +0 -90
- package/.agents/generated/claude/skills/microservices/SKILL.md +0 -218
- package/.agents/generated/claude/skills/minimalist-design/SKILL.md +0 -108
- package/.agents/generated/claude/skills/nestjs/SKILL.md +0 -195
- package/.agents/generated/claude/skills/nextjs/SKILL.md +0 -219
- package/.agents/generated/claude/skills/node/SKILL.md +0 -224
- package/.agents/generated/claude/skills/performance/SKILL.md +0 -121
- package/.agents/generated/claude/skills/react/SKILL.md +0 -227
- package/.agents/generated/claude/skills/react-best-practices/SKILL.md +0 -146
- package/.agents/generated/claude/skills/redesign-audit/SKILL.md +0 -112
- package/.agents/generated/claude/skills/soft-design/SKILL.md +0 -103
- package/.agents/generated/claude/skills/state-management/SKILL.md +0 -120
- package/.agents/generated/claude/skills/subagent-orchestrator/SKILL.md +0 -93
- package/.agents/generated/claude/skills/system-design/SKILL.md +0 -507
- package/.agents/generated/claude/skills/testing/SKILL.md +0 -157
- package/.agents/generated/claude/skills/typescript/SKILL.md +0 -192
- package/.agents/generated/claude/skills/ui-design/SKILL.md +0 -161
- package/.agents/generated/claude/skills/ui-ux-pro/SKILL.md +0 -451
- package/.agents/generated/claude/skills/ux-design/SKILL.md +0 -168
- package/.agents/generated/claude/skills/vercel-optimize/SKILL.md +0 -76
- package/.agents/generated/claude/skills/web-accessibility/SKILL.md +0 -225
- package/.agents/generated/gemini/skills/adapters/SKILL.md +0 -135
- package/.agents/generated/gemini/skills/architecture-diagrams/SKILL.md +0 -107
- package/.agents/generated/gemini/skills/brutalist-design/SKILL.md +0 -151
- package/.agents/generated/gemini/skills/database/SKILL.md +0 -200
- package/.agents/generated/gemini/skills/ddd/SKILL.md +0 -314
- package/.agents/generated/gemini/skills/decisions/SKILL.md +0 -143
- package/.agents/generated/gemini/skills/docker/SKILL.md +0 -144
- package/.agents/generated/gemini/skills/fastapi/SKILL.md +0 -209
- package/.agents/generated/gemini/skills/generators/SKILL.md +0 -142
- package/.agents/generated/gemini/skills/graphify/SKILL.md +0 -205
- package/.agents/generated/gemini/skills/impeccable-design/SKILL.md +0 -250
- package/.agents/generated/gemini/skills/interview-me/SKILL.md +0 -96
- package/.agents/generated/gemini/skills/microservices/SKILL.md +0 -227
- package/.agents/generated/gemini/skills/minimalist-design/SKILL.md +0 -114
- package/.agents/generated/gemini/skills/nestjs/SKILL.md +0 -204
- package/.agents/generated/gemini/skills/nextjs/SKILL.md +0 -298
- package/.agents/generated/gemini/skills/node/SKILL.md +0 -323
- package/.agents/generated/gemini/skills/performance/SKILL.md +0 -185
- package/.agents/generated/gemini/skills/react/SKILL.md +0 -332
- package/.agents/generated/gemini/skills/react-best-practices/SKILL.md +0 -152
- package/.agents/generated/gemini/skills/redesign-audit/SKILL.md +0 -118
- package/.agents/generated/gemini/skills/soft-design/SKILL.md +0 -109
- package/.agents/generated/gemini/skills/state-management/SKILL.md +0 -129
- package/.agents/generated/gemini/skills/subagent-orchestrator/SKILL.md +0 -99
- package/.agents/generated/gemini/skills/system-design/SKILL.md +0 -631
- package/.agents/generated/gemini/skills/testing/SKILL.md +0 -166
- package/.agents/generated/gemini/skills/typescript/SKILL.md +0 -275
- package/.agents/generated/gemini/skills/ui-design/SKILL.md +0 -170
- package/.agents/generated/gemini/skills/ui-ux-pro/SKILL.md +0 -460
- package/.agents/generated/gemini/skills/ux-design/SKILL.md +0 -177
- package/.agents/generated/gemini/skills/vercel-optimize/SKILL.md +0 -82
- package/.agents/generated/gemini/skills/web-accessibility/SKILL.md +0 -300
- package/.agents/mcp/runtime.py +0 -470
- package/.agents/mcp/server.mjs +0 -189271
- package/benchmarks/gemini-issues.js +0 -533
|
@@ -0,0 +1,778 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* .agents/workspace/workspace-graph.js
|
|
3
|
+
* ContextOS — Workspace Evidence Graph Builder
|
|
4
|
+
*
|
|
5
|
+
* Discovers and builds a structured evidence graph of the workspace repository:
|
|
6
|
+
* - Monorepo & multi-package workspace discovery (npm/yarn workspaces, pnpm, Turborepo, Lerna, Nx)
|
|
7
|
+
* - Polyglot ecosystem support (npm, python, rust, go)
|
|
8
|
+
* - Nearest-package resolution with bounded traversal & symlink escape guards
|
|
9
|
+
* - Package dependency extraction, internal cross-package edges, and stack configs
|
|
10
|
+
* - Cryptographic deterministic fingerprinting (SHA-256)
|
|
11
|
+
* - Cross-platform POSIX path normalization
|
|
12
|
+
* - Zero install-time runtime dependencies (pure Node.js)
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
'use strict';
|
|
16
|
+
|
|
17
|
+
const fs = require('fs');
|
|
18
|
+
const path = require('path');
|
|
19
|
+
const crypto = require('crypto');
|
|
20
|
+
|
|
21
|
+
// Default directories to ignore during traversal
|
|
22
|
+
const DEFAULT_IGNORED_DIRS = new Set([
|
|
23
|
+
'.git',
|
|
24
|
+
'node_modules',
|
|
25
|
+
'.contextos-worktrees',
|
|
26
|
+
'.agents',
|
|
27
|
+
'dist',
|
|
28
|
+
'build',
|
|
29
|
+
'.next',
|
|
30
|
+
'out',
|
|
31
|
+
'target',
|
|
32
|
+
'vendor',
|
|
33
|
+
'__pycache__',
|
|
34
|
+
'.venv',
|
|
35
|
+
'venv',
|
|
36
|
+
'.turbo',
|
|
37
|
+
'.cache',
|
|
38
|
+
'coverage',
|
|
39
|
+
'.svn',
|
|
40
|
+
'.hg',
|
|
41
|
+
'scratch',
|
|
42
|
+
'temp',
|
|
43
|
+
'tmp'
|
|
44
|
+
]);
|
|
45
|
+
|
|
46
|
+
// Known stack config patterns to detect per package
|
|
47
|
+
const KNOWN_CONFIG_FILES = [
|
|
48
|
+
'tsconfig.json',
|
|
49
|
+
'jsconfig.json',
|
|
50
|
+
'next.config.js',
|
|
51
|
+
'next.config.mjs',
|
|
52
|
+
'next.config.ts',
|
|
53
|
+
'vite.config.js',
|
|
54
|
+
'vite.config.mjs',
|
|
55
|
+
'vite.config.ts',
|
|
56
|
+
'tailwind.config.js',
|
|
57
|
+
'tailwind.config.cjs',
|
|
58
|
+
'tailwind.config.mjs',
|
|
59
|
+
'tailwind.config.ts',
|
|
60
|
+
'Dockerfile',
|
|
61
|
+
'docker-compose.yml',
|
|
62
|
+
'docker-compose.yaml',
|
|
63
|
+
'vitest.config.js',
|
|
64
|
+
'vitest.config.ts',
|
|
65
|
+
'vitest.config.mjs',
|
|
66
|
+
'jest.config.js',
|
|
67
|
+
'jest.config.ts',
|
|
68
|
+
'nest-cli.json',
|
|
69
|
+
'prisma/schema.prisma'
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Normalizes any path to a POSIX path with forward slashes.
|
|
74
|
+
* @param {string} p
|
|
75
|
+
* @returns {string}
|
|
76
|
+
*/
|
|
77
|
+
function toPosix(p) {
|
|
78
|
+
if (!p) return '';
|
|
79
|
+
return p.replace(/\\/g, '/');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Standardizes directory paths for safe relative comparisons.
|
|
84
|
+
* @param {string} p
|
|
85
|
+
* @returns {string}
|
|
86
|
+
*/
|
|
87
|
+
function cleanRelative(p) {
|
|
88
|
+
const posix = toPosix(p).replace(/^\.\//, '');
|
|
89
|
+
if (posix === '' || posix === '.') return '.';
|
|
90
|
+
return posix.replace(/\/+$/, '');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Simple glob-to-RegExp compiler for workspace patterns (e.g. "packages/*", "apps/**").
|
|
95
|
+
* @param {string} glob
|
|
96
|
+
* @returns {RegExp}
|
|
97
|
+
*/
|
|
98
|
+
function globToRegex(glob) {
|
|
99
|
+
const normalized = toPosix(glob).replace(/^\.\//, '').replace(/\/+$/, '');
|
|
100
|
+
let regStr = '';
|
|
101
|
+
for (let i = 0; i < normalized.length; i++) {
|
|
102
|
+
const char = normalized[i];
|
|
103
|
+
if (char === '*' && normalized[i + 1] === '*') {
|
|
104
|
+
regStr += '.*';
|
|
105
|
+
i++; // skip second asterisk
|
|
106
|
+
} else if (char === '*') {
|
|
107
|
+
regStr += '[^/]+';
|
|
108
|
+
} else if (char === '?') {
|
|
109
|
+
regStr += '[^/]';
|
|
110
|
+
} else if (['.', '+', '^', '$', '(', ')', '[', ']', '{', '}', '|', '\\'].includes(char)) {
|
|
111
|
+
regStr += '\\' + char;
|
|
112
|
+
} else {
|
|
113
|
+
regStr += char;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return new RegExp(`^${regStr}$`);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
class WorkspaceGraphBuilder {
|
|
120
|
+
constructor(options = {}) {
|
|
121
|
+
this.maxDepth = options.maxDepth || 8;
|
|
122
|
+
this.maxPackages = options.maxPackages || 500;
|
|
123
|
+
this.ignoredDirs = new Set([...DEFAULT_IGNORED_DIRS, ...(options.ignoredDirs || [])]);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Discovers the real repository root starting from a directory.
|
|
128
|
+
* Traverses upwards looking for markers (.git, pnpm-workspace.yaml, lerna.json, root package.json with workspaces).
|
|
129
|
+
* @param {string} startDir
|
|
130
|
+
* @returns {string} Real absolute path
|
|
131
|
+
*/
|
|
132
|
+
findRepositoryRoot(startDir) {
|
|
133
|
+
let current = path.resolve(startDir || process.cwd());
|
|
134
|
+
try {
|
|
135
|
+
current = fs.realpathSync(current);
|
|
136
|
+
} catch {
|
|
137
|
+
// keep current as is
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const { root } = path.parse(current);
|
|
141
|
+
let candidate = current;
|
|
142
|
+
|
|
143
|
+
while (current && current !== root) {
|
|
144
|
+
// Priority 1: .git folder or file
|
|
145
|
+
if (fs.existsSync(path.join(current, '.git'))) {
|
|
146
|
+
return current;
|
|
147
|
+
}
|
|
148
|
+
// Priority 2: monorepo workspace markers
|
|
149
|
+
if (
|
|
150
|
+
fs.existsSync(path.join(current, 'pnpm-workspace.yaml')) ||
|
|
151
|
+
fs.existsSync(path.join(current, 'lerna.json')) ||
|
|
152
|
+
fs.existsSync(path.join(current, 'turbo.json')) ||
|
|
153
|
+
fs.existsSync(path.join(current, 'nx.json'))
|
|
154
|
+
) {
|
|
155
|
+
candidate = current;
|
|
156
|
+
}
|
|
157
|
+
// Check package.json for workspaces
|
|
158
|
+
const pkgPath = path.join(current, 'package.json');
|
|
159
|
+
if (fs.existsSync(pkgPath)) {
|
|
160
|
+
try {
|
|
161
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
162
|
+
if (pkg.workspaces) {
|
|
163
|
+
candidate = current;
|
|
164
|
+
}
|
|
165
|
+
} catch {
|
|
166
|
+
// ignore corrupted package.json
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const parent = path.dirname(current);
|
|
171
|
+
if (parent === current) break;
|
|
172
|
+
current = parent;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return candidate;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Builds the Workspace Evidence Graph for the given root directory.
|
|
180
|
+
* @param {string} rootDir
|
|
181
|
+
* @param {object} [options]
|
|
182
|
+
* @returns {object} WorkspaceGraph
|
|
183
|
+
*/
|
|
184
|
+
build(rootDir, options = {}) {
|
|
185
|
+
const realRoot = this.findRepositoryRoot(rootDir);
|
|
186
|
+
const posixRoot = toPosix(realRoot);
|
|
187
|
+
let hitLimit = false;
|
|
188
|
+
|
|
189
|
+
// Read workspace globs if defined (npm/yarn or pnpm)
|
|
190
|
+
const workspaceGlobs = this._detectDeclaredWorkspaces(realRoot);
|
|
191
|
+
|
|
192
|
+
const packages = [];
|
|
193
|
+
const evidence = [];
|
|
194
|
+
const packageIdSet = new Set();
|
|
195
|
+
const manifestHashes = [];
|
|
196
|
+
|
|
197
|
+
// Check root package
|
|
198
|
+
const rootPkg = this._inspectDirectoryForPackage(realRoot, '.', realRoot);
|
|
199
|
+
if (rootPkg) {
|
|
200
|
+
packages.push(rootPkg);
|
|
201
|
+
packageIdSet.add(rootPkg.id);
|
|
202
|
+
manifestHashes.push(...rootPkg.manifestHashes);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Traverse directory tree
|
|
206
|
+
const visitedDirs = new Set();
|
|
207
|
+
try {
|
|
208
|
+
visitedDirs.add(fs.realpathSync(realRoot));
|
|
209
|
+
} catch {
|
|
210
|
+
visitedDirs.add(realRoot);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const walk = (currentDir, depth) => {
|
|
214
|
+
if (packages.length >= this.maxPackages || depth > this.maxDepth) {
|
|
215
|
+
hitLimit = true;
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
let entries;
|
|
220
|
+
try {
|
|
221
|
+
entries = fs.readdirSync(currentDir, { withFileTypes: true });
|
|
222
|
+
} catch {
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
for (const entry of entries) {
|
|
227
|
+
if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
|
|
228
|
+
if (this.ignoredDirs.has(entry.name)) continue;
|
|
229
|
+
|
|
230
|
+
const fullPath = path.join(currentDir, entry.name);
|
|
231
|
+
let realEntryPath;
|
|
232
|
+
try {
|
|
233
|
+
realEntryPath = fs.realpathSync(fullPath);
|
|
234
|
+
} catch {
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Symlink escape guard: must remain within root realpath
|
|
239
|
+
if (
|
|
240
|
+
!realEntryPath.startsWith(visitedDirs.values().next().value) &&
|
|
241
|
+
!realEntryPath.startsWith(realRoot)
|
|
242
|
+
) {
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (visitedDirs.has(realEntryPath)) continue;
|
|
247
|
+
visitedDirs.add(realEntryPath);
|
|
248
|
+
|
|
249
|
+
const relPath = cleanRelative(path.relative(realRoot, fullPath));
|
|
250
|
+
|
|
251
|
+
// Check if this directory is a package
|
|
252
|
+
const isDeclared =
|
|
253
|
+
workspaceGlobs.length === 0 ||
|
|
254
|
+
workspaceGlobs.some((g) => g.test(relPath));
|
|
255
|
+
|
|
256
|
+
const pkg = this._inspectDirectoryForPackage(fullPath, relPath, realRoot);
|
|
257
|
+
if (pkg) {
|
|
258
|
+
packages.push(pkg);
|
|
259
|
+
packageIdSet.add(pkg.id);
|
|
260
|
+
manifestHashes.push(...pkg.manifestHashes);
|
|
261
|
+
if (packages.length >= this.maxPackages) {
|
|
262
|
+
hitLimit = true;
|
|
263
|
+
break;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Recurse into subdirectories (if not deeper than maxDepth)
|
|
268
|
+
if (depth < this.maxDepth) {
|
|
269
|
+
walk(fullPath, depth + 1);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
walk(realRoot, 1);
|
|
275
|
+
|
|
276
|
+
// Compute internal cross-package dependencies
|
|
277
|
+
for (const pkg of packages) {
|
|
278
|
+
pkg.internalDependencies = pkg.dependencies.filter(
|
|
279
|
+
(dep) => packageIdSet.has(dep) && dep !== pkg.id
|
|
280
|
+
);
|
|
281
|
+
// Clean up internal hashes before returning
|
|
282
|
+
delete pkg.manifestHashes;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Sort packages deterministically by root path
|
|
286
|
+
packages.sort((a, b) => a.root.localeCompare(b.root));
|
|
287
|
+
|
|
288
|
+
// Compute deterministic fingerprint
|
|
289
|
+
manifestHashes.sort();
|
|
290
|
+
const fingerprint = crypto
|
|
291
|
+
.createHash('sha256')
|
|
292
|
+
.update(manifestHashes.join('\n') || realRoot)
|
|
293
|
+
.digest('hex');
|
|
294
|
+
|
|
295
|
+
// Aggregate baseline evidence for detected packages
|
|
296
|
+
for (const pkg of packages) {
|
|
297
|
+
for (const dep of pkg.dependencies) {
|
|
298
|
+
evidence.push({
|
|
299
|
+
target: dep,
|
|
300
|
+
source: pkg.root === '.' ? 'root-package' : 'workspace-package',
|
|
301
|
+
weight: pkg.root === '.' ? 20 : 30,
|
|
302
|
+
description: `package ${pkg.id} (${pkg.ecosystem}) depends on ${dep}`
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return {
|
|
308
|
+
schemaVersion: 1,
|
|
309
|
+
repositoryRoot: posixRoot,
|
|
310
|
+
packages,
|
|
311
|
+
evidence,
|
|
312
|
+
fingerprint,
|
|
313
|
+
partial: hitLimit
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Finds the nearest enclosing package for a given file path.
|
|
319
|
+
* Returns the package with the longest matching relative root prefix.
|
|
320
|
+
* @param {string} filePath
|
|
321
|
+
* @param {object} graph WorkspaceGraph
|
|
322
|
+
* @returns {object|null}
|
|
323
|
+
*/
|
|
324
|
+
findNearestPackage(filePath, graph) {
|
|
325
|
+
if (!filePath || !graph || !Array.isArray(graph.packages)) return null;
|
|
326
|
+
|
|
327
|
+
let targetRel = toPosix(filePath).trim();
|
|
328
|
+
const repoRoot = toPosix(graph.repositoryRoot);
|
|
329
|
+
|
|
330
|
+
// Handle absolute paths
|
|
331
|
+
if (path.isAbsolute(filePath)) {
|
|
332
|
+
if (process.platform === 'win32') {
|
|
333
|
+
if (targetRel.toLowerCase().startsWith(repoRoot.toLowerCase() + '/')) {
|
|
334
|
+
targetRel = targetRel.slice(repoRoot.length + 1);
|
|
335
|
+
} else if (targetRel.toLowerCase() === repoRoot.toLowerCase()) {
|
|
336
|
+
targetRel = '.';
|
|
337
|
+
}
|
|
338
|
+
} else {
|
|
339
|
+
if (targetRel.startsWith(repoRoot + '/')) {
|
|
340
|
+
targetRel = targetRel.slice(repoRoot.length + 1);
|
|
341
|
+
} else if (targetRel === repoRoot) {
|
|
342
|
+
targetRel = '.';
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
targetRel = cleanRelative(targetRel);
|
|
348
|
+
|
|
349
|
+
let bestMatch = null;
|
|
350
|
+
let longestPrefixLen = -1;
|
|
351
|
+
|
|
352
|
+
for (const pkg of graph.packages) {
|
|
353
|
+
const pkgRoot = cleanRelative(pkg.root);
|
|
354
|
+
|
|
355
|
+
if (pkgRoot === '.') {
|
|
356
|
+
// Root package matches everything as a baseline fallback
|
|
357
|
+
if (longestPrefixLen < 0) {
|
|
358
|
+
bestMatch = pkg;
|
|
359
|
+
longestPrefixLen = 0;
|
|
360
|
+
}
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Check if targetRel is inside pkgRoot
|
|
365
|
+
const isMatch =
|
|
366
|
+
process.platform === 'win32'
|
|
367
|
+
? targetRel.toLowerCase() === pkgRoot.toLowerCase() ||
|
|
368
|
+
targetRel.toLowerCase().startsWith(pkgRoot.toLowerCase() + '/')
|
|
369
|
+
: targetRel === pkgRoot || targetRel.startsWith(pkgRoot + '/');
|
|
370
|
+
|
|
371
|
+
if (isMatch) {
|
|
372
|
+
if (pkgRoot.length > longestPrefixLen) {
|
|
373
|
+
bestMatch = pkg;
|
|
374
|
+
longestPrefixLen = pkgRoot.length;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
return bestMatch;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Extracts evidence signals for a file based on its nearest enclosing package.
|
|
384
|
+
* @param {string} filePath
|
|
385
|
+
* @param {object} graph
|
|
386
|
+
* @returns {Array<object>} Evidence items with nearest package weights
|
|
387
|
+
*/
|
|
388
|
+
extractPackageEvidence(filePath, graph) {
|
|
389
|
+
const pkg = this.findNearestPackage(filePath, graph);
|
|
390
|
+
if (!pkg) return [];
|
|
391
|
+
|
|
392
|
+
const evidence = [];
|
|
393
|
+
|
|
394
|
+
// Dependencies in nearest package get high confidence weight (60)
|
|
395
|
+
for (const dep of pkg.dependencies) {
|
|
396
|
+
evidence.push({
|
|
397
|
+
target: dep,
|
|
398
|
+
source: 'nearest-package',
|
|
399
|
+
weight: 60,
|
|
400
|
+
description: `nearest workspace package "${dep}" in ${pkg.id}`
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// Config files in nearest package get weight 50
|
|
405
|
+
for (const cfg of pkg.configs) {
|
|
406
|
+
evidence.push({
|
|
407
|
+
target: cfg,
|
|
408
|
+
source: 'package-config',
|
|
409
|
+
weight: 50,
|
|
410
|
+
description: `package config "${cfg}" in ${pkg.id}`
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
return evidence;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Inspects a directory to determine if it is a package in any supported ecosystem.
|
|
419
|
+
* @private
|
|
420
|
+
*/
|
|
421
|
+
_inspectDirectoryForPackage(dirPath, relativeDir, repoRoot) {
|
|
422
|
+
const cleanRel = cleanRelative(relativeDir);
|
|
423
|
+
const manifests = [];
|
|
424
|
+
const manifestHashes = [];
|
|
425
|
+
const dependencies = new Set();
|
|
426
|
+
const languages = new Set();
|
|
427
|
+
let ecosystem = 'unknown';
|
|
428
|
+
let id = cleanRel === '.' ? path.basename(repoRoot) : path.basename(dirPath);
|
|
429
|
+
|
|
430
|
+
// 1. Node / NPM (package.json)
|
|
431
|
+
const pkgJsonPath = path.join(dirPath, 'package.json');
|
|
432
|
+
if (fs.existsSync(pkgJsonPath)) {
|
|
433
|
+
try {
|
|
434
|
+
const content = fs.readFileSync(pkgJsonPath, 'utf8');
|
|
435
|
+
const parsed = JSON.parse(content);
|
|
436
|
+
ecosystem = 'npm';
|
|
437
|
+
manifests.push(cleanRel === '.' ? 'package.json' : `${cleanRel}/package.json`);
|
|
438
|
+
manifestHashes.push(
|
|
439
|
+
crypto.createHash('sha256').update(content).digest('hex')
|
|
440
|
+
);
|
|
441
|
+
|
|
442
|
+
if (parsed.name) id = parsed.name;
|
|
443
|
+
|
|
444
|
+
// Collect all dependencies
|
|
445
|
+
const allDeps = {
|
|
446
|
+
...parsed.dependencies,
|
|
447
|
+
...parsed.devDependencies,
|
|
448
|
+
...parsed.peerDependencies
|
|
449
|
+
};
|
|
450
|
+
for (const dep of Object.keys(allDeps)) {
|
|
451
|
+
dependencies.add(dep);
|
|
452
|
+
}
|
|
453
|
+
} catch {
|
|
454
|
+
// invalid package.json
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// 2. Python (pyproject.toml, requirements.txt, Pipfile, setup.py)
|
|
459
|
+
const pyprojectPath = path.join(dirPath, 'pyproject.toml');
|
|
460
|
+
const requirementsPath = path.join(dirPath, 'requirements.txt');
|
|
461
|
+
const pipfilePath = path.join(dirPath, 'Pipfile');
|
|
462
|
+
const setupPyPath = path.join(dirPath, 'setup.py');
|
|
463
|
+
|
|
464
|
+
if (
|
|
465
|
+
fs.existsSync(pyprojectPath) ||
|
|
466
|
+
fs.existsSync(requirementsPath) ||
|
|
467
|
+
fs.existsSync(pipfilePath) ||
|
|
468
|
+
fs.existsSync(setupPyPath)
|
|
469
|
+
) {
|
|
470
|
+
if (ecosystem === 'unknown') ecosystem = 'python';
|
|
471
|
+
languages.add('python');
|
|
472
|
+
|
|
473
|
+
if (fs.existsSync(pyprojectPath)) {
|
|
474
|
+
try {
|
|
475
|
+
const content = fs.readFileSync(pyprojectPath, 'utf8');
|
|
476
|
+
manifests.push(cleanRel === '.' ? 'pyproject.toml' : `${cleanRel}/pyproject.toml`);
|
|
477
|
+
manifestHashes.push(
|
|
478
|
+
crypto.createHash('sha256').update(content).digest('hex')
|
|
479
|
+
);
|
|
480
|
+
const pyDeps = this._parsePyprojectDependencies(content);
|
|
481
|
+
for (const d of pyDeps) dependencies.add(d);
|
|
482
|
+
const nameMatch = content.match(/name\s*=\s*["']([^"']+)["']/);
|
|
483
|
+
if (nameMatch && ecosystem === 'python') id = nameMatch[1];
|
|
484
|
+
} catch {
|
|
485
|
+
// ignore
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
if (fs.existsSync(requirementsPath)) {
|
|
490
|
+
try {
|
|
491
|
+
const content = fs.readFileSync(requirementsPath, 'utf8');
|
|
492
|
+
manifests.push(cleanRel === '.' ? 'requirements.txt' : `${cleanRel}/requirements.txt`);
|
|
493
|
+
manifestHashes.push(
|
|
494
|
+
crypto.createHash('sha256').update(content).digest('hex')
|
|
495
|
+
);
|
|
496
|
+
const reqDeps = this._parseRequirementsDependencies(content);
|
|
497
|
+
for (const d of reqDeps) dependencies.add(d);
|
|
498
|
+
} catch {
|
|
499
|
+
// ignore
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// 3. Rust (Cargo.toml)
|
|
505
|
+
const cargoPath = path.join(dirPath, 'Cargo.toml');
|
|
506
|
+
if (fs.existsSync(cargoPath)) {
|
|
507
|
+
if (ecosystem === 'unknown') ecosystem = 'rust';
|
|
508
|
+
languages.add('rust');
|
|
509
|
+
try {
|
|
510
|
+
const content = fs.readFileSync(cargoPath, 'utf8');
|
|
511
|
+
manifests.push(cleanRel === '.' ? 'Cargo.toml' : `${cleanRel}/Cargo.toml`);
|
|
512
|
+
manifestHashes.push(
|
|
513
|
+
crypto.createHash('sha256').update(content).digest('hex')
|
|
514
|
+
);
|
|
515
|
+
const nameMatch = content.match(/\[package\][\s\S]*?name\s*=\s*["']([^"']+)["']/);
|
|
516
|
+
if (nameMatch && ecosystem === 'rust') id = nameMatch[1];
|
|
517
|
+
const cargoDeps = this._parseCargoDependencies(content);
|
|
518
|
+
for (const d of cargoDeps) dependencies.add(d);
|
|
519
|
+
} catch {
|
|
520
|
+
// ignore
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// 4. Go (go.mod)
|
|
525
|
+
const goModPath = path.join(dirPath, 'go.mod');
|
|
526
|
+
if (fs.existsSync(goModPath)) {
|
|
527
|
+
if (ecosystem === 'unknown') ecosystem = 'go';
|
|
528
|
+
languages.add('go');
|
|
529
|
+
try {
|
|
530
|
+
const content = fs.readFileSync(goModPath, 'utf8');
|
|
531
|
+
manifests.push(cleanRel === '.' ? 'go.mod' : `${cleanRel}/go.mod`);
|
|
532
|
+
manifestHashes.push(
|
|
533
|
+
crypto.createHash('sha256').update(content).digest('hex')
|
|
534
|
+
);
|
|
535
|
+
const modMatch = content.match(/module\s+([^\s\r\n]+)/);
|
|
536
|
+
if (modMatch && ecosystem === 'go') id = modMatch[1];
|
|
537
|
+
const goDeps = this._parseGoModDependencies(content);
|
|
538
|
+
for (const d of goDeps) dependencies.add(d);
|
|
539
|
+
} catch {
|
|
540
|
+
// ignore
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// If no manifest found, not a package
|
|
545
|
+
if (manifests.length === 0) {
|
|
546
|
+
return null;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// Detect configs
|
|
550
|
+
const configs = this._detectConfigs(dirPath, cleanRel);
|
|
551
|
+
|
|
552
|
+
// Detect languages if not already determined
|
|
553
|
+
this._detectLanguages(dirPath, languages);
|
|
554
|
+
|
|
555
|
+
return {
|
|
556
|
+
id,
|
|
557
|
+
root: cleanRel,
|
|
558
|
+
ecosystem,
|
|
559
|
+
manifests,
|
|
560
|
+
dependencies: Array.from(dependencies).sort(),
|
|
561
|
+
configs: configs.sort(),
|
|
562
|
+
languages: Array.from(languages).sort(),
|
|
563
|
+
internalDependencies: [],
|
|
564
|
+
manifestHashes
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
/**
|
|
569
|
+
* Detects declared workspace patterns from root manifest files.
|
|
570
|
+
* @private
|
|
571
|
+
*/
|
|
572
|
+
_detectDeclaredWorkspaces(rootDir) {
|
|
573
|
+
const patterns = [];
|
|
574
|
+
|
|
575
|
+
// Check package.json workspaces
|
|
576
|
+
const pkgPath = path.join(rootDir, 'package.json');
|
|
577
|
+
if (fs.existsSync(pkgPath)) {
|
|
578
|
+
try {
|
|
579
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
580
|
+
let ws = pkg.workspaces;
|
|
581
|
+
if (ws && typeof ws === 'object' && Array.isArray(ws.packages)) {
|
|
582
|
+
ws = ws.packages;
|
|
583
|
+
}
|
|
584
|
+
if (Array.isArray(ws)) {
|
|
585
|
+
for (const item of ws) {
|
|
586
|
+
if (typeof item === 'string') {
|
|
587
|
+
patterns.push(globToRegex(item));
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
} catch {
|
|
592
|
+
// ignore
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// Check pnpm-workspace.yaml
|
|
597
|
+
const pnpmPath = path.join(rootDir, 'pnpm-workspace.yaml');
|
|
598
|
+
if (fs.existsSync(pnpmPath)) {
|
|
599
|
+
try {
|
|
600
|
+
const content = fs.readFileSync(pnpmPath, 'utf8');
|
|
601
|
+
const lines = content.split(/\r?\n/);
|
|
602
|
+
let inPackages = false;
|
|
603
|
+
for (const line of lines) {
|
|
604
|
+
const trimmed = line.trim();
|
|
605
|
+
if (trimmed.startsWith('packages:')) {
|
|
606
|
+
inPackages = true;
|
|
607
|
+
continue;
|
|
608
|
+
}
|
|
609
|
+
if (inPackages) {
|
|
610
|
+
if (trimmed.startsWith('-')) {
|
|
611
|
+
const val = trimmed.replace(/^-\s*/, '').replace(/['"]/g, '').trim();
|
|
612
|
+
if (val && !val.startsWith('!')) {
|
|
613
|
+
patterns.push(globToRegex(val));
|
|
614
|
+
}
|
|
615
|
+
} else if (trimmed && !trimmed.startsWith('#')) {
|
|
616
|
+
inPackages = false;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
} catch {
|
|
621
|
+
// ignore
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
return patterns;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
/**
|
|
629
|
+
* Detects known configuration files in a package directory.
|
|
630
|
+
* @private
|
|
631
|
+
*/
|
|
632
|
+
_detectConfigs(dirPath, relativeDir) {
|
|
633
|
+
const configs = [];
|
|
634
|
+
for (const cfg of KNOWN_CONFIG_FILES) {
|
|
635
|
+
const fullPath = path.join(dirPath, cfg);
|
|
636
|
+
if (fs.existsSync(fullPath)) {
|
|
637
|
+
configs.push(relativeDir === '.' ? cfg : `${relativeDir}/${cfg}`);
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
return configs;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
/**
|
|
644
|
+
* Scans shallowly for language indicators in the package directory.
|
|
645
|
+
* @private
|
|
646
|
+
*/
|
|
647
|
+
_detectLanguages(dirPath, languagesSet) {
|
|
648
|
+
let entries;
|
|
649
|
+
try {
|
|
650
|
+
entries = fs.readdirSync(dirPath, { withFileTypes: true });
|
|
651
|
+
} catch {
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
for (const entry of entries) {
|
|
656
|
+
if (entry.isFile()) {
|
|
657
|
+
const ext = path.extname(entry.name).toLowerCase();
|
|
658
|
+
if (ext === '.ts' || ext === '.tsx') {
|
|
659
|
+
languagesSet.add('typescript');
|
|
660
|
+
} else if (ext === '.js' || ext === '.jsx' || ext === '.mjs' || ext === '.cjs') {
|
|
661
|
+
languagesSet.add('javascript');
|
|
662
|
+
} else if (ext === '.py') {
|
|
663
|
+
languagesSet.add('python');
|
|
664
|
+
} else if (ext === '.rs') {
|
|
665
|
+
languagesSet.add('rust');
|
|
666
|
+
} else if (ext === '.go') {
|
|
667
|
+
languagesSet.add('go');
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
/**
|
|
674
|
+
* Parses dependencies from requirements.txt content.
|
|
675
|
+
* @private
|
|
676
|
+
*/
|
|
677
|
+
_parseRequirementsDependencies(content) {
|
|
678
|
+
const deps = [];
|
|
679
|
+
const lines = content.split(/\r?\n/);
|
|
680
|
+
for (const line of lines) {
|
|
681
|
+
const clean = line.split('#')[0].trim();
|
|
682
|
+
if (!clean) continue;
|
|
683
|
+
const match = clean.match(/^([a-zA-Z0-9_\-\.]+)/);
|
|
684
|
+
if (match) {
|
|
685
|
+
deps.push(match[1].toLowerCase());
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
return deps;
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
/**
|
|
692
|
+
* Parses dependencies from pyproject.toml content.
|
|
693
|
+
* @private
|
|
694
|
+
*/
|
|
695
|
+
_parsePyprojectDependencies(content) {
|
|
696
|
+
const deps = [];
|
|
697
|
+
// Match dependencies array: dependencies = [ "foo>=1.0", "bar" ]
|
|
698
|
+
const arrayMatch = content.match(/dependencies\s*=\s*\[([\s\S]*?)\]/);
|
|
699
|
+
if (arrayMatch) {
|
|
700
|
+
const inner = arrayMatch[1];
|
|
701
|
+
const items = inner.match(/["']([^"']+)["']/g) || [];
|
|
702
|
+
for (const item of items) {
|
|
703
|
+
const raw = item.replace(/["']/g, '').trim();
|
|
704
|
+
const m = raw.match(/^([a-zA-Z0-9_\-\.]+)/);
|
|
705
|
+
if (m) deps.push(m[1].toLowerCase());
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
// Match poetry dependencies [tool.poetry.dependencies]
|
|
710
|
+
const poetrySection = content.match(/\[tool\.poetry\.dependencies\]([\s\S]*?)(?=\n\[|$)/);
|
|
711
|
+
if (poetrySection) {
|
|
712
|
+
const lines = poetrySection[1].split(/\r?\n/);
|
|
713
|
+
for (const line of lines) {
|
|
714
|
+
const trimmed = line.trim();
|
|
715
|
+
if (!trimmed || trimmed.startsWith('#')) continue;
|
|
716
|
+
const m = trimmed.match(/^([a-zA-Z0-9_\-\.]+)\s*=/);
|
|
717
|
+
if (m && m[1].toLowerCase() !== 'python') {
|
|
718
|
+
deps.push(m[1].toLowerCase());
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
return deps;
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
/**
|
|
727
|
+
* Parses dependencies from Cargo.toml content.
|
|
728
|
+
* @private
|
|
729
|
+
*/
|
|
730
|
+
_parseCargoDependencies(content) {
|
|
731
|
+
const deps = [];
|
|
732
|
+
const depSection = content.match(/\[(?:workspace\.)?dependencies\]([\s\S]*?)(?=\n\[|$)/);
|
|
733
|
+
if (depSection) {
|
|
734
|
+
const lines = depSection[1].split(/\r?\n/);
|
|
735
|
+
for (const line of lines) {
|
|
736
|
+
const trimmed = line.trim();
|
|
737
|
+
if (!trimmed || trimmed.startsWith('#')) continue;
|
|
738
|
+
const m = trimmed.match(/^([a-zA-Z0-9_\-]+)\s*=/);
|
|
739
|
+
if (m) deps.push(m[1]);
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
return deps;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
/**
|
|
746
|
+
* Parses dependencies from go.mod content.
|
|
747
|
+
* @private
|
|
748
|
+
*/
|
|
749
|
+
_parseGoModDependencies(content) {
|
|
750
|
+
const deps = [];
|
|
751
|
+
// Match single require
|
|
752
|
+
const singleMatches = content.matchAll(/require\s+([^\s\r\n]+)\s+/g);
|
|
753
|
+
for (const m of singleMatches) {
|
|
754
|
+
deps.push(m[1]);
|
|
755
|
+
}
|
|
756
|
+
// Match require block
|
|
757
|
+
const blockMatch = content.match(/require\s*\(([\s\S]*?)\)/);
|
|
758
|
+
if (blockMatch) {
|
|
759
|
+
const lines = blockMatch[1].split(/\r?\n/);
|
|
760
|
+
for (const line of lines) {
|
|
761
|
+
const trimmed = line.trim();
|
|
762
|
+
if (!trimmed || trimmed.startsWith('//')) continue;
|
|
763
|
+
const m = trimmed.match(/^([^\s]+)/);
|
|
764
|
+
if (m) deps.push(m[1]);
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
return deps;
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
module.exports = {
|
|
772
|
+
WorkspaceGraphBuilder,
|
|
773
|
+
toPosix,
|
|
774
|
+
cleanRelative,
|
|
775
|
+
globToRegex,
|
|
776
|
+
DEFAULT_IGNORED_DIRS,
|
|
777
|
+
KNOWN_CONFIG_FILES
|
|
778
|
+
};
|